From d44966e092fd0796ae305d66c69436f69e2cc6ce Mon Sep 17 00:00:00 2001
From: Lari Taskula <lari.taskula@jns.fi>
Date: Fri, 21 Oct 2016 17:26:24 +0300
Subject: [PATCH] Bug 17499: Add Koha-objects for messaging preferences
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=utf-8

This patch adds Koha-objects for messaging preferences. This patch does not
add any extra logic into these objects.

Includes test coverage for basic usage.

To test:
1. prove t/db_dependent/Koha/Patron/Message/*

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Add useful Koha::Exceptions

This patch adds some basic Koha::Exceptions that will be useful.

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Add basic validation for messaging preference

This patch adds simple validation for messaging preferences.

The validation includes
- check that only either borrowernumber or categorycode is given, but not both
- throw exception if patron for the given borrowernumber is not found
- throw exception if category for the given categorycode is not found
- throw exception if days in advance cannot be configured
- throw exception if days in advance configuration is invalid (value between 0-30)
- throw exception if digest is not available

To test:
1. Run t/db_dependent/Koha/Patron/Message/Preferences.t

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Throw an exception on duplicate messaging preference

When trying to add a duplicate messaging preference that has the same
borrowernumber or category, and message_attribute_id as another preference,
throw Koha::Exceptions::DuplicateObject.

To test:
1. Run t/db_dependent/Koha/Patron/Message/Preferences.t

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Add a method for getting messaging options

This patch adds a method for getting available messaging options.

To test:
1. Run t/db_dependent/Koha/Patron/Message/Preferences.t

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Add a method for setting default messaging preferences

This patch adds a method Koha::Patron::Message::Preference->new_from_default that
can be used to add category's default messaging preferences to patron for a given
message type.

Example call:

Koha::Patron::Message::Preference->new_from_default({
    borrowernumber => 123,
    categorycode => "ABC",
    message_attribute_id => 1,
});

Also adds a simple method for Koha::Patron, set_default_messaging_preferences.
Usage: $patron->set_default_messaging_preferences()

To test:
1. Run t/db_dependent/Koha/Patron/Message/Preferences.t

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Improve object usability by enabling direct access to transport preferences

Since messaging preference is a feature that has multiple related database tables,
usage via Koha-objects is sometimes frustrating.

This patch adds a feature for Koha::Patron::Message::Preference which enables
access to message transport preferences directly via this object. It allows us
to skip calls to Koha::Patron::Message::Transport::Preference(s) because we can
now get and set via K::P::M::Preference.

Get:
$preference->message_transport_types
Returns a hashref, where each key is stored transport type and value for the key
is letter code for the transport.

Set:
$preference->set({ message_transport_types => ['sms'] }) or
$preference->message_transport_types('email', 'sms') or
$preference->message_transport_types(['email', 'sms'])
Returns $self (Koha::Patron::Message::Preference object)

To test:
1. Run t/db_dependent/Koha/Patron/Message/Preferences.t

Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: Simplify searching by message_name

This patch adds optional search & find parameter message_name for
Koha::Patron::Message::Preferences->search and ->find. This simplifies object
usage by allowing us to skip joins or finding the attribute id ourselves.

prove -v t/db_dependent/Koha/Patron/Message/Preferences.t turns green
Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: (follow-up) Bugfix for validation

- Bugfix for days_in_advance and wants_digest validation. Have to check definedness
  of value instead the value itself.
- Fixes broken tests caused by above update.
- Adding a missing unit test for wants_digest validation
- Minor edits to exception messages.
- Throwing exception for invalid message_attribute_id. Incl. unit test

Tested again, prove -v t/db_dependent/Koha/Patron/Message/Preferences.t turns green
prove -v t/db_dependent/Koha/Patron/Message/* turns green
Signed-off-by: Marc Véron <veron@veron.ch>

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: (follow-up) Bugfix for validation, 2

Remove duplicate validation for digest availability. This is already checked in
$preference->validate().

Set values for current object before validating messaging transport types, not
the other way around as it was before this patch.

Also prevents a crash when patron has messaging transport type selected for
a message for which it should not be available. This patch logs it with as
a warning, and only throws an exception when attempting to set it.

To test:
1. prove t/db_dependent/Koha/Patron/Message/*

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Bug 17499: (follow-up) Add information on digest being the only option to get_options

If a library wants to force digest on a message type, add this information to
Koha::Patron::Message::Preferences->get_options.

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Squashed 11 commits during QA.
---
 Koha/Exceptions.pm                                 |  18 +-
 Koha/Patron.pm                                     |  35 +
 Koha/Patron/Message/Attribute.pm                   |  50 ++
 Koha/Patron/Message/Attributes.pm                  |  55 ++
 Koha/Patron/Message/Preference.pm                  | 450 +++++++++++++
 Koha/Patron/Message/Preferences.pm                 | 146 +++++
 Koha/Patron/Message/Transport.pm                   |  50 ++
 Koha/Patron/Message/Transport/Preference.pm        |  51 ++
 Koha/Patron/Message/Transport/Preferences.pm       |  56 ++
 Koha/Patron/Message/Transport/Type.pm              |  51 ++
 Koha/Patron/Message/Transport/Types.pm             |  56 ++
 Koha/Patron/Message/Transports.pm                  |  55 ++
 t/db_dependent/Koha/Patron/Message/Attributes.t    |  74 +++
 t/db_dependent/Koha/Patron/Message/Preferences.t   | 704 +++++++++++++++++++++
 .../Koha/Patron/Message/Transport/Preferences.t    | 179 ++++++
 .../Koha/Patron/Message/Transport/Types.t          |  54 ++
 t/db_dependent/Koha/Patron/Message/Transports.t    | 119 ++++
 17 files changed, 2202 insertions(+), 1 deletion(-)
 create mode 100644 Koha/Patron/Message/Attribute.pm
 create mode 100644 Koha/Patron/Message/Attributes.pm
 create mode 100644 Koha/Patron/Message/Preference.pm
 create mode 100644 Koha/Patron/Message/Preferences.pm
 create mode 100644 Koha/Patron/Message/Transport.pm
 create mode 100644 Koha/Patron/Message/Transport/Preference.pm
 create mode 100644 Koha/Patron/Message/Transport/Preferences.pm
 create mode 100644 Koha/Patron/Message/Transport/Type.pm
 create mode 100644 Koha/Patron/Message/Transport/Types.pm
 create mode 100644 Koha/Patron/Message/Transports.pm
 create mode 100644 t/db_dependent/Koha/Patron/Message/Attributes.t
 create mode 100644 t/db_dependent/Koha/Patron/Message/Preferences.t
 create mode 100644 t/db_dependent/Koha/Patron/Message/Transport/Preferences.t
 create mode 100644 t/db_dependent/Koha/Patron/Message/Transport/Types.t
 create mode 100644 t/db_dependent/Koha/Patron/Message/Transports.t

diff --git a/Koha/Exceptions.pm b/Koha/Exceptions.pm
index 60b9e6a..793aafa 100644
--- a/Koha/Exceptions.pm
+++ b/Koha/Exceptions.pm
@@ -8,6 +8,12 @@ use Exception::Class (
     'Koha::Exceptions::Exception' => {
         description => 'Something went wrong!',
     },
+
+    'Koha::Exceptions::BadParameter' => {
+        isa => 'Koha::Exceptions::Exception',
+        description => 'Bad given parameter',
+        fields => ['parameter'],
+    },
     'Koha::Exceptions::DuplicateObject' => {
         isa => 'Koha::Exceptions::Exception',
         description => 'Same object already exists',
@@ -22,7 +28,13 @@ use Exception::Class (
     },
     'Koha::Exceptions::MissingParameter' => {
         isa => 'Koha::Exceptions::Exception',
-        description => 'A required parameter is missing'
+        description => 'A required parameter is missing',
+        fields => ['parameter'],
+    },
+    'Koha::Exceptions::TooManyParameters' => {
+        isa => 'Koha::Exceptions::Exception',
+        description => 'Too many parameters given',
+        fields => ['parameter'],
     },
     'Koha::Exceptions::WrongParameter' => {
         isa => 'Koha::Exceptions::Exception',
@@ -32,6 +44,10 @@ use Exception::Class (
         isa => 'Koha::Exceptions::Exception',
         description => 'General problem adding a library limit'
     },
+    'Koha::Exceptions::UnknownObject' => {
+        isa => 'Koha::Exceptions::Exception',
+        description => 'Object cannot be found or is not known',
+    },
     # Virtualshelves exceptions
     'Koha::Exceptions::Virtualshelves::DuplicateObject' => {
         isa => 'Koha::Exceptions::DuplicateObject',
diff --git a/Koha/Patron.pm b/Koha/Patron.pm
index f7970ea..9b2f11a 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -33,6 +33,7 @@ use Koha::Patron::Categories;
 use Koha::Patron::HouseboundProfile;
 use Koha::Patron::HouseboundRole;
 use Koha::Patron::Images;
+use Koha::Patron::Message::Preferences;
 use Koha::Patrons;
 use Koha::Virtualshelves;
 use Koha::Club::Enrollments;
@@ -654,6 +655,40 @@ sub account_locked {
           and $self->login_attempts >= $FailedLoginAttempts )? 1 : 0;
 }
 
+=head3 set_default_messaging_preferences
+
+    $patron->set_default_messaging_preferences
+
+Sets default messaging preferences on patron.
+
+See Koha::Patron::Message::Preference(s) for more documentation, especially on
+thrown exceptions.
+
+=cut
+
+sub set_default_messaging_preferences {
+    my ($self, $categorycode) = @_;
+
+    my $options = Koha::Patron::Message::Preferences->get_options;
+
+    foreach my $option (@$options) {
+        # Check that this option has preference configuration for this category
+        unless (Koha::Patron::Message::Preferences->search({
+            message_attribute_id => $option->{message_attribute_id},
+            categorycode         => $categorycode || $self->categorycode,
+        })->count) {
+            next;
+        }
+        Koha::Patron::Message::Preference->new_from_default({
+            borrowernumber => $self->borrowernumber,
+            categorycode   => $categorycode || $self->categorycode,
+            message_attribute_id => $option->{message_attribute_id},
+        })->store;
+    }
+
+    return $self;
+}
+
 =head3 type
 
 =cut
diff --git a/Koha/Patron/Message/Attribute.pm b/Koha/Patron/Message/Attribute.pm
new file mode 100644
index 0000000..9d9004c
--- /dev/null
+++ b/Koha/Patron/Message/Attribute.pm
@@ -0,0 +1,50 @@
+package Koha::Patron::Message::Attribute;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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.a
+#
+# 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 Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Patron::Message::Attribute - Koha Patron Message Attribute object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageAttribute';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Attributes.pm b/Koha/Patron/Message/Attributes.pm
new file mode 100644
index 0000000..a2df4e6
--- /dev/null
+++ b/Koha/Patron/Message/Attributes.pm
@@ -0,0 +1,55 @@
+package Koha::Patron::Message::Attributes;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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 Koha::Database;
+use Koha::Patron::Message::Attribute;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Patron::Message::Attributes - Koha Patron Message Attributes object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageAttribute';
+}
+
+sub object_class {
+    return 'Koha::Patron::Message::Attribute';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Preference.pm b/Koha/Patron/Message/Preference.pm
new file mode 100644
index 0000000..6fb7fb8
--- /dev/null
+++ b/Koha/Patron/Message/Preference.pm
@@ -0,0 +1,450 @@
+package Koha::Patron::Message::Preference;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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.a
+#
+# 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 Koha::Database;
+use Koha::Exceptions;
+use Koha::Patron::Categories;
+use Koha::Patron::Message::Attributes;
+use Koha::Patron::Message::Preferences;
+use Koha::Patron::Message::Transport::Preferences;
+use Koha::Patron::Message::Transport::Types;
+use Koha::Patron::Message::Transports;
+use Koha::Patrons;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Patron::Message::Preference - Koha Patron Message Preference object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 new
+
+my $preference = Koha::Patron::Message::Preference->new({
+   borrowernumber => 123,
+   #categorycode => 'ABC',
+   message_attribute_id => 4,
+   message_transport_types => ['email', 'sms'], # see documentation below
+   wants_digest => 1,
+   days_in_advance => 7,
+});
+
+Takes either borrowernumber or categorycode, but not both.
+
+days_in_advance may not be available. See message_attributes table for takes_days
+configuration.
+
+wants_digest may not be available. See message_transports table for is_digest
+configuration.
+
+You can instantiate a new object without custom validation errors, but when
+storing, validation may throw exceptions. See C<validate()> for more
+documentation.
+
+C<message_transport_types> is a parameter that is not actually a column in this
+Koha-object. Given this parameter, the message transport types will be added as
+related transport types for this object. For get and set, you can access them via
+subroutine C<message_transport_types()> in this class.
+
+=cut
+
+sub new {
+    my ($class, $params) = @_;
+
+    my $types = $params->{'message_transport_types'};
+    delete $params->{'message_transport_types'};
+
+    my $self = $class->SUPER::new($params);
+
+    $self->_set_message_transport_types($types);
+
+    return $self;
+}
+
+=head3 new_from_default
+
+my $preference = Koha::Patron::Message::Preference->new_from_default({
+    borrowernumber => 123,
+    categorycode   => 'ABC',   # if not given, patron's categorycode will be used
+    message_attribute_id => 1,
+});
+
+NOTE: This subroutine initializes and STORES the object (in order to set
+message transport types for the preference), so no need to call ->store when
+preferences are initialized via this method.
+
+Stores default messaging preference for C<categorycode> to patron for given
+C<message_attribute_id>.
+
+Throws Koha::Exceptions::MissingParameter if any of following is missing:
+- borrowernumber
+- message_attribute_id
+
+Throws Koha::Exceptions::UnknownObject if default preferences are not found.
+
+=cut
+
+sub new_from_default {
+    my ($class, $params) = @_;
+
+    my @required = qw(borrowernumber message_attribute_id);
+    foreach my $p (@required) {
+        Koha::Exceptions::MissingParameter->throw(
+            error => 'Missing required parameter.',
+            parameter => $p,
+        ) unless exists $params->{$p};
+    }
+    unless ($params->{'categorycode'}) {
+        my $patron = Koha::Patrons->find($params->{borrowernumber});
+        $params->{'categorycode'} = $patron->categorycode;
+    }
+
+    my $default = Koha::Patron::Message::Preferences->find({
+        categorycode => $params->{'categorycode'},
+        message_attribute_id => $params->{'message_attribute_id'},
+    });
+    Koha::Exceptions::UnknownObject->throw(
+        error => 'Default messaging preference for given categorycode and'
+        .' message_attribute_id cannot be found.',
+    ) unless $default;
+    $default = $default->unblessed;
+
+    # Add a new messaging preference for patron
+    my $self = $class->SUPER::new({
+        borrowernumber => $params->{'borrowernumber'},
+        message_attribute_id => $default->{'message_attribute_id'},
+        days_in_advance => $default->{'days_in_advance'},
+        wants_digest => $default->{'wants_digest'},
+    })->store;
+
+    # Set default messaging transport types
+    my $default_transport_types =
+    Koha::Patron::Message::Transport::Preferences->search({
+        borrower_message_preference_id =>
+                    $default->{'borrower_message_preference_id'}
+    });
+    while (my $transport = $default_transport_types->next) {
+        Koha::Patron::Message::Transport::Preference->new({
+            borrower_message_preference_id => $self->borrower_message_preference_id,
+            message_transport_type => $transport->message_transport_type,
+        })->store;
+    }
+
+    return $self;
+}
+
+=head3 message_name
+
+$preference->message_name
+
+Gets message_name for this messaging preference.
+
+Setter not implemented.
+
+=cut
+
+sub message_name {
+    my ($self) = @_;
+
+    if ($self->{'_message_name'}) {
+        return $self->{'_message_name'};
+    }
+    $self->{'_message_name'} = Koha::Patron::Message::Attributes->find({
+        message_attribute_id => $self->message_attribute_id,
+    })->message_name;
+    return $self->{'_message_name'};
+}
+
+=head3 message_transport_types
+
+$preference->message_transport_types
+Returns a HASHREF of message transport types for this messaging preference, e.g.
+if ($preference->message_transport_types->{'email'}) {
+    # email is one of the transport preferences
+}
+
+$preference->message_transport_types('email', 'sms');
+Sets the given message transport types for this messaging preference
+
+=cut
+
+sub message_transport_types {
+    my $self = shift;
+
+    unless (@_) {
+        if ($self->{'_message_transport_types'}) {
+            return $self->{'_message_transport_types'};
+        }
+        map {
+            my $transport = Koha::Patron::Message::Transports->find({
+                message_attribute_id => $self->message_attribute_id,
+                message_transport_type => $_->message_transport_type,
+                is_digest => $self->wants_digest
+            });
+            unless ($transport) {
+                my $logger = Koha::Logger->get;
+                $logger->warn(
+                    $self->message_name . ' has no transport with '.
+                    $_->message_transport_type . ' (digest: '.
+                    ($self->wants_digest ? 'yes':'no').').'
+                );
+            }
+            $self->{'_message_transport_types'}->{$_->message_transport_type}
+                = $transport ? $transport->letter_code : ' ';
+        }
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $self->borrower_message_preference_id,
+        })->as_list;
+        return $self->{'_message_transport_types'} || {};
+    }
+    else {
+        $self->_set_message_transport_types(@_);
+        return $self;
+    }
+}
+
+=head3 set
+
+$preference->set({
+    message_transport_types => ['sms', 'phone'],
+    wants_digest => 0,
+})->store;
+
+Sets preference object values and additionally message_transport_types if given.
+
+=cut
+
+sub set {
+    my ($self, $params) = @_;
+
+    my $mtt = $params->{'message_transport_types'};
+    delete $params->{'message_transport_types'};
+
+    $self->SUPER::set($params) if $params;
+    if ($mtt) {
+        $self->message_transport_types($mtt);
+    }
+
+    return $self;
+}
+
+=head3 store
+
+Makes a validation before actual Koha::Object->store so that proper exceptions
+can be thrown. See C<validate()> for documentation about exceptions.
+
+=cut
+
+sub store {
+    my $self = shift;
+
+    $self->validate->SUPER::store(@_);
+
+    # store message transport types
+    if (exists $self->{'_message_transport_types'}) {
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id =>
+                $self->borrower_message_preference_id,
+        })->delete;
+        foreach my $type (keys %{$self->{'_message_transport_types'}}) {
+            Koha::Patron::Message::Transport::Preference->new({
+                borrower_message_preference_id =>
+                    $self->borrower_message_preference_id,
+                message_transport_type => $type,
+            })->store;
+        }
+    }
+
+    return $self;
+}
+
+=head3 validate
+
+Makes a basic validation for object.
+
+Throws following exceptions regarding parameters.
+- Koha::Exceptions::MissingParameter
+- Koha::Exceptions::TooManyParameters
+- Koha::Exceptions::BadParameter
+
+See $_->parameter to identify the parameter causing the exception.
+
+Throws Koha::Exceptions::DuplicateObject if this preference already exists.
+
+Returns Koha::Patron::Message::Preference object.
+
+=cut
+
+sub validate {
+    my ($self) = @_;
+
+    if ($self->borrowernumber && $self->categorycode) {
+        Koha::Exceptions::TooManyParameters->throw(
+            error => 'Both borrowernumber and category given, only one accepted',
+            parameter => ['borrowernumber', 'categorycode'],
+        );
+    }
+    if (!$self->borrowernumber && !$self->categorycode) {
+        Koha::Exceptions::MissingParameter->throw(
+            error => 'borrowernumber or category required, none given',
+            parameter => ['borrowernumber', 'categorycode'],
+        );
+    }
+    if ($self->borrowernumber) {
+        Koha::Exceptions::BadParameter->throw(
+            error => 'Patron not found.',
+            parameter => 'borrowernumber',
+        ) unless Koha::Patrons->find($self->borrowernumber);
+    }
+    if ($self->categorycode) {
+        Koha::Exceptions::BadParameter->throw(
+            error => 'Category not found.',
+            parameter => 'categorycode',
+        ) unless Koha::Patron::Categories->find($self->categorycode);
+    }
+
+    if (!$self->in_storage) {
+        my $previous = Koha::Patron::Message::Preferences->search({
+            borrowernumber => $self->borrowernumber,
+            categorycode   => $self->categorycode,
+            message_attribute_id => $self->message_attribute_id,
+        });
+        if ($previous->count) {
+            Koha::Exceptions::DuplicateObject->throw(
+                error => 'A preference for this borrower/category and'
+                .' message_attribute_id already exists',
+            );
+        }
+    }
+
+    my $attr = Koha::Patron::Message::Attributes->find(
+        $self->message_attribute_id
+    );
+    unless ($attr) {
+        Koha::Exceptions::BadParameter->throw(
+            error => 'Message attribute with id '.$self->message_attribute_id
+            .' not found',
+            parameter => 'message_attribute_id'
+        );
+    }
+    if (defined $self->days_in_advance) {
+        if ($attr && $attr->takes_days == 0) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'days_in_advance cannot be defined for '.
+                $attr->message_name . '.',
+                parameter => 'days_in_advance',
+            );
+        }
+        elsif ($self->days_in_advance < 0 || $self->days_in_advance > 30) {
+            Koha::Exceptions::BadParameter->throw(
+                error => 'days_in_advance has to be a value between 0-30 for '.
+                $attr->message_name . '.',
+                parameter => 'days_in_advance',
+            );
+        }
+    }
+    if (defined $self->wants_digest) {
+        my $transports = Koha::Patron::Message::Transports->search({
+            message_attribute_id => $self->message_attribute_id,
+            is_digest            => $self->wants_digest ? 1 : 0,
+        });
+        Koha::Exceptions::BadParameter->throw(
+            error => (!$self->wants_digest ? 'No d' : 'D').'igest not available '.
+            'for '.$attr->message_name.'.',
+            parameter => 'wants_digest',
+        ) if $transports->count == 0;
+    }
+
+    return $self;
+}
+
+sub _set_message_transport_types {
+    my $self = shift;
+
+    return unless $_[0];
+
+    $self->{'_message_transport_types'} = undef;
+    my $types = ref $_[0] eq "ARRAY" ? $_[0] : [@_];
+    return unless $types;
+    $self->_validate_message_transport_types({ message_transport_types => $types });
+    foreach my $type (@$types) {
+        unless (exists $self->{'_message_transport_types'}->{$type}) {
+            my $transport = Koha::Patron::Message::Transports->find({
+                message_attribute_id => $self->message_attribute_id,
+                message_transport_type => $type
+            });
+            unless ($transport) {
+                Koha::Exceptions::BadParameter->throw(
+                    error => 'No transport configured for '.$self->message_name.
+                        " transport type $type.",
+                    parameter => 'message_transport_types'
+                );
+            }
+            $self->{'_message_transport_types'}->{$type}
+                = $transport->letter_code;
+        }
+    }
+    return $self;
+}
+
+sub _validate_message_transport_types {
+    my ($self, $params) = @_;
+
+    if (ref($params) eq 'HASH' && $params->{'message_transport_types'}) {
+        if (ref($params->{'message_transport_types'}) ne 'ARRAY') {
+            $params->{'message_transport_types'} = [$params->{'message_transport_types'}];
+        }
+        my $types = $params->{'message_transport_types'};
+
+        foreach my $type (@{$types}) {
+            unless (Koha::Patron::Message::Transport::Types->find({
+                message_transport_type => $type
+            })) {
+                Koha::Exceptions::BadParameter->throw(
+                    error => "Message transport type '$type' does not exist",
+                    parameter => 'message_transport_types',
+                );
+            }
+        }
+        return $types;
+    }
+}
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'BorrowerMessagePreference';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Preferences.pm b/Koha/Patron/Message/Preferences.pm
new file mode 100644
index 0000000..500bb85
--- /dev/null
+++ b/Koha/Patron/Message/Preferences.pm
@@ -0,0 +1,146 @@
+package Koha::Patron::Message::Preferences;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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 Koha::Database;
+use Koha::Patron::Message::Attributes;
+use Koha::Patron::Message::Preference;
+use Koha::Patron::Message::Transports;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Patron::Message::Preferences - Koha Patron Message Preferences object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 find_with_message_name
+
+Koha::Patron::Message::Preferences->find_with_message_name({
+    borrowernumber => 123,
+    message_name => 'Hold_Filled',
+});
+
+Converts C<message_name> into C<message_attribute_id> and continues find.
+
+=cut
+
+sub find_with_message_name {
+    my ($self, $id) = @_;
+
+    if (ref($id) eq "HASH" && $id->{'message_name'}) {
+        my $attr = Koha::Patron::Message::Attributes->find({
+            message_name => $id->{'message_name'},
+        });
+        $id->{'message_attribute_id'} = ($attr) ?
+                    $attr->message_attribute_id : undef;
+        delete $id->{'message_name'};
+    }
+
+    return $self->SUPER::find($id);
+}
+
+=head3 get_options
+
+my $messaging_options = Koha::Patron::Message::Preferences->get_options
+
+Returns an ARRAYref of HASHrefs on available messaging options.
+
+=cut
+
+sub get_options {
+    my ($self) = @_;
+
+    my $transports = Koha::Patron::Message::Transports->search(undef,
+        {
+            join => ['message_attribute'],
+            '+select' => ['message_attribute.message_name', 'message_attribute.takes_days'],
+            '+as' => ['message_name', 'takes_days'],
+        });
+
+    my $choices;
+    while (my $transport = $transports->next) {
+        my $name = $transport->get_column('message_name');
+        $choices->{$name}->{'message_attribute_id'} = $transport->message_attribute_id;
+        $choices->{$name}->{'message_name'}         = $name;
+        $choices->{$name}->{'takes_days'}           = $transport->get_column('takes_days');
+        $choices->{$name}->{'has_digest'}           = $transport->is_digest ? 1 : 0;
+        $choices->{$name}->{'has_digest_off'}       = !$transport->is_digest ? 1 : 0;
+        $choices->{$name}->{'transport_'.$transport->get_column('message_transport_type')} = ' ';
+    }
+
+    my @return = values %$choices;
+    @return = sort { $a->{message_attribute_id} <=> $b->{message_attribute_id} } @return;
+
+    return \@return;
+}
+
+=head3 search
+
+Koha::Patron::Message::Preferences->search_with_message_name({
+    borrowernumber => 123,
+    message_name => 'Hold_Filled',
+});
+
+Converts C<message_name> into C<message_attribute_id> and continues search. Use
+Koha::Patron::Message::Preferences->search with a proper join for more complicated
+searches.
+
+=cut
+
+sub search_with_message_name {
+    my ($self, $params, $attributes) = @_;
+
+    if (ref($params) eq "HASH" && $params->{'message_name'}) {
+        my $attr = Koha::Patron::Message::Attributes->find({
+            message_name => $params->{'message_name'},
+        });
+        $params->{'message_attribute_id'} = ($attr) ?
+                    $attr->message_attribute_id : undef;
+        delete $params->{'message_name'};
+    }
+
+    return $self->SUPER::search($params, $attributes);
+}
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'BorrowerMessagePreference';
+}
+
+sub object_class {
+    return 'Koha::Patron::Message::Preference';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transport.pm b/Koha/Patron/Message/Transport.pm
new file mode 100644
index 0000000..ca0906e
--- /dev/null
+++ b/Koha/Patron/Message/Transport.pm
@@ -0,0 +1,50 @@
+package Koha::Patron::Message::Transport;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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.a
+#
+# 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 Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Patron::Message::Transport - Koha Patron Message Transport object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageTransport';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transport/Preference.pm b/Koha/Patron/Message/Transport/Preference.pm
new file mode 100644
index 0000000..fe0ddeb
--- /dev/null
+++ b/Koha/Patron/Message/Transport/Preference.pm
@@ -0,0 +1,51 @@
+package Koha::Patron::Message::Transport::Preference;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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.a
+#
+# 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 Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Patron::Message::Transport::Preference - Koha Patron Message Transport
+Preference object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'BorrowerMessageTransportPreference';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transport/Preferences.pm b/Koha/Patron/Message/Transport/Preferences.pm
new file mode 100644
index 0000000..aabd851
--- /dev/null
+++ b/Koha/Patron/Message/Transport/Preferences.pm
@@ -0,0 +1,56 @@
+package Koha::Patron::Message::Transport::Preferences;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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 Koha::Database;
+use Koha::Patron::Message::Transport::Preference;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Patron::Message::Transport::Preferences - Koha Patron Message Transport
+Preferences object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'BorrowerMessageTransportPreference';
+}
+
+sub object_class {
+    return 'Koha::Patron::Message::Transport::Preference';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transport/Type.pm b/Koha/Patron/Message/Transport/Type.pm
new file mode 100644
index 0000000..4a52a10
--- /dev/null
+++ b/Koha/Patron/Message/Transport/Type.pm
@@ -0,0 +1,51 @@
+package Koha::Patron::Message::Transport::Type;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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.a
+#
+# 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 Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::Patron::Message::Transport::Type - Koha Patron Message Transport Type
+object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageTransportType';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transport/Types.pm b/Koha/Patron/Message/Transport/Types.pm
new file mode 100644
index 0000000..2633ea3
--- /dev/null
+++ b/Koha/Patron/Message/Transport/Types.pm
@@ -0,0 +1,56 @@
+package Koha::Patron::Message::Transport::Types;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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 Koha::Database;
+use Koha::Patron::Message::Transport::Type;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Patron::Message::Transport::Types - Koha Patron Message Transport Types
+object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageTransportType';
+}
+
+sub object_class {
+    return 'Koha::Patron::Message::Transport::Type';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/Koha/Patron/Message/Transports.pm b/Koha/Patron/Message/Transports.pm
new file mode 100644
index 0000000..b6aee32
--- /dev/null
+++ b/Koha/Patron/Message/Transports.pm
@@ -0,0 +1,55 @@
+package Koha::Patron::Message::Transports;
+
+# Copyright Koha-Suomi Oy 2016
+#
+# 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 Koha::Database;
+use Koha::Patron::Message::Transport;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::Patron::Message::Transports - Koha Patron Message Transports object class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'MessageTransport';
+}
+
+sub object_class {
+    return 'Koha::Patron::Message::Transport';
+}
+
+=head1 AUTHOR
+
+Lari Taskula <lari.taskula@jns.fi>
+
+=cut
+
+1;
diff --git a/t/db_dependent/Koha/Patron/Message/Attributes.t b/t/db_dependent/Koha/Patron/Message/Attributes.t
new file mode 100644
index 0000000..836b4f0
--- /dev/null
+++ b/t/db_dependent/Koha/Patron/Message/Attributes.t
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+# Copyright 2017 Koha-Suomi Oy
+#
+# 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 Test::More tests => 2;
+
+use Koha::Database;
+
+my $schema  = Koha::Database->new->schema;
+
+subtest 'Test class imports' => sub {
+    plan tests => 2;
+
+    use_ok('Koha::Patron::Message::Attribute');
+    use_ok('Koha::Patron::Message::Attributes');
+};
+
+subtest 'Test Koha::Patron::Message::Attributes' => sub {
+    plan tests => 6;
+
+    $schema->storage->txn_begin;
+
+    Koha::Patron::Message::Attribute->new({
+        message_name => 'Test_Attribute'
+    })->store;
+    Koha::Patron::Message::Attribute->new({
+        message_name => 'Test_Attribute2',
+        takes_days   => 1
+    })->store;
+
+    my $attribute  = Koha::Patron::Message::Attributes->find({
+        message_name => 'Test_Attribute' });
+    my $attribute2 = Koha::Patron::Message::Attributes->find({
+        message_name => 'Test_Attribute2' });
+
+    is($attribute->message_name, 'Test_Attribute',
+       'Added a new messaging attribute.');
+    is($attribute->takes_days, 0,
+       'For that messaging attribute, takes_days is disabled by default.');
+    is($attribute2->message_name, 'Test_Attribute2',
+       'Added another messaging attribute.');
+    is($attribute2->takes_days, 1,
+       'takes_days is enabled for that message attribute (as expected).');
+
+    $attribute->delete;
+    $attribute2->delete;
+    is(Koha::Patron::Message::Attributes->find({
+        message_name => 'Test_Attribute' }), undef,
+       'Deleted the first message attribute.');
+    is(Koha::Patron::Message::Attributes->find({
+        message_name => 'Test_Attribute2' }), undef,
+       'Deleted the second message attribute.');
+
+    $schema->storage->txn_rollback;
+};
+
+1;
diff --git a/t/db_dependent/Koha/Patron/Message/Preferences.t b/t/db_dependent/Koha/Patron/Message/Preferences.t
new file mode 100644
index 0000000..c38545e
--- /dev/null
+++ b/t/db_dependent/Koha/Patron/Message/Preferences.t
@@ -0,0 +1,704 @@
+#!/usr/bin/perl
+
+# Copyright 2017 Koha-Suomi Oy
+#
+# 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 Test::More tests => 7;
+
+use t::lib::Mocks;
+use t::lib::TestBuilder;
+
+use C4::Context;
+
+use Koha::Notice::Templates;
+use Koha::Patron::Categories;
+use Koha::Patron::Message::Attributes;
+use Koha::Patron::Message::Transport::Types;
+use Koha::Patron::Message::Transports;
+use Koha::Patrons;
+
+use File::Temp qw/tempfile/;
+use Log::Log4perl;
+
+my $schema  = Koha::Database->new->schema;
+my $builder = t::lib::TestBuilder->new;
+
+subtest 'Test class imports' => sub {
+    plan tests => 2;
+
+    use_ok('Koha::Patron::Message::Preference');
+    use_ok('Koha::Patron::Message::Preferences');
+};
+
+subtest 'Test Koha::Patron::Message::Preferences' => sub {
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $attribute = build_a_test_attribute();
+    my $letter = build_a_test_letter();
+    my $mtt = build_a_test_transport_type();
+    Koha::Patron::Message::Transport->new({
+        message_attribute_id   => $attribute->message_attribute_id,
+        message_transport_type => $mtt->message_transport_type,
+        is_digest              => 0,
+        letter_module          => $letter->module,
+        letter_code            => $letter->code,
+    })->store;
+
+    subtest 'Test for a patron' => sub {
+        plan tests => 3;
+
+        my $patron = build_a_test_patron();
+        Koha::Patron::Message::Preference->new({
+            borrowernumber       => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest         => 0,
+            days_in_advance      => undef,
+        })->store;
+
+        my $preference = Koha::Patron::Message::Preferences->find({
+            borrowernumber       => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id
+        });
+        ok($preference->borrower_message_preference_id > 0,
+           'Added a new messaging preference for patron.');
+
+        subtest 'Test set not throwing an exception on duplicate object' => sub {
+            plan tests => 1;
+
+            Koha::Patron::Message::Attributes->find({
+                message_attribute_id => $attribute->message_attribute_id
+            })->set({ takes_days => 1 })->store;
+            $preference->set({ days_in_advance => 1 })->store;
+            is(ref($preference), 'Koha::Patron::Message::Preference',
+             'Updating the preference does not cause duplicate object exception');
+        };
+
+        $preference->delete;
+        is(Koha::Patron::Message::Preferences->search({
+            borrowernumber       => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id
+        })->count, 0, 'Deleted the messaging preference.');
+    };
+
+    subtest 'Test for a category' => sub {
+        my $category = build_a_test_category();
+        Koha::Patron::Message::Preference->new({
+            categorycode         => $category->categorycode,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest         => 0,
+            days_in_advance      => undef,
+        })->store;
+
+        my $preference = Koha::Patron::Message::Preferences->find({
+            categorycode         => $category->categorycode,
+            message_attribute_id => $attribute->message_attribute_id
+        });
+        ok($preference->borrower_message_preference_id > 0,
+           'Added a new messaging preference for category.');
+
+        $preference->delete;
+        is(Koha::Patron::Message::Preferences->search({
+            categorycode         => $category->categorycode,
+            message_attribute_id => $attribute->message_attribute_id
+        })->count, 0, 'Deleted the messaging preference.');
+    };
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'Test Koha::Patron::Message::Preferences->get_options' => sub {
+    plan tests => 2;
+
+    subtest 'Test method availability and return value' => sub {
+        plan tests => 3;
+
+        ok(Koha::Patron::Message::Preferences->can('get_options'),
+            'Method get_options is available.');
+        ok(my $options = Koha::Patron::Message::Preferences->get_options,
+            'Called get_options successfully.');
+        is(ref($options), 'ARRAY', 'get_options returns a ARRAYref');
+    };
+
+    subtest 'Make sure options are correct' => sub {
+        $schema->storage->txn_begin;
+        my $options = Koha::Patron::Message::Preferences->get_options;
+
+        foreach my $option (@$options) {
+            my $n = $option->{'message_name'};
+            my $attr = Koha::Patron::Message::Attributes->find($option->{'message_attribute_id'});
+            is($option->{'message_attribute_id'}, $attr->message_attribute_id,
+               '$n: message_attribute_id is set');
+            is($option->{'message_name'}, $attr->message_name, '$n: message_name is set');
+            is($option->{'takes_days'}, $attr->takes_days, '$n: takes_days is set');
+            my $transports = Koha::Patron::Message::Transports->search({
+                message_attribute_id => $option->{'message_attribute_id'},
+                is_digest => $option->{'has_digest'} || 0,
+            });
+            while (my $trnzport = $transports->next) {
+                is($option->{'has_digest'} || 0, $trnzport->is_digest, '$n: has_digest is set for '.$trnzport->message_transport_type);
+                is($option->{'transport_'.$trnzport->message_transport_type}, ' ', '$n: transport_'.$trnzport->message_transport_type.' is set');
+            }
+        }
+
+        $schema->storage->txn_rollback;
+    };
+};
+
+subtest 'Add preferences from defaults' => sub {
+    plan tests => 3;
+
+    $schema->storage->txn_begin;
+
+    my $patron = build_a_test_patron();
+    my ($default, $mtt1, $mtt2) = build_a_test_category_preference({
+        patron => $patron,
+    });
+    ok(Koha::Patron::Message::Preference->new_from_default({
+        borrowernumber       => $patron->borrowernumber,
+        categorycode         => $patron->categorycode,
+        message_attribute_id => $default->message_attribute_id,
+    })->store, 'Added a default preference to patron.');
+    ok(my $pref = Koha::Patron::Message::Preferences->find({
+        borrowernumber       => $patron->borrowernumber,
+        message_attribute_id => $default->message_attribute_id,
+    }), 'Found the default preference from patron.');
+    is(Koha::Patron::Message::Transport::Preferences->search({
+        borrower_message_preference_id => $pref->borrower_message_preference_id
+    })->count, 2, 'Found the two transport types that we set earlier');
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'Test Koha::Patron::Message::Preference->message_transport_types' => sub {
+    plan tests => 4;
+
+    ok(Koha::Patron::Message::Preference->can('message_transport_types'),
+       'Method message_transport_types available');
+
+    subtest 'get message_transport_types' => sub {
+        plan tests => 5;
+
+        $schema->storage->txn_begin;
+
+        my $patron = build_a_test_patron();
+        my ($preference, $mtt1, $mtt2) = build_a_test_complete_preference({
+            patron => $patron
+        });
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        })->delete;
+        Koha::Patron::Message::Transport::Preference->new({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+            message_transport_type => $mtt1->message_transport_type,
+        })->store;
+        Koha::Patron::Message::Transport::Preference->new({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+            message_transport_type => $mtt2->message_transport_type,
+        })->store;
+        my $stored_transports = Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        });
+        my $transport1 = Koha::Patron::Message::Transports->find({
+            message_attribute_id => $preference->message_attribute_id,
+            message_transport_type => $mtt1->message_transport_type,
+        });
+        my $transport2 = Koha::Patron::Message::Transports->find({
+            message_attribute_id => $preference->message_attribute_id,
+            message_transport_type => $mtt2->message_transport_type,
+        });
+        my $transports = $preference->message_transport_types;
+        is(keys %{$transports}, $stored_transports->count,
+           '->message_transport_types gets correct amount of transport types.');
+        is($transports->{$stored_transports->next->message_transport_type},
+           $transport1->letter_code, 'Found correct message transport type and letter code.');
+        is($transports->{$stored_transports->next->message_transport_type},
+           $transport2->letter_code, 'Found correct message transport type and letter code.');
+        ok(!$preference->message_transport_types->{'nonexistent'},
+           'Didn\'t find nonexistent transport type.');
+
+        subtest 'test logging of warnings by invalid message transport type' => sub {
+            plan tests => 2;
+
+            my $log = mytempfile();
+            my $conf = mytempfile( <<"HERE"
+log4perl.logger.opac = WARN, OPAC
+log4perl.appender.OPAC=Log::Log4perl::Appender::TestBuffer
+log4perl.appender.OPAC.filename=$log
+log4perl.appender.OPAC.mode=append
+log4perl.appender.OPAC.layout=SimpleLayout
+log4perl.logger.intranet = WARN, INTRANET
+log4perl.appender.INTRANET=Log::Log4perl::Appender::TestBuffer
+log4perl.appender.INTRANET.filename=$log
+log4perl.appender.INTRANET.mode=append
+log4perl.appender.INTRANET.layout=SimpleLayout
+HERE
+            );
+            t::lib::Mocks::mock_config('log4perl_conf', $conf);
+            my $appenders = Log::Log4perl->appenders;
+            my $appender = Log::Log4perl->appenders->{OPAC};
+
+            my $pref = Koha::Patron::Message::Preferences->find(
+                $preference->borrower_message_preference_id
+            );
+            my $transports = $pref->message_transport_types;
+            is($appender, undef, 'Nothing in buffer yet');
+
+            my $mtt_new = build_a_test_transport_type();
+            Koha::Patron::Message::Transport::Preference->new({
+                borrower_message_preference_id =>
+                                $pref->borrower_message_preference_id,
+                message_transport_type => $mtt_new->message_transport_type,
+            })->store;
+            $pref = Koha::Patron::Message::Preferences->find(
+                $pref->borrower_message_preference_id
+            );
+            $transports = $pref->message_transport_types;
+            $appender = Log::Log4perl->appenders->{OPAC};
+            my $name = $pref->message_name;
+            my $tt = $mtt_new->message_transport_type;
+            like($appender->buffer, qr/WARN - $name has no transport with $tt/,
+                 'Logged invalid message transport type');
+        };
+
+        $schema->storage->txn_rollback;
+    };
+
+    subtest 'set message_transport_types' => sub {
+        plan tests => 6;
+
+        $schema->storage->txn_begin;
+
+        my $patron = build_a_test_patron();
+        my ($preference, $mtt1, $mtt2) = build_a_test_complete_preference({
+            patron => $patron
+        });
+
+        my $mtt1_str = $mtt1->message_transport_type;
+        my $mtt2_str = $mtt2->message_transport_type;
+        # 1/3, use message_transport_types(list)
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        })->delete;
+        ok($preference->message_transport_types($mtt1_str, $mtt2_str)->store,
+           '1/3 Set returned true.');
+        my $stored_transports = Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+            '-or' => [
+                message_transport_type => $mtt1_str,
+                message_transport_type => $mtt2_str
+            ]
+        });
+        is($stored_transports->count, 2, 'Two transports selected');
+
+        # 2/3, use message_transport_types(ARRAYREF)
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        })->delete;
+        ok($preference->message_transport_types([$mtt1_str, $mtt2_str])->store,
+           '2/3 Set returned true.');
+        $stored_transports = Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+            '-or' => [
+                message_transport_type => $mtt1_str,
+                message_transport_type => $mtt2_str
+            ]
+        });
+        is($stored_transports->count, 2, 'Two transports selected');
+
+        # 3/3, use set({ message_transport_types => ARRAYREF })
+        Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        })->delete;
+        ok($preference->set({
+            message_transport_types => [$mtt1_str, $mtt2_str]})->store,
+           '3/3 Set returned true.');
+        $stored_transports = Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+            '-or' => [
+                message_transport_type => $mtt1_str,
+                message_transport_type => $mtt2_str
+            ]
+        });
+        is($stored_transports->count, 2, 'Two transports selected');
+
+        $schema->storage->txn_rollback;
+    };
+
+    subtest 'new message_transport_types' => sub {
+        plan tests => 3;
+
+        $schema->storage->txn_begin;
+
+        my $patron    = build_a_test_patron();
+        my $letter    = build_a_test_letter();
+        my $attribute = build_a_test_attribute();
+        my $mtt       = build_a_test_transport_type();
+        Koha::Patron::Message::Transport->new({
+            message_attribute_id   => $attribute->message_attribute_id,
+            message_transport_type => $mtt->message_transport_type,
+            is_digest              => 0,
+            letter_module          => $letter->module,
+            letter_code            => $letter->code,
+        })->store;
+        ok(my $preference = Koha::Patron::Message::Preference->new({
+            borrowernumber => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest => 0,
+            days_in_advance => undef,
+            message_transport_types => $mtt->message_transport_type,
+        })->store, 'Added a new messaging preference and transport types to patron.');
+        ok($preference->message_transport_types->{$mtt->message_transport_type},
+           'The transport type is stored in the object.');
+        my $stored_transports = Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $preference->borrower_message_preference_id,
+        });
+        is($stored_transports->next->message_transport_type, $mtt->message_transport_type,
+           'The transport type is stored in the database.');
+
+        $schema->storage->txn_rollback;
+    };
+};
+
+subtest 'Test Koha::Patron::Message::Preference->message_name' => sub {
+    plan tests => 1;
+
+    $schema->storage->txn_begin;
+
+    my $patron      = build_a_test_patron();
+    my $attribute   = build_a_test_attribute();
+    my ($preference, $mtt1, $mtt2) = build_a_test_complete_preference({
+        patron => $patron,
+        attr   => $attribute,
+    });
+    my $message_name_pref = Koha::Patron::Message::Preferences->search_with_message_name({
+        borrowernumber => $patron->{'borrowernumber'},
+        message_name => $attribute->message_name,
+    })->next;
+    is($message_name_pref->message_name, $attribute->message_name, "Found preference with message_name");
+
+    $schema->storage->txn_rollback;
+};
+
+subtest 'Test adding a new preference with invalid parameters' => sub {
+    plan tests => 4;
+
+    subtest 'Missing parameters' => sub {
+        plan tests => 1;
+
+        eval { Koha::Patron::Message::Preference->new->store };
+        is(ref $@, 'Koha::Exceptions::MissingParameter',
+            'Adding a message preference without parameters'
+            .' => Koha::Exceptions::MissingParameter');
+    };
+
+    subtest 'Too many parameters' => sub {
+        plan tests => 1;
+
+        $schema->storage->txn_begin;
+
+        my $patron = build_a_test_patron();
+        eval { Koha::Patron::Message::Preference->new({
+            borrowernumber => $patron->borrowernumber,
+            categorycode   => $patron->categorycode,
+        })->store };
+        is(ref $@, 'Koha::Exceptions::TooManyParameters',
+            'Adding a message preference for both borrowernumber and categorycode'
+            .' => Koha::Exceptions::TooManyParameters');
+
+        $schema->storage->txn_rollback;
+    };
+
+    subtest 'Bad parameter' => sub {
+        plan tests => 19;
+
+        $schema->storage->txn_begin;
+
+        eval { Koha::Patron::Message::Preference->new({
+                borrowernumber => -999,
+            })->store };
+        is(ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid borrowernumber'
+            .' => Koha::Exceptions::BadParameter');
+        is ($@->parameter, 'borrowernumber', 'The previous exception tells us it'
+            .' was the borrowernumber.');
+
+        eval { Koha::Patron::Message::Preference->new({
+                categorycode => 'nonexistent',
+            })->store };
+        is(ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid categorycode'
+            .' => Koha::Exceptions::BadParameter');
+        is($@->parameter, 'categorycode', 'The previous exception tells us it'
+            .' was the categorycode.');
+
+        my $attribute = build_a_test_attribute({ takes_days => 0 });
+        my $patron    = build_a_test_patron();
+        eval { Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_attribute_id => $attribute->message_attribute_id,
+                days_in_advance => 10,
+            })->store };
+        is(ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with days in advance option when not'
+            .' available => Koha::Exceptions::BadParameter');
+        is($@->parameter, 'days_in_advance', 'The previous exception tells us it'
+            .' was the days_in_advance.');
+
+        $attribute->set({ takes_days => 1 })->store;
+        eval { Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_attribute_id => $attribute->message_attribute_id,
+                days_in_advance => 31,
+            })->store };
+        is(ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with days in advance option too large'
+            .' => Koha::Exceptions::BadParameter');
+        is($@->parameter, 'days_in_advance', 'The previous exception tells us it'
+            .' was the days_in_advance.');
+
+        eval { Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_transport_types => ['nonexistent']
+            })->store };
+        is (ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid message_transport_type'
+            .' => Koha::Exceptions::BadParameter');
+        is ($@->parameter, 'message_transport_types', 'The previous exception '
+            .'tells us it was the message_transport_types.');
+
+        my $mtt_new = build_a_test_transport_type();
+        eval {
+            Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_attribute_id => $attribute->message_attribute_id,
+                message_transport_types => [$mtt_new->message_transport_type],
+                wants_digest => 1,
+            })->store };
+        is (ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid message_transport_type'
+           .' => Koha::Exceptions::BadParameter');
+        is ($@->parameter, 'message_transport_types', 'The previous exception '
+            .'tells us it was the message_transport_types.');
+        like ($@->error, qr/^No transport configured/, 'Exception is because of '
+            .'given message_transport_type is not a valid option.');
+
+        eval {
+            Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_attribute_id => $attribute->message_attribute_id,
+                message_transport_types => [],
+                wants_digest => 1,
+            })->store };
+        is (ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid message_transport_type'
+            .' => Koha::Exceptions::BadParameter');
+        is ($@->parameter, 'wants_digest', 'The previous exception tells us it'
+            .' was the wants_digest');
+        like ($@->error, qr/^Digest not available/, 'Exception s because of given'
+            .' digest is not available for this transport.');
+
+        eval {
+            Koha::Patron::Message::Preference->new({
+                borrowernumber => $patron->borrowernumber,
+                message_attribute_id => -1,
+                message_transport_types => [],
+            })->store };
+        is (ref $@, 'Koha::Exceptions::BadParameter',
+            'Adding a message preference with invalid message_transport_type'
+            .' => Koha::Exceptions::BadParameter');
+        is ($@->parameter, 'message_attribute_id', 'The previous exception tells'
+            .' us it was the message_attribute_id');
+        like ($@->error, qr/^Message attribute with id -1 not found/, 'Exception '
+            .' is because of given message attribute id is not found.');
+
+        $schema->storage->txn_rollback;
+    };
+
+    subtest 'Duplicate object' => sub {
+        plan tests => 2;
+
+        $schema->storage->txn_begin;
+
+        my $attribute = build_a_test_attribute();
+        my $letter = build_a_test_letter();
+        my $mtt = build_a_test_transport_type();
+        Koha::Patron::Message::Transport->new({
+            message_attribute_id   => $attribute->message_attribute_id,
+            message_transport_type => $mtt->message_transport_type,
+            is_digest              => 0,
+            letter_module          => $letter->module,
+            letter_code            => $letter->code,
+        })->store;
+        my $patron    = build_a_test_patron();
+        my $preference = Koha::Patron::Message::Preference->new({
+            borrowernumber => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest => 0,
+            days_in_advance => undef,
+        })->store;
+        ok($preference->borrower_message_preference_id,
+           'Added a new messaging preference for patron.');
+        eval { Koha::Patron::Message::Preference->new({
+            borrowernumber => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest => 0,
+            days_in_advance => undef,
+        })->store };
+        is(ref $@, 'Koha::Exceptions::DuplicateObject',
+                'Adding a duplicate preference'
+                .' => Koha::Exceptions::DuplicateObject');
+
+        $schema->storage->txn_rollback;
+    };
+};
+
+sub build_a_test_attribute {
+    my ($params) = @_;
+
+    $params->{takes_days} = $params->{takes_days} && $params->{takes_days} > 0
+                            ? 1 : 0;
+
+    my $attribute = $builder->build({
+        source => 'MessageAttribute',
+        value => $params,
+    });
+
+    return Koha::Patron::Message::Attributes->find(
+        $attribute->{message_attribute_id}
+    );
+}
+
+sub build_a_test_category {
+    my $categorycode   = $builder->build({
+        source => 'Category' })->{categorycode};
+
+    return Koha::Patron::Categories->find($categorycode);
+}
+
+sub build_a_test_letter {
+    my ($params) = @_;
+
+    my $mtt = $params->{mtt} ? $params->{mtt} : 'email';
+    my $branchcode     = $builder->build({
+        source => 'Branch' })->{branchcode};
+    my $letter = $builder->build({
+        source => 'Letter',
+        value => {
+            branchcode => '',
+            is_html => 0,
+            message_transport_type => $mtt
+        }
+    });
+
+    return Koha::Notice::Templates->find({
+        module     => $letter->{module},
+        code       => $letter->{code},
+        branchcode => $letter->{branchcode},
+    });
+}
+
+sub build_a_test_patron {
+    my $categorycode   = $builder->build({
+        source => 'Category' })->{categorycode};
+    my $branchcode     = $builder->build({
+        source => 'Branch' })->{branchcode};
+    my $borrowernumber = $builder->build({
+        source => 'Borrower' })->{borrowernumber};
+
+    return Koha::Patrons->find($borrowernumber);
+}
+
+sub build_a_test_transport_type {
+    my $mtt = $builder->build({
+        source => 'MessageTransportType' });
+
+    return Koha::Patron::Message::Transport::Types->find(
+        $mtt->{message_transport_type}
+    );
+}
+
+sub build_a_test_category_preference {
+    my ($params) = @_;
+
+    my $patron = $params->{patron};
+    my $attr = $params->{attr}
+                    ? $params->{attr}
+                    : build_a_test_attribute($params->{days_in_advance});
+
+    my $letter = $params->{letter} ? $params->{letter} : build_a_test_letter();
+    my $mtt1 = $params->{mtt1} ? $params->{mtt1} : build_a_test_transport_type();
+    my $mtt2 = $params->{mtt2} ? $params->{mtt2} : build_a_test_transport_type();
+
+    Koha::Patron::Message::Transport->new({
+        message_attribute_id   => $attr->message_attribute_id,
+        message_transport_type => $mtt1->message_transport_type,
+        is_digest              => $params->{digest} ? 1 : 0,
+        letter_module          => $letter->module,
+        letter_code            => $letter->code,
+    })->store;
+
+    Koha::Patron::Message::Transport->new({
+        message_attribute_id   => $attr->message_attribute_id,
+        message_transport_type => $mtt2->message_transport_type,
+        is_digest              => $params->{digest} ? 1 : 0,
+        letter_module          => $letter->module,
+        letter_code            => $letter->code,
+    })->store;
+
+    my $default = Koha::Patron::Message::Preference->new({
+        categorycode         => $patron->categorycode,
+        message_attribute_id => $attr->message_attribute_id,
+        wants_digest         => $params->{digest} ? 1 : 0,
+        days_in_advance      => $params->{days_in_advance}
+                                 ? $params->{days_in_advance} : undef,
+    })->store;
+
+    Koha::Patron::Message::Transport::Preference->new({
+        borrower_message_preference_id => $default->borrower_message_preference_id,
+        message_transport_type         => $mtt1->message_transport_type,
+    })->store;
+    Koha::Patron::Message::Transport::Preference->new({
+        borrower_message_preference_id => $default->borrower_message_preference_id,
+        message_transport_type         => $mtt2->message_transport_type,
+    })->store;
+
+    return ($default, $mtt1, $mtt2);
+}
+
+sub build_a_test_complete_preference {
+    my ($params) = @_;
+
+    my ($default, $mtt1, $mtt2) = build_a_test_category_preference($params);
+    my $patron = $params->{patron};
+    $patron->set_default_messaging_preferences;
+    return (Koha::Patron::Message::Preferences->search({
+        borrowernumber => $patron->borrowernumber
+    })->next, $mtt1, $mtt2);
+}
+
+sub mytempfile {
+    my ( $fh, $fn ) = tempfile( SUFFIX => '.logger.test', UNLINK => 1 );
+    print $fh $_[0]//'';
+    close $fh;
+    return $fn;
+}
+
+1;
diff --git a/t/db_dependent/Koha/Patron/Message/Transport/Preferences.t b/t/db_dependent/Koha/Patron/Message/Transport/Preferences.t
new file mode 100644
index 0000000..0cdf809
--- /dev/null
+++ b/t/db_dependent/Koha/Patron/Message/Transport/Preferences.t
@@ -0,0 +1,179 @@
+#!/usr/bin/perl
+
+# Copyright 2017 Koha-Suomi Oy
+#
+# 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 Test::More tests => 2;
+
+use t::lib::Mocks;
+use t::lib::TestBuilder;
+
+use Koha::Notice::Templates;
+use Koha::Patron::Categories;
+use Koha::Patron::Message::Attributes;
+use Koha::Patron::Message::Preferences;
+use Koha::Patron::Message::Transport::Types;
+use Koha::Patron::Message::Transports;
+use Koha::Patrons;
+
+my $schema  = Koha::Database->new->schema;
+my $builder = t::lib::TestBuilder->new;
+
+subtest 'Test class imports' => sub {
+    plan tests => 2;
+
+    use_ok('Koha::Patron::Message::Transport::Preference');
+    use_ok('Koha::Patron::Message::Transport::Preferences');
+};
+
+subtest 'Test Koha::Patron::Message::Transport::Preferences' => sub {
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $attribute = build_a_test_attribute();
+    my $mtt       = build_a_test_transport_type();
+    my $letter    = build_a_test_letter({
+        mtt => $mtt->message_transport_type
+    });
+    Koha::Patron::Message::Transport->new({
+        message_attribute_id   => $attribute->message_attribute_id,
+        message_transport_type => $mtt->message_transport_type,
+        is_digest              => 0,
+        letter_module          => $letter->module,
+        letter_code            => $letter->code,
+    })->store;
+
+    subtest 'For a patron' => sub {
+        my $patron    = build_a_test_patron();
+        my $preference = Koha::Patron::Message::Preference->new({
+            borrowernumber       => $patron->borrowernumber,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest         => 0,
+            days_in_advance      => undef,
+        })->store;
+
+        my $pref_id = $preference->borrower_message_preference_id;
+        my $transport_pref = Koha::Patron::Message::Transport::Preference->new({
+            borrower_message_preference_id => $pref_id,
+            message_transport_type => $mtt->message_transport_type,
+        })->store;
+        is(ref($transport_pref), 'Koha::Patron::Message::Transport::Preference',
+           'Added a new messaging transport preference for patron.');
+
+        $transport_pref->delete;
+        is(Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $pref_id,
+            message_transport_type => $mtt->message_transport_type,
+        })->count, 0, 'Deleted the messaging transport preference.');
+    };
+
+    subtest 'For a category' => sub {
+        my $category   = build_a_test_category();
+        my $preference = Koha::Patron::Message::Preference->new({
+            categorycode         => $category->categorycode,
+            message_attribute_id => $attribute->message_attribute_id,
+            wants_digest         => 0,
+            days_in_advance      => undef,
+        })->store;
+
+        my $pref_id = $preference->borrower_message_preference_id;
+        my $transport_pref = Koha::Patron::Message::Transport::Preference->new({
+            borrower_message_preference_id => $pref_id,
+            message_transport_type => $mtt->message_transport_type,
+        })->store;
+        is(ref($transport_pref), 'Koha::Patron::Message::Transport::Preference',
+           'Added a new messaging transport preference for category.');
+
+        $transport_pref->delete;
+        is(Koha::Patron::Message::Transport::Preferences->search({
+            borrower_message_preference_id => $pref_id,
+            message_transport_type => $mtt->message_transport_type,
+        })->count, 0, 'Deleted the messaging transport preference.');
+    };
+
+    $schema->storage->txn_rollback;
+};
+
+sub build_a_test_attribute {
+    my ($params) = @_;
+
+    $params->{takes_days} = $params->{takes_days} && $params->{takes_days} > 0
+                            ? 1 : 0;
+
+    my $attribute = $builder->build({
+        source => 'MessageAttribute',
+        value => $params,
+    });
+
+    return Koha::Patron::Message::Attributes->find(
+        $attribute->{message_attribute_id}
+    );
+}
+
+sub build_a_test_category {
+    my $categorycode   = $builder->build({
+        source => 'Category' })->{categorycode};
+
+    return Koha::Patron::Categories->find($categorycode);
+}
+
+sub build_a_test_letter {
+    my ($params) = @_;
+
+    my $mtt = $params->{mtt} ? $params->{mtt} : 'email';
+    my $branchcode     = $builder->build({
+        source => 'Branch' })->{branchcode};
+    my $letter = $builder->build({
+        source => 'Letter',
+        value => {
+            branchcode => '',
+            is_html => 0,
+            message_transport_type => $mtt
+        }
+    });
+
+    return Koha::Notice::Templates->find({
+        module => $letter->{module},
+        code   => $letter->{code},
+        branchcode => $letter->{branchcode},
+    });
+}
+
+sub build_a_test_patron {
+    my $categorycode   = $builder->build({
+        source => 'Category' })->{categorycode};
+    my $branchcode     = $builder->build({
+        source => 'Branch' })->{branchcode};
+    my $borrowernumber = $builder->build({
+        source => 'Borrower' })->{borrowernumber};
+
+    return Koha::Patrons->find($borrowernumber);
+}
+
+sub build_a_test_transport_type {
+    my $mtt = $builder->build({
+        source => 'MessageTransportType' });
+
+    return Koha::Patron::Message::Transport::Types->find(
+        $mtt->{message_transport_type}
+    );
+}
+
+1;
diff --git a/t/db_dependent/Koha/Patron/Message/Transport/Types.t b/t/db_dependent/Koha/Patron/Message/Transport/Types.t
new file mode 100644
index 0000000..cbba32b
--- /dev/null
+++ b/t/db_dependent/Koha/Patron/Message/Transport/Types.t
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+# Copyright 2017 Koha-Suomi Oy
+#
+# 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 Test::More tests => 2;
+
+use Koha::Database;
+
+my $schema  = Koha::Database->new->schema;
+
+subtest 'Test class imports' => sub {
+    plan tests => 2;
+
+    use_ok('Koha::Patron::Message::Transport::Type');
+    use_ok('Koha::Patron::Message::Transport::Types');
+};
+
+subtest 'Test Koha::Patron::Message::Transport::Types' => sub {
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $transport_type = Koha::Patron::Message::Transport::Type->new({
+        message_transport_type => 'test'
+    })->store;
+
+    is($transport_type->message_transport_type, 'test',
+       'Added a new message transport type.');
+
+    $transport_type->delete;
+    is(Koha::Patron::Message::Transport::Types->find('test'), undef,
+       'Deleted the message transport type.');
+
+    $schema->storage->txn_rollback;
+};
+
+1;
diff --git a/t/db_dependent/Koha/Patron/Message/Transports.t b/t/db_dependent/Koha/Patron/Message/Transports.t
new file mode 100644
index 0000000..b9296fd
--- /dev/null
+++ b/t/db_dependent/Koha/Patron/Message/Transports.t
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+
+# Copyright 2017 Koha-Suomi Oy
+#
+# 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 Test::More tests => 2;
+
+use t::lib::TestBuilder;
+
+use Koha::Notice::Templates;
+use Koha::Patron::Message::Attributes;
+use Koha::Patron::Message::Transport::Types;
+
+my $schema  = Koha::Database->new->schema;
+my $builder = t::lib::TestBuilder->new;
+
+subtest 'Test class imports' => sub {
+    plan tests => 2;
+
+    use_ok('Koha::Patron::Message::Transport');
+    use_ok('Koha::Patron::Message::Transports');
+};
+
+subtest 'Test Koha::Patron::Message::Transports' => sub {
+    plan tests => 2;
+
+    $schema->storage->txn_begin;
+
+    my $attribute = build_a_test_attribute();
+    my $mtt       = build_a_test_transport_type();
+    my $letter    = build_a_test_letter({
+        mtt => $mtt->message_transport_type
+    });
+
+    my $transport = Koha::Patron::Message::Transport->new({
+        message_attribute_id   => $attribute->message_attribute_id,
+        message_transport_type => $mtt->message_transport_type,
+        is_digest              => 0,
+        letter_module          => $letter->module,
+        letter_code            => $letter->code,
+    })->store;
+
+    is($transport->message_attribute_id, $attribute->message_attribute_id,
+       'Added a new messaging transport.');
+
+    $transport->delete;
+    is(Koha::Patron::Message::Transports->search({
+        message_attribute_id => $attribute->message_attribute_id,
+        message_transport_type => $mtt->message_transport_type,
+        is_digest => 0
+    })->count, 0, 'Deleted the messaging transport.');
+
+    $schema->storage->txn_rollback;
+};
+
+sub build_a_test_attribute {
+    my ($params) = @_;
+
+    $params->{takes_days} = $params->{takes_days} && $params->{takes_days} > 0
+                            ? 1 : 0;
+
+    my $attribute = $builder->build({
+        source => 'MessageAttribute',
+        value => $params,
+    });
+
+    return Koha::Patron::Message::Attributes->find(
+        $attribute->{message_attribute_id}
+    );
+}
+
+sub build_a_test_letter {
+    my ($params) = @_;
+
+    my $mtt = $params->{mtt} ? $params->{mtt} : 'email';
+    my $branchcode     = $builder->build({
+        source => 'Branch' })->{branchcode};
+    my $letter = $builder->build({
+        source => 'Letter',
+        value => {
+            branchcode => '',
+            is_html => 0,
+            message_transport_type => $mtt
+        }
+    });
+
+    return Koha::Notice::Templates->find({
+        module => $letter->{module},
+        code   => $letter->{code},
+        branchcode => $letter->{branchcode},
+    });
+}
+
+sub build_a_test_transport_type {
+    my $mtt = $builder->build({
+        source => 'MessageTransportType' });
+
+    return Koha::Patron::Message::Transport::Types->find(
+        $mtt->{message_transport_type}
+    );
+}
+
+1;
-- 
2.1.4