#
# 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 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.
+# 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.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
use strict;
#use warnings; FIXME - Bug 2505
use C4::Context;
use C4::Dates qw(format_date_in_iso format_date);
-use Digest::MD5 qw(md5_base64);
use String::Random qw( random_string );
use Date::Calc qw/Today Add_Delta_YM check_date Date_to_Days/;
use C4::Log; # logaction
use C4::Accounts;
use C4::Biblio;
use C4::Letters;
-use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
-use C4::Members::Attributes qw(SearchIdMatchingAttribute);
+use C4::Members::Attributes qw(SearchIdMatchingAttribute UpdateBorrowerAttribute);
use C4::NewsChannels; #get slip news
use DateTime;
-use DateTime::Format::DateParse;
+use Koha::Database;
use Koha::DateUtils;
+use Koha::Borrower::Debarments qw(IsDebarred);
use Text::Unaccent qw( unac_string );
+use Koha::AuthUtils qw(hash_password);
+use Koha::Database;
+require Koha::NorwegianPatronDB;
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
&GetPendingIssues
&GetAllIssues
- &get_institutions
&getzipnamecity
&getidcity
&GetFirstValidEmailAddress
+ &GetNoticeEmailAddress
&GetAge
&GetCities
- &GetRoadTypes
- &GetRoadTypeDetails
&GetSortDetails
&GetTitles
GetBorrowerCategorycode
&GetBorrowercategoryList
- &GetBorrowersWhoHaveNotBorrowedSince
+ &GetBorrowersToExpunge
&GetBorrowersWhoHaveNeverBorrowed
&GetBorrowersWithIssuesHistoryOlderThan
&GetExpiryDate
+ &GetUpcomingMembershipExpires
&AddMessage
&DeleteMessage
&IssueSlip
GetBorrowersWithEmail
+
+ HasOverdues
+ GetOverduesForPatron
);
#Modify data
push @EXPORT, qw(
&AddMember
&AddMember_Opac
- &add_member_orgs
&MoveMemberToDeleted
&ExtendMemberSubscriptionTo
);
&checkuserpassword
&Check_Userid
&Generate_Userid
- &fixEthnicity
- ðnicitycategories
&fixup_cardnumber
&checkcardnumber
);
=head1 FUNCTIONS
-=head2 Search
-
- $borrowers_result_array_ref = &Search($filter,$orderby, $limit,
- $columns_out, $search_on_fields,$searchtype);
-
-Looks up patrons (borrowers) on filter. A wrapper for SearchInTable('borrowers').
-
-For C<$filter>, C<$orderby>, C<$limit>, C<&columns_out>, C<&search_on_fields> and C<&searchtype>
-refer to C4::SQLHelper:SearchInTable().
-
-Special C<$filter> key '' is effectively expanded to search on surname firstname othernamescw
-and cardnumber unless C<&search_on_fields> is defined
-
-Examples:
-
- $borrowers = Search('abcd', 'cardnumber');
-
- $borrowers = Search({''=>'abcd', category_type=>'I'}, 'surname');
-
-=cut
-
-sub _express_member_find {
- my ($filter) = @_;
-
- # this is used by circulation everytime a new borrowers cardnumber is scanned
- # so we can check an exact match first, if that works return, otherwise do the rest
- my $dbh = C4::Context->dbh;
- my $query = "SELECT borrowernumber FROM borrowers WHERE cardnumber = ?";
- if ( my $borrowernumber = $dbh->selectrow_array($query, undef, $filter) ) {
- return( {"borrowernumber"=>$borrowernumber} );
- }
-
- my ($search_on_fields, $searchtype);
- if ( length($filter) == 1 ) {
- $search_on_fields = [ qw(surname) ];
- $searchtype = 'start_with';
- } else {
- $search_on_fields = [ qw(surname firstname othernames cardnumber) ];
- $searchtype = 'contain';
- }
-
- return (undef, $search_on_fields, $searchtype);
-}
-
-sub Search {
- my ( $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype ) = @_;
-
- my $search_string;
- my $found_borrower;
-
- if ( my $fr = ref $filter ) {
- if ( $fr eq "HASH" ) {
- if ( my $search_string = $filter->{''} ) {
- my ($member_filter, $member_search_on_fields, $member_searchtype) = _express_member_find($search_string);
- if ($member_filter) {
- $filter = $member_filter;
- $found_borrower = 1;
- } else {
- $search_on_fields ||= $member_search_on_fields;
- $searchtype ||= $member_searchtype;
- }
- }
- }
- else {
- $search_string = $filter;
- }
- }
- else {
- $search_string = $filter;
- my ($member_filter, $member_search_on_fields, $member_searchtype) = _express_member_find($search_string);
- if ($member_filter) {
- $filter = $member_filter;
- $found_borrower = 1;
- } else {
- $search_on_fields ||= $member_search_on_fields;
- $searchtype ||= $member_searchtype;
- }
- }
-
- if ( !$found_borrower && C4::Context->preference('ExtendedPatronAttributes') && $search_string ) {
- my $matching_records = C4::Members::Attributes::SearchIdMatchingAttribute($search_string);
- if(scalar(@$matching_records)>0) {
- if ( my $fr = ref $filter ) {
- if ( $fr eq "HASH" ) {
- my %f = %$filter;
- $filter = [ $filter ];
- delete $f{''};
- push @$filter, { %f, "borrowernumber"=>$$matching_records };
- }
- else {
- push @$filter, {"borrowernumber"=>$matching_records};
- }
- }
- else {
- $filter = [ $filter ];
- push @$filter, {"borrowernumber"=>$matching_records};
- }
- }
- }
-
- # $showallbranches was not used at the time SearchMember() was mainstreamed into Search().
- # Mentioning for the reference
-
- if ( C4::Context->preference("IndependantBranches") ) { # && !$showallbranches){
- if ( my $userenv = C4::Context->userenv ) {
- my $branch = $userenv->{'branch'};
- if ( ($userenv->{flags} % 2 !=1) &&
- $branch && $branch ne "insecure" ){
-
- if (my $fr = ref $filter) {
- if ( $fr eq "HASH" ) {
- $filter->{branchcode} = $branch;
- }
- else {
- foreach (@$filter) {
- $_ = { '' => $_ } unless ref $_;
- $_->{branchcode} = $branch;
- }
- }
- }
- else {
- $filter = { '' => $filter, branchcode => $branch };
- }
- }
- }
- }
-
- if ($found_borrower) {
- $searchtype = "exact";
- }
- $searchtype ||= "start_with";
-
- return SearchInTable( "borrowers", $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype );
-}
-
=head2 GetMemberDetails
($borrower) = &GetMemberDetails($borrowernumber, $cardnumber);
my $query;
my $sth;
if ($borrowernumber) {
- $sth = $dbh->prepare("SELECT borrowers.*,category_type,categories.description,reservefee,enrolmentperiod FROM borrowers LEFT JOIN categories ON borrowers.categorycode=categories.categorycode WHERE borrowernumber=?");
+ $sth = $dbh->prepare("
+ SELECT borrowers.*,
+ category_type,
+ categories.description,
+ categories.BlockExpiredPatronOpacActions,
+ reservefee,
+ enrolmentperiod
+ FROM borrowers
+ LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
+ WHERE borrowernumber = ?
+ ");
$sth->execute($borrowernumber);
}
elsif ($cardnumber) {
- $sth = $dbh->prepare("SELECT borrowers.*,category_type,categories.description,reservefee,enrolmentperiod FROM borrowers LEFT JOIN categories ON borrowers.categorycode=categories.categorycode WHERE cardnumber=?");
+ $sth = $dbh->prepare("
+ SELECT borrowers.*,
+ category_type,
+ categories.description,
+ categories.BlockExpiredPatronOpacActions,
+ reservefee,
+ enrolmentperiod
+ FROM borrowers
+ LEFT JOIN categories ON borrowers.categorycode = categories.categorycode
+ WHERE cardnumber = ?
+ ");
$sth->execute($cardnumber);
}
else {
return;
}
my $borrower = $sth->fetchrow_hashref;
- my ($amount) = GetMemberAccountRecords( $borrowernumber);
+ return unless $borrower;
+ my ($amount) = GetMemberAccountRecords($borrower->{borrowernumber});
$borrower->{'amountoutstanding'} = $amount;
# FIXME - patronflags calls GetMemberAccountRecords... just have patronflags return $amount
my $flags = patronflags( $borrower);
$borrower->{'showname'} = $borrower->{'firstname'};
}
+ # Handle setting the true behavior for BlockExpiredPatronOpacActions
+ $borrower->{'BlockExpiredPatronOpacActions'} =
+ C4::Context->preference('BlockExpiredPatronOpacActions')
+ if ( $borrower->{'BlockExpiredPatronOpacActions'} == -1 );
+
+ $borrower->{'is_expired'} = 0;
+ $borrower->{'is_expired'} = 1 if
+ defined($borrower->{dateexpiry}) &&
+ $borrower->{'dateexpiry'} ne '0000-00-00' &&
+ Date_to_Days( Today() ) >
+ Date_to_Days( split /-/, $borrower->{'dateexpiry'} );
+
return ($borrower); #, $flags, $accessflagshash);
}
my %flags;
my ( $patroninformation) = @_;
my $dbh=C4::Context->dbh;
- my ($amount) = GetMemberAccountRecords( $patroninformation->{'borrowernumber'});
- if ( $amount > 0 ) {
+ my ($balance, $owing) = GetMemberAccountBalance( $patroninformation->{'borrowernumber'});
+ if ( $owing > 0 ) {
my %flaginfo;
my $noissuescharge = C4::Context->preference("noissuescharge") || 5;
- $flaginfo{'message'} = sprintf "Patron owes \$%.02f", $amount;
- $flaginfo{'amount'} = sprintf "%.02f", $amount;
- if ( $amount > $noissuescharge && !C4::Context->preference("AllowFineOverride") ) {
+ $flaginfo{'message'} = sprintf 'Patron owes %.02f', $owing;
+ $flaginfo{'amount'} = sprintf "%.02f", $owing;
+ if ( $owing > $noissuescharge && !C4::Context->preference("AllowFineOverride") ) {
$flaginfo{'noissues'} = 1;
}
$flags{'CHARGES'} = \%flaginfo;
}
- elsif ( $amount < 0 ) {
+ elsif ( $balance < 0 ) {
my %flaginfo;
- $flaginfo{'message'} = sprintf "Patron has credit of \$%.02f", -$amount;
- $flaginfo{'amount'} = sprintf "%.02f", $amount;
+ $flaginfo{'message'} = sprintf 'Patron has credit of %.02f', -$balance;
+ $flaginfo{'amount'} = sprintf "%.02f", $balance;
$flags{'CREDITS'} = \%flaginfo;
}
if ( $patroninformation->{'gonenoaddress'}
C<GetMemberRelatives> returns a borrowersnumber's list of guarantor/guarantees of the member given in parameter
-=cut
+=cut
+
sub GetMemberRelatives {
my $borrowernumber = shift;
my $dbh = C4::Context->dbh;
my ($block_status, $count) = IsMemberBlocked( $borrowernumber );
-Returns whether a patron has overdue items that may result
-in a block or whether the patron has active fine days
-that would block circulation privileges.
+Returns whether a patron is restricted or has overdue items that may result
+in a block of circulation privileges.
C<$block_status> can have the following values:
-1 if the patron has outstanding fine days, in which case C<$count> is the number of them
+1 if the patron is currently restricted, in which case
+C<$count> is the expiration date (9999-12-31 for indefinite)
-1 if the patron has overdue items, in which case C<$count> is the number of them
0 if the patron has no overdue items or outstanding fine days, in which case C<$count> is 0
-Outstanding fine days are checked before current overdue items
-are.
-
-FIXME: this needs to be split into two functions; a potential block
-based on the number of current overdue items could be orthogonal
-to a block based on whether the patron has any fine days accrued.
+Existing active restrictions are checked before current overdue items.
=cut
my $borrowernumber = shift;
my $dbh = C4::Context->dbh;
- my $blockeddate = CheckBorrowerDebarred($borrowernumber);
+ my $blockeddate = Koha::Borrower::Debarments::IsDebarred($borrowernumber);
return ( 1, $blockeddate ) if $blockeddate;
return ($overdue_count, $issue_count, $total_fines);
}
-sub columns(;$) {
- return @{C4::Context->dbh->selectcol_arrayref("SHOW columns from borrowers")};
+
+=head2 columns
+
+ my @columns = C4::Member::columns();
+
+Returns an array of borrowers' table columns on success,
+and an empty array on failure.
+
+=cut
+
+sub columns {
+
+ # Pure ANSI SQL goodness.
+ my $sql = 'SELECT * FROM borrowers WHERE 1=0;';
+
+ # Get the database handle.
+ my $dbh = C4::Context->dbh;
+
+ # Run the SQL statement to load STH's readonly properties.
+ my $sth = $dbh->prepare($sql);
+ my $rv = $sth->execute();
+
+ # This only fails if the table doesn't exist.
+ # This will always be called AFTER an install or upgrade,
+ # so borrowers will exist!
+ my @data;
+ if ($sth->{NUM_OF_FIELDS}>0) {
+ @data = @{$sth->{NAME}};
+ }
+ else {
+ @data = ();
+ }
+ return @data;
}
+
=head2 ModMember
my $success = ModMember(borrowernumber => $borrowernumber,
if ($data{password} eq '****' or $data{password} eq '') {
delete $data{password};
} else {
- $data{password} = md5_base64($data{password});
+ if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
+ # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
+ Koha::NorwegianPatronDB::NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
+ }
+ $data{password} = hash_password($data{password});
}
}
- my $execute_success=UpdateInTable("borrowers",\%data);
- if ($execute_success) { # only proceed if the update was a success
+ my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
+
+ # get only the columns of a borrower
+ my $schema = Koha::Database->new()->schema;
+ my @columns = $schema->source('Borrower')->columns;
+ my $new_borrower = { map { join(' ', @columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) };
+ delete $new_borrower->{flags};
+
+ $new_borrower->{dateofbirth} ||= undef if exists $new_borrower->{dateofbirth};
+ $new_borrower->{dateenrolled} ||= undef if exists $new_borrower->{dateenrolled};
+ $new_borrower->{dateexpiry} ||= undef if exists $new_borrower->{dateexpiry};
+ $new_borrower->{debarred} ||= undef if exists $new_borrower->{debarred};
+ my $rs = $schema->resultset('Borrower')->search({
+ borrowernumber => $new_borrower->{borrowernumber},
+ });
+ my $execute_success = $rs->update($new_borrower);
+ if ($execute_success ne '0E0') { # only proceed if the update was a success
# ok if its an adult (type) it may have borrowers that depend on it as a guarantor
# so when we update information for an adult we should check for guarantees and update the relevant part
# of their records, ie addresses and phone numbers
# is adult check guarantees;
UpdateGuarantees(%data);
}
+
+ # If the patron changes to a category with enrollment fee, we add a fee
+ if ( $data{categorycode} and $data{categorycode} ne $old_categorycode ) {
+ if ( C4::Context->preference('FeeOnChangePatronCategory') ) {
+ AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+ }
+ }
+
+ # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
+ # cronjob will use for syncing with NL
+ if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
+ my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
+ 'synctype' => 'norwegianpatrondb',
+ 'borrowernumber' => $data{'borrowernumber'}
+ });
+ # Do not set to "edited" if syncstatus is "new". We need to sync as new before
+ # we can sync as changed. And the "new sync" will pick up all changes since
+ # the patron was created anyway.
+ if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) {
+ $borrowersync->update( { 'syncstatus' => 'edited' } );
+ }
+ # Set the value of 'sync'
+ $borrowersync->update( { 'sync' => $data{'sync'} } );
+ # Try to do the live sync
+ Koha::NorwegianPatronDB::NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
+ }
+
logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog");
}
return $execute_success;
}
-
=head2 AddMember
$borrowernumber = &AddMember(%borrower);
insert new borrower into table
+
+(%borrower keys are database columns. Database columns could be
+different in different versions. Please look into database for correct
+column names.)
+
Returns the borrowernumber upon success
Returns as undef upon any db error without further processing
sub AddMember {
my (%data) = @_;
my $dbh = C4::Context->dbh;
+ my $schema = Koha::Database->new()->schema;
# generate a proper login if none provided
- $data{'userid'} = Generate_Userid($data{'borrowernumber'}, $data{'firstname'}, $data{'surname'}) if $data{'userid'} eq '';
+ $data{'userid'} = Generate_Userid( $data{'borrowernumber'}, $data{'firstname'}, $data{'surname'} )
+ if ( $data{'userid'} eq '' || !Check_Userid( $data{'userid'} ) );
# add expiration date if it isn't already there
unless ( $data{'dateexpiry'} ) {
$data{'dateenrolled'} = C4::Dates->new()->output("iso");
}
+ my $patron_category = $schema->resultset('Category')->find( $data{'categorycode'} );
+ $data{'privacy'} =
+ $patron_category->default_privacy() eq 'default' ? 1
+ : $patron_category->default_privacy() eq 'never' ? 2
+ : $patron_category->default_privacy() eq 'forever' ? 0
+ : undef;
+ # Make a copy of the plain text password for later use
+ my $plain_text_password = $data{'password'};
+
# create a disabled account if no password provided
- $data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
- $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
+ $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
+
+ # we don't want invalid dates in the db (mysql has a bad habit of inserting 0000-00-00
+ $data{'dateofbirth'} = undef if( not $data{'dateofbirth'} );
+ $data{'debarred'} = undef if ( not $data{'debarred'} );
+ # get only the columns of Borrower
+ my @columns = $schema->source('Borrower')->columns;
+ my $new_member = { map { join(' ',@columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) } ;
+ delete $new_member->{borrowernumber};
+
+ my $rs = $schema->resultset('Borrower');
+ $data{borrowernumber} = $rs->create($new_member)->id;
+
+ # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
+ # cronjob will use for syncing with NL
+ if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
+ Koha::Database->new->schema->resultset('BorrowerSync')->create({
+ 'borrowernumber' => $data{'borrowernumber'},
+ 'synctype' => 'norwegianpatrondb',
+ 'sync' => 1,
+ 'syncstatus' => 'new',
+ 'hashed_pin' => Koha::NorwegianPatronDB::NLEncryptPIN( $plain_text_password ),
+ });
+ }
# mysql_insertid is probably bad. not necessarily accurate and mysql-specific at best.
logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
-
- # check for enrollment fee & add it if needed
- my $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
- $sth->execute($data{'categorycode'});
- my ($enrolmentfee) = $sth->fetchrow;
- if ($sth->err) {
- warn sprintf('Database returned the following error: %s', $sth->errstr);
- return;
- }
- if ($enrolmentfee && $enrolmentfee > 0) {
- # insert fee in patron debts
- manualinvoice($data{'borrowernumber'}, '', '', 'A', $enrolmentfee);
- }
- return $data{'borrowernumber'};
+ AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+
+ return $data{borrowernumber};
}
+=head2 Check_Userid
+
+ my $uniqueness = Check_Userid($userid,$borrowernumber);
+
+ $borrowernumber is optional (i.e. it can contain a blank value). If $userid is passed with a blank $borrowernumber variable, the database will be checked for all instances of that userid (i.e. userid=? AND borrowernumber != '').
+
+ If $borrowernumber is provided, the database will be checked for every instance of that userid coupled with a different borrower(number) than the one provided.
+
+ return :
+ 0 for not unique (i.e. this $userid already exists)
+ 1 for unique (i.e. this $userid does not exist, or this $userid/$borrowernumber combination already exists)
+
+=cut
sub Check_Userid {
- my ($uid,$member) = @_;
- my $dbh = C4::Context->dbh;
- # Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
- # Then we need to tell the user and have them create a new one.
- my $sth =
- $dbh->prepare(
- "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
- $sth->execute( $uid, $member );
- if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
- return 0;
- }
- else {
- return 1;
- }
+ my ( $uid, $borrowernumber ) = @_;
+
+ return 0 unless ($uid); # userid is a unique column, we should assume NULL is not unique
+
+ return 0 if ( $uid eq C4::Context->config('user') );
+
+ my $rs = Koha::Database->new()->schema()->resultset('Borrower');
+
+ my $params;
+ $params->{userid} = $uid;
+ $params->{borrowernumber} = { '!=' => $borrowernumber } if ($borrowernumber);
+
+ my $count = $rs->count( $params );
+
+ return $count ? 0 : 1;
}
+=head2 Generate_Userid
+
+ my $newuid = Generate_Userid($borrowernumber, $firstname, $surname);
+
+ Generate a userid using the $surname and the $firstname (if there is a value in $firstname).
+
+ $borrowernumber is optional (i.e. it can contain a blank value). A value is passed when generating a new userid for an existing borrower. When a new userid is created for a new borrower, a blank value is passed to this sub.
+
+ return :
+ new userid ($firstname.$surname if there is a $firstname, or $surname if there is no value in $firstname) plus offset (0 if the $newuid is unique, or a higher numeric value if Check_Userid finds an existing match for the $newuid in the database).
+
+=cut
+
sub Generate_Userid {
my ($borrowernumber, $firstname, $surname) = @_;
my $newuid;
my $offset = 0;
+ #The script will "do" the following code and increment the $offset until Check_Userid = 1 (i.e. until $newuid comes back as unique)
do {
$firstname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
$surname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
my $sth = C4::Context->dbh->prepare($query);
$sth->execute(@borrowernumbers);
my $data = $sth->fetchall_arrayref({});
- my $tz = C4::Context->tz();
- my $today = DateTime->now( time_zone => $tz);
+ my $today = dt_from_string;
foreach (@{$data}) {
if ($_->{issuedate}) {
$_->{issuedate} = dt_from_string($_->{issuedate}, 'sql');
}
+ $_->{date_due_sql} = $_->{date_due};
+ # FIXME no need to have this value
$_->{date_due} or next;
- $_->{date_due} = DateTime::Format::DateParse->parse_datetime($_->{date_due}, $tz->name());
+ $_->{date_due_sql} = $_->{date_due};
+ # FIXME no need to have this value
+ $_->{date_due} = dt_from_string($_->{date_due}, 'sql');
if ( DateTime->compare($_->{date_due}, $today) == -1 ) {
$_->{overdue} = 1;
}
sub GetAllIssues {
my ( $borrowernumber, $order, $limit ) = @_;
+ return unless $borrowernumber;
+ $order = 'date_due desc' unless $order;
+
my $dbh = C4::Context->dbh;
my $query =
'SELECT *, issues.timestamp as issuestimestamp, issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp
=cut
-#'
sub GetMemberAccountRecords {
- my ($borrowernumber,$date) = @_;
+ my ($borrowernumber) = @_;
my $dbh = C4::Context->dbh;
my @acctlines;
my $numlines = 0;
SELECT *
FROM accountlines
WHERE borrowernumber=?);
- my @bind = ($borrowernumber);
- if ($date && $date ne ''){
- $strsth.=" AND date < ? ";
- push(@bind,$date);
- }
- $strsth.=" ORDER BY date desc,timestamp DESC";
+ $strsth.=" ORDER BY accountlines_id desc";
my $sth= $dbh->prepare( $strsth );
- $sth->execute( @bind );
+ $sth->execute( $borrowernumber );
+
my $total = 0;
while ( my $data = $sth->fetchrow_hashref ) {
if ( $data->{itemnumber} ) {
return ( $total, \@acctlines,$numlines);
}
+=head2 GetMemberAccountBalance
+
+ ($total_balance, $non_issue_balance, $other_charges) = &GetMemberAccountBalance($borrowernumber);
+
+Calculates amount immediately owing by the patron - non-issue charges.
+Based on GetMemberAccountRecords.
+Charges exempt from non-issue are:
+* Res (reserves)
+* Rent (rental) if RentalsInNoissuesCharge syspref is set to false
+* Manual invoices if ManInvInNoissuesCharge syspref is set to false
+
+=cut
+
+sub GetMemberAccountBalance {
+ my ($borrowernumber) = @_;
+
+ my $ACCOUNT_TYPE_LENGTH = 5; # this is plain ridiculous...
+
+ my @not_fines;
+ push @not_fines, 'Res' unless C4::Context->preference('HoldsInNoissuesCharge');
+ push @not_fines, 'Rent' unless C4::Context->preference('RentalsInNoissuesCharge');
+ unless ( C4::Context->preference('ManInvInNoissuesCharge') ) {
+ my $dbh = C4::Context->dbh;
+ my $man_inv_types = $dbh->selectcol_arrayref(qq{SELECT authorised_value FROM authorised_values WHERE category = 'MANUAL_INV'});
+ push @not_fines, map substr($_, 0, $ACCOUNT_TYPE_LENGTH), @$man_inv_types;
+ }
+ my %not_fine = map {$_ => 1} @not_fines;
+
+ my ($total, $acctlines) = GetMemberAccountRecords($borrowernumber);
+ my $other_charges = 0;
+ foreach (@$acctlines) {
+ $other_charges += $_->{amountoutstanding} if $not_fine{ substr($_->{accounttype}, 0, $ACCOUNT_TYPE_LENGTH) };
+ }
+
+ return ( $total, $total - $other_charges, $other_charges);
+}
+
=head2 GetBorNotifyAcctRecord
($total, $acctlines, $count) = &GetBorNotifyAcctRecord($params,$notifyid);
}
sub checkcardnumber {
- my ($cardnumber,$borrowernumber) = @_;
+ my ( $cardnumber, $borrowernumber ) = @_;
+
# If cardnumber is null, we assume they're allowed.
- return 0 if !defined($cardnumber);
+ return 0 unless defined $cardnumber;
+
my $dbh = C4::Context->dbh;
my $query = "SELECT * FROM borrowers WHERE cardnumber=?";
$query .= " AND borrowernumber <> ?" if ($borrowernumber);
- my $sth = $dbh->prepare($query);
- if ($borrowernumber) {
- $sth->execute($cardnumber,$borrowernumber);
- } else {
- $sth->execute($cardnumber);
- }
- if (my $data= $sth->fetchrow_hashref()){
- return 1;
- }
- else {
- return 0;
- }
-}
+ my $sth = $dbh->prepare($query);
+ $sth->execute(
+ $cardnumber,
+ ( $borrowernumber ? $borrowernumber : () )
+ );
+
+ return 1 if $sth->fetchrow_hashref;
+
+ my ( $min_length, $max_length ) = get_cardnumber_length();
+ return 2
+ if length $cardnumber > $max_length
+ or length $cardnumber < $min_length;
+ return 0;
+}
+
+=head2 get_cardnumber_length
+
+ my ($min, $max) = C4::Members::get_cardnumber_length()
+
+Returns the minimum and maximum length for patron cardnumbers as
+determined by the CardnumberLength system preference, the
+BorrowerMandatoryField system preference, and the width of the
+database column.
+
+=cut
+
+sub get_cardnumber_length {
+ my ( $min, $max ) = ( 0, 16 ); # borrowers.cardnumber is a nullable varchar(16)
+ $min = 1 if C4::Context->preference('BorrowerMandatoryField') =~ /cardnumber/;
+ if ( my $cardnumber_length = C4::Context->preference('CardnumberLength') ) {
+ # Is integer and length match
+ if ( $cardnumber_length =~ m|^\d+$| ) {
+ $min = $max = $cardnumber_length
+ if $cardnumber_length >= $min
+ and $cardnumber_length <= $max;
+ }
+ # Else assuming it is a range
+ elsif ( $cardnumber_length =~ m|(\d*),(\d*)| ) {
+ $min = $1 if $1 and $min < $1;
+ $max = $2 if $2 and $max > $2;
+ }
+
+ }
+ return ( $min, $max );
+}
=head2 getzipnamecity (OUEST-PROVENCE)
}
}
+=head2 GetNoticeEmailAddress
+
+ $email = GetNoticeEmailAddress($borrowernumber);
+
+Return the email address of borrower used for notices, given the borrowernumber.
+Returns the empty string if no email address.
+
+=cut
+
+sub GetNoticeEmailAddress {
+ my $borrowernumber = shift;
+
+ my $which_address = C4::Context->preference("AutoEmailPrimaryAddress");
+ # if syspref is set to 'first valid' (value == OFF), look up email address
+ if ( $which_address eq 'OFF' ) {
+ return GetFirstValidEmailAddress($borrowernumber);
+ }
+ # specified email address field
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare( qq{
+ SELECT $which_address AS primaryemail
+ FROM borrowers
+ WHERE borrowernumber=?
+ } );
+ $sth->execute($borrowernumber);
+ my $data = $sth->fetchrow_hashref;
+ return $data->{'primaryemail'} || '';
+}
+
=head2 GetExpiryDate
$expirydate = GetExpiryDate($categorycode, $dateenrolled);
}
}
-=head2 checkuserpassword (OUEST-PROVENCE)
+=head2 GetUpcomingMembershipExpires
-check for the password and login are not used
-return the number of record
-0=> NOT USED 1=> USED
+ my $upcoming_mem_expires = GetUpcomingMembershipExpires();
=cut
-sub checkuserpassword {
- my ( $borrowernumber, $userid, $password ) = @_;
- $password = md5_base64($password);
+sub GetUpcomingMembershipExpires {
my $dbh = C4::Context->dbh;
- my $sth =
- $dbh->prepare(
-"Select count(*) from borrowers where borrowernumber !=? and userid =? and password=? "
- );
- $sth->execute( $borrowernumber, $userid, $password );
- my $number_rows = $sth->fetchrow;
- return $number_rows;
+ my $days = C4::Context->preference("MembershipExpiryDaysNotice") || 0;
+ my $dateexpiry = output_pref({ dt => (dt_from_string()->add( days => $days)), dateformat => 'iso', dateonly => 1 });
+ my $query = "
+ SELECT borrowers.*, categories.description,
+ branches.branchname, branches.branchemail FROM borrowers
+ LEFT JOIN branches on borrowers.branchcode = branches.branchcode
+ LEFT JOIN categories on borrowers.categorycode = categories.categorycode
+ WHERE dateexpiry = ?;
+ ";
+ my $sth = $dbh->prepare($query);
+ $sth->execute($dateexpiry);
+ my $results = $sth->fetchall_arrayref({});
+ return $results;
}
=head2 GetborCatFromCatType
$categorycode = &GetBorrowerCategoryCode( $borrowernumber );
Given the borrowernumber, the function returns the corresponding categorycode
+
=cut
sub GetBorrowerCategorycode {
? 0
: C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
my $dbh = C4::Context->dbh;
- my $query = "SELECT * FROM categories";
+ my $query = "SELECT categories.* FROM categories";
$query .= qq{
LEFT JOIN categories_branches ON categories.categorycode = categories_branches.categorycode
WHERE branchcode = ? OR branchcode IS NULL GROUP BY description
return $data;
} # sub getborrowercategory
-=head2 ethnicitycategories
-
- ($codes_arrayref, $labels_hashref) = ðnicitycategories();
-
-Looks up the different ethnic types in the database. Returns two
-elements: a reference-to-array, which lists the ethnicity codes, and a
-reference-to-hash, which maps the ethnicity codes to ethnicity
-descriptions.
-
-=cut
-
-#'
-
-sub ethnicitycategories {
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("Select code,name from ethnicity order by name");
- $sth->execute;
- my %labels;
- my @codes;
- while ( my $data = $sth->fetchrow_hashref ) {
- push @codes, $data->{'code'};
- $labels{ $data->{'code'} } = $data->{'name'};
- }
- return ( \@codes, \%labels );
-}
-
-=head2 fixEthnicity
-
- $ethn_name = &fixEthnicity($ethn_code);
-
-Takes an ethnicity code (e.g., "european" or "pi") and returns the
-corresponding descriptive name from the C<ethnicity> table in the
-Koha database ("European" or "Pacific Islander").
-
-=cut
-
-#'
-
-sub fixEthnicity {
- my $ethnicity = shift;
- return unless $ethnicity;
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("Select name from ethnicity where code = ?");
- $sth->execute($ethnicity);
- my $data = $sth->fetchrow_hashref;
- return $data->{'name'};
-} # sub fixEthnicity
-
=head2 GetAge
$dateofbirth,$date = &GetAge($date);
return $age;
} # sub get_age
-=head2 get_institutions
-
- $insitutions = get_institutions();
-
-Just returns a list of all the borrowers of type I, borrownumber and name
-
-=cut
-
-#'
-sub get_institutions {
- my $dbh = C4::Context->dbh();
- my $sth =
- $dbh->prepare(
-"SELECT borrowernumber,surname FROM borrowers WHERE categorycode=? ORDER BY surname"
- );
- $sth->execute('I');
- my %orgs;
- while ( my $data = $sth->fetchrow_hashref() ) {
- $orgs{ $data->{'borrowernumber'} } = $data;
- }
- return ( \%orgs );
+=head2 SetAge
-} # sub get_institutions
+ $borrower = C4::Members::SetAge($borrower, $datetimeduration);
+ $borrower = C4::Members::SetAge($borrower, '0015-12-10');
+ $borrower = C4::Members::SetAge($borrower, $datetimeduration, $datetime_reference);
-=head2 add_member_orgs
+ eval { $borrower = C4::Members::SetAge($borrower, '015-1-10'); };
+ if ($@) {print $@;} #Catch a bad ISO Date or kill your script!
- add_member_orgs($borrowernumber,$borrowernumbers);
+This function sets the borrower's dateofbirth to match the given age.
+Optionally relative to the given $datetime_reference.
-Takes a borrowernumber and a list of other borrowernumbers and inserts them into the borrowers_to_borrowers table
+@PARAM1 koha.borrowers-object
+@PARAM2 DateTime::Duration-object as the desired age
+ OR a ISO 8601 Date. (To make the API more pleasant)
+@PARAM3 DateTime-object as the relative date, defaults to now().
+RETURNS The given borrower reference @PARAM1.
+DIES If there was an error with the ISO Date handling.
=cut
#'
-sub add_member_orgs {
- my ( $borrowernumber, $otherborrowers ) = @_;
- my $dbh = C4::Context->dbh();
- my $query =
- "INSERT INTO borrowers_to_borrowers (borrower1,borrower2) VALUES (?,?)";
- my $sth = $dbh->prepare($query);
- foreach my $otherborrowernumber (@$otherborrowers) {
- $sth->execute( $borrowernumber, $otherborrowernumber );
+sub SetAge{
+ my ( $borrower, $datetimeduration, $datetime_ref ) = @_;
+ $datetime_ref = DateTime->now() unless $datetime_ref;
+
+ if ($datetimeduration && ref $datetimeduration ne 'DateTime::Duration') {
+ if ($datetimeduration =~ /^(\d{4})-(\d{2})-(\d{2})/) {
+ $datetimeduration = DateTime::Duration->new(years => $1, months => $2, days => $3);
+ }
+ else {
+ die "C4::Members::SetAge($borrower, $datetimeduration), datetimeduration not a valid ISO 8601 Date!\n";
+ }
}
-} # sub add_member_orgs
+ my $new_datetime_ref = $datetime_ref->clone();
+ $new_datetime_ref->subtract_duration( $datetimeduration );
+
+ $borrower->{dateofbirth} = $new_datetime_ref->ymd();
+
+ return $borrower;
+} # sub SetAge
=head2 GetCities
$result = &MoveMemberToDeleted($borrowernumber);
Copy the record from borrowers to deletedborrowers table.
+The routine returns 1 for success, undef for failure.
=cut
-# FIXME: should do it in one SQL statement w/ subquery
-# Otherwise, we should return the @data on success
-
sub MoveMemberToDeleted {
my ($member) = shift or return;
- my $dbh = C4::Context->dbh;
- my $query = qq|SELECT *
- FROM borrowers
- WHERE borrowernumber=?|;
- my $sth = $dbh->prepare($query);
- $sth->execute($member);
- my @data = $sth->fetchrow_array;
- (@data) or return; # if we got a bad borrowernumber, there's nothing to insert
- $sth =
- $dbh->prepare( "INSERT INTO deletedborrowers VALUES ("
- . ( "?," x ( scalar(@data) - 1 ) )
- . "?)" );
- $sth->execute(@data);
+
+ my $schema = Koha::Database->new()->schema();
+ my $borrowers_rs = $schema->resultset('Borrower');
+ $borrowers_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+ my $borrower = $borrowers_rs->find($member);
+ return unless $borrower;
+
+ my $deleted = $schema->resultset('Deletedborrower')->create($borrower);
+
+ return $deleted ? 1 : undef;
}
=head2 DelMember
return $sth->rows;
}
+=head2 HandleDelBorrower
+
+ HandleDelBorrower($borrower);
+
+When a member is deleted (DelMember in Members.pm), you should call me first.
+This routine deletes/moves lists and entries for the deleted member/borrower.
+Lists owned by the borrower are deleted, but entries from the borrower to
+other lists are kept.
+
+=cut
+
+sub HandleDelBorrower {
+ my ($borrower)= @_;
+ my $query;
+ my $dbh = C4::Context->dbh;
+
+ #Delete all lists and all shares of this borrower
+ #Consistent with the approach Koha uses on deleting individual lists
+ #Note that entries in virtualshelfcontents added by this borrower to
+ #lists of others will be handled by a table constraint: the borrower
+ #is set to NULL in those entries.
+ $query="DELETE FROM virtualshelves WHERE owner=?";
+ $dbh->do($query,undef,($borrower));
+
+ #NOTE:
+ #We could handle the above deletes via a constraint too.
+ #But a new BZ report 11889 has been opened to discuss another approach.
+ #Instead of deleting we could also disown lists (based on a pref).
+ #In that way we could save shared and public lists.
+ #The current table constraints support that idea now.
+ #This pref should then govern the results of other routines/methods such as
+ #Koha::Virtualshelf->new->delete too.
+}
+
=head2 ExtendMemberSubscriptionTo (OUEST-PROVENCE)
$date = ExtendMemberSubscriptionTo($borrowerid, $date);
SET dateexpiry='$date'
WHERE borrowernumber='$borrowerid'
EOF
- # add enrolmentfee if needed
- $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
- $sth->execute($borrower->{'categorycode'});
- my ($enrolmentfee) = $sth->fetchrow;
- if ($enrolmentfee && $enrolmentfee > 0) {
- # insert fee in patron debts
- manualinvoice($borrower->{'borrowernumber'}, '', '', 'A', $enrolmentfee);
- }
- logaction("MEMBERS", "RENEW", $borrower->{'borrowernumber'}, "Membership renewed")if C4::Context->preference("BorrowersLog");
- return $date if ($sth);
- return 0;
-}
-
-=head2 GetRoadTypes (OUEST-PROVENCE)
-
- ($idroadtypearrayref, $roadttype_hashref) = &GetRoadTypes();
-
-Looks up the different road type . Returns two
-elements: a reference-to-array, which lists the id_roadtype
-codes, and a reference-to-hash, which maps the road type of the road .
-=cut
-
-sub GetRoadTypes {
- my $dbh = C4::Context->dbh;
- my $query = qq|
-SELECT roadtypeid,road_type
-FROM roadtype
-ORDER BY road_type|;
- my $sth = $dbh->prepare($query);
- $sth->execute();
- my %roadtype;
- my @id;
-
- # insert empty value to create a empty choice in cgi popup
+ AddEnrolmentFeeIfNeeded( $borrower->{categorycode}, $borrower->{borrowernumber} );
- while ( my $data = $sth->fetchrow_hashref ) {
-
- push @id, $data->{'roadtypeid'};
- $roadtype{ $data->{'roadtypeid'} } = $data->{'road_type'};
- }
-
-#test to know if the table contain some records if no the function return nothing
- my $id = @id;
- if ( $id eq 0 ) {
- return ();
- }
- else {
- unshift( @id, "" );
- return ( \@id, \%roadtype );
- }
+ logaction("MEMBERS", "RENEW", $borrower->{'borrowernumber'}, "Membership renewed")if C4::Context->preference("BorrowersLog");
+ return $date if ($sth);
+ return 0;
}
-
-
=head2 GetTitles (OUEST-PROVENCE)
($borrowertitle)= &GetTitles();
=head2 GetPatronImage
- my ($imagedata, $dberror) = GetPatronImage($cardnumber);
+ my ($imagedata, $dberror) = GetPatronImage($borrowernumber);
-Returns the mimetype and binary image data of the image for the patron with the supplied cardnumber.
+Returns the mimetype and binary image data of the image for the patron with the supplied borrowernumber.
=cut
sub GetPatronImage {
- my ($cardnumber) = @_;
- warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
+ my ($borrowernumber) = @_;
+ warn "Borrowernumber passed to GetPatronImage is $borrowernumber" if $debug;
my $dbh = C4::Context->dbh;
- my $query = 'SELECT mimetype, imagefile FROM patronimage WHERE cardnumber = ?';
+ my $query = 'SELECT mimetype, imagefile FROM patronimage WHERE borrowernumber = ?';
my $sth = $dbh->prepare($query);
- $sth->execute($cardnumber);
+ $sth->execute($borrowernumber);
my $imagedata = $sth->fetchrow_hashref;
warn "Database error!" if $sth->errstr;
return $imagedata, $sth->errstr;
my ($cardnumber, $mimetype, $imgfile) = @_;
warn "Parameters passed in: Cardnumber=$cardnumber, Mimetype=$mimetype, " . ($imgfile ? "Imagefile" : "No Imagefile") if $debug;
my $dbh = C4::Context->dbh;
- my $query = "INSERT INTO patronimage (cardnumber, mimetype, imagefile) VALUES (?,?,?) ON DUPLICATE KEY UPDATE imagefile = ?;";
+ my $query = "INSERT INTO patronimage (borrowernumber, mimetype, imagefile) VALUES ( ( SELECT borrowernumber from borrowers WHERE cardnumber = ? ),?,?) ON DUPLICATE KEY UPDATE imagefile = ?;";
my $sth = $dbh->prepare($query);
$sth->execute($cardnumber,$mimetype,$imgfile,$imgfile);
warn "Error returned inserting $cardnumber.$mimetype." if $sth->errstr;
=head2 RmPatronImage
- my ($dberror) = RmPatronImage($cardnumber);
+ my ($dberror) = RmPatronImage($borrowernumber);
-Removes the image for the patron with the supplied cardnumber.
+Removes the image for the patron with the supplied borrowernumber.
=cut
sub RmPatronImage {
- my ($cardnumber) = @_;
- warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
+ my ($borrowernumber) = @_;
+ warn "Borrowernumber passed to GetPatronImage is $borrowernumber" if $debug;
my $dbh = C4::Context->dbh;
- my $query = "DELETE FROM patronimage WHERE cardnumber = ?;";
+ my $query = "DELETE FROM patronimage WHERE borrowernumber = ?;";
my $sth = $dbh->prepare($query);
- $sth->execute($cardnumber);
+ $sth->execute($borrowernumber);
my $dberror = $sth->errstr;
warn "Database error!" if $sth->errstr;
return $dberror;
return $hidelostitems;
}
-=head2 GetRoadTypeDetails (OUEST-PROVENCE)
+=head2 GetBorrowersToExpunge
- ($roadtype) = &GetRoadTypeDetails($roadtypeid);
+ $borrowers = &GetBorrowersToExpunge(
+ not_borrowered_since => $not_borrowered_since,
+ expired_before => $expired_before,
+ category_code => $category_code,
+ branchcode => $branchcode
+ );
-Returns the description of roadtype
-C<&$roadtype>return description of road type
-C<&$roadtypeid>this is the value of roadtype s
+ This function get all borrowers based on the given criteria.
=cut
-sub GetRoadTypeDetails {
- my ($roadtypeid) = @_;
- my $dbh = C4::Context->dbh;
- my $query = qq|
-SELECT road_type
-FROM roadtype
-WHERE roadtypeid=?|;
- my $sth = $dbh->prepare($query);
- $sth->execute($roadtypeid);
- my $roadtype = $sth->fetchrow;
- return ($roadtype);
-}
-
-=head2 GetBorrowersWhoHaveNotBorrowedSince
-
- &GetBorrowersWhoHaveNotBorrowedSince($date)
-
-this function get all borrowers who haven't borrowed since the date given on input arg.
-
-=cut
+sub GetBorrowersToExpunge {
+ my $params = shift;
-sub GetBorrowersWhoHaveNotBorrowedSince {
- my $filterdate = shift||POSIX::strftime("%Y-%m-%d",localtime());
- my $filterexpiry = shift;
- my $filterbranch = shift ||
- ((C4::Context->preference('IndependantBranches')
+ my $filterdate = $params->{'not_borrowered_since'};
+ my $filterexpiry = $params->{'expired_before'};
+ my $filtercategory = $params->{'category_code'};
+ my $filterbranch = $params->{'branchcode'} ||
+ ((C4::Context->preference('IndependentBranches')
&& C4::Context->userenv
- && C4::Context->userenv->{flags} % 2 !=1
+ && !C4::Context->IsSuperLibrarian()
&& C4::Context->userenv->{branch})
? C4::Context->userenv->{branch}
: "");
+
my $dbh = C4::Context->dbh;
- my $query = "
+ my $query = q|
SELECT borrowers.borrowernumber,
- max(old_issues.timestamp) as latestissue,
- max(issues.timestamp) as currentissue
+ MAX(old_issues.timestamp) AS latestissue,
+ MAX(issues.timestamp) AS currentissue
FROM borrowers
JOIN categories USING (categorycode)
+ LEFT JOIN (
+ SELECT guarantorid
+ FROM borrowers
+ WHERE guarantorid IS NOT NULL
+ AND guarantorid <> 0
+ ) as tmp ON borrowers.borrowernumber=tmp.guarantorid
LEFT JOIN old_issues USING (borrowernumber)
LEFT JOIN issues USING (borrowernumber)
WHERE category_type <> 'S'
- AND borrowernumber NOT IN (SELECT guarantorid FROM borrowers WHERE guarantorid IS NOT NULL AND guarantorid <> 0)
- ";
+ AND tmp.guarantorid IS NULL
+ |;
+
my @query_params;
- if ($filterbranch && $filterbranch ne ""){
- $query.=" AND borrowers.branchcode= ?";
- push @query_params,$filterbranch;
+ if ( $filterbranch && $filterbranch ne "" ) {
+ $query.= " AND borrowers.branchcode = ? ";
+ push( @query_params, $filterbranch );
}
- if($filterexpiry){
+ if ( $filterexpiry ) {
$query .= " AND dateexpiry < ? ";
- push @query_params,$filterdate;
+ push( @query_params, $filterexpiry );
+ }
+ if ( $filtercategory ) {
+ $query .= " AND categorycode = ? ";
+ push( @query_params, $filtercategory );
}
- $query.=" GROUP BY borrowers.borrowernumber";
- if ($filterdate){
- $query.=" HAVING (latestissue < ? OR latestissue IS NULL)
- AND currentissue IS NULL";
+ $query.=" GROUP BY borrowers.borrowernumber HAVING currentissue IS NULL ";
+ if ( $filterdate ) {
+ $query.=" AND ( latestissue < ? OR latestissue IS NULL ) ";
push @query_params,$filterdate;
}
warn $query if $debug;
+
my $sth = $dbh->prepare($query);
if (scalar(@query_params)>0){
$sth->execute(@query_params);
sub GetBorrowersWhoHaveNeverBorrowed {
my $filterbranch = shift ||
- ((C4::Context->preference('IndependantBranches')
+ ((C4::Context->preference('IndependentBranches')
&& C4::Context->userenv
- && C4::Context->userenv->{flags} % 2 !=1
+ && !C4::Context->IsSuperLibrarian()
&& C4::Context->userenv->{branch})
? C4::Context->userenv->{branch}
: "");
my $dbh = C4::Context->dbh;
my $date = shift ||POSIX::strftime("%Y-%m-%d",localtime());
my $filterbranch = shift ||
- ((C4::Context->preference('IndependantBranches')
+ ((C4::Context->preference('IndependentBranches')
&& C4::Context->userenv
- && C4::Context->userenv->{flags} % 2 !=1
+ && !C4::Context->IsSuperLibrarian()
&& C4::Context->userenv->{branch})
? C4::Context->userenv->{branch}
: "");
return $results;
}
-=head2 DebarMember
-
-my $success = DebarMember( $borrowernumber, $todate );
-
-marks a Member as debarred, and therefore unable to checkout any more
-items.
-
-return :
-true on success, false on failure
-
-=cut
-
-sub DebarMember {
- my $borrowernumber = shift;
- my $todate = shift;
-
- return unless defined $borrowernumber;
- return unless $borrowernumber =~ /^\d+$/;
-
- return ModMember(
- borrowernumber => $borrowernumber,
- debarred => $todate
- );
-
-}
-
=head2 ModPrivacy
-=over 4
-
-my $success = ModPrivacy( $borrowernumber, $privacy );
+ my $success = ModPrivacy( $borrowernumber, $privacy );
Update the privacy of a patron.
return :
true on success, false on failure
-=back
-
=cut
sub ModPrivacy {
$quickslip is boolean, to indicate whether we want a quick slip
+ IssueSlip populates ISSUESLIP and ISSUEQSLIP, and will make the following expansions:
+
+ Both slips:
+
+ <<branches.*>>
+ <<borrowers.*>>
+
+ ISSUESLIP:
+
+ <checkedout>
+ <<biblio.*>>
+ <<items.*>>
+ <<biblioitems.*>>
+ <<issues.*>>
+ </checkedout>
+
+ <overdue>
+ <<biblio.*>>
+ <<items.*>>
+ <<biblioitems.*>>
+ <<issues.*>>
+ </overdue>
+
+ <news>
+ <<opac_news.*>>
+ </news>
+
+ ISSUEQSLIP:
+
+ <checkedout>
+ <<biblio.*>>
+ <<items.*>>
+ <<biblioitems.*>>
+ <<issues.*>>
+ </checkedout>
+
+ NOTE: Not all table fields are available, pleasee see GetPendingIssues for a list of available fields.
+
=cut
sub IssueSlip {
my ($branch, $borrowernumber, $quickslip) = @_;
-# return unless ( C4::Context->boolean_preference('printcirculationslips') );
+ # FIXME Check callers before removing this statement
+ #return unless $borrowernumber;
- my $now = POSIX::strftime("%Y-%m-%d", localtime);
+ my @issues = @{ GetPendingIssues($borrowernumber) };
- my $issueslist = GetPendingIssues($borrowernumber);
- foreach my $it (@$issueslist){
- if ((substr $it->{'issuedate'}, 0, 10) eq $now) {
- $it->{'now'} = 1;
+ for my $issue (@issues) {
+ $issue->{date_due} = $issue->{date_due_sql};
+ if ($quickslip) {
+ my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
+ if ( substr( $issue->{issuedate}, 0, 10 ) eq $today
+ or substr( $issue->{lastreneweddate}, 0, 10 ) eq $today ) {
+ $issue->{now} = 1;
+ };
}
- elsif ((substr $it->{'date_due'}, 0, 10) le $now) {
- $it->{'overdue'} = 1;
- }
-
- $it->{'date_due'}=format_date($it->{'date_due'});
}
- my @issues = sort { $b->{'timestamp'} <=> $a->{'timestamp'} } @$issueslist;
+
+ # Sort on timestamp then on issuedate (useful for tests and could be if modified in a batch
+ @issues = sort {
+ my $s = $b->{timestamp} <=> $a->{timestamp};
+ $s == 0 ?
+ $b->{issuedate} <=> $a->{issuedate} : $s;
+ } @issues;
my ($letter_code, %repeat);
if ( $quickslip ) {
$letter_code = 'ISSUEQSLIP';
%repeat = (
'checkedout' => [ map {
- 'biblio' => $_,
- 'items' => $_,
- 'issues' => $_,
+ 'biblio' => $_,
+ 'items' => $_,
+ 'biblioitems' => $_,
+ 'issues' => $_,
}, grep { $_->{'now'} } @issues ],
);
}
$letter_code = 'ISSUESLIP';
%repeat = (
'checkedout' => [ map {
- 'biblio' => $_,
- 'items' => $_,
- 'issues' => $_,
+ 'biblio' => $_,
+ 'items' => $_,
+ 'biblioitems' => $_,
+ 'issues' => $_,
}, grep { !$_->{'overdue'} } @issues ],
'overdue' => [ map {
- 'biblio' => $_,
- 'items' => $_,
- 'issues' => $_,
+ 'biblio' => $_,
+ 'items' => $_,
+ 'biblioitems' => $_,
+ 'issues' => $_,
}, grep { $_->{'overdue'} } @issues ],
'news' => [ map {
$_->{'timestamp'} = $_->{'newdate'};
{ opac_news => $_ }
- } @{ GetNewsToDisplay("slip") } ],
+ } @{ GetNewsToDisplay("slip",$branch) } ],
);
}
return @result;
}
+=head2 AddMember_Opac
+
+=cut
+
sub AddMember_Opac {
my ( %borrower ) = @_;
return ( $borrowernumber, $password );
}
+=head2 AddEnrolmentFeeIfNeeded
+
+ AddEnrolmentFeeIfNeeded( $borrower->{categorycode}, $borrower->{borrowernumber} );
+
+Add enrolment fee for a patron if needed.
+
+=cut
+
+sub AddEnrolmentFeeIfNeeded {
+ my ( $categorycode, $borrowernumber ) = @_;
+ # check for enrollment fee & add it if needed
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare(q{
+ SELECT enrolmentfee
+ FROM categories
+ WHERE categorycode=?
+ });
+ $sth->execute( $categorycode );
+ if ( $sth->err ) {
+ warn sprintf('Database returned the following error: %s', $sth->errstr);
+ return;
+ }
+ my ($enrolmentfee) = $sth->fetchrow;
+ if ($enrolmentfee && $enrolmentfee > 0) {
+ # insert fee in patron debts
+ C4::Accounts::manualinvoice( $borrowernumber, '', '', 'A', $enrolmentfee );
+ }
+}
+
+=head2 HasOverdues
+
+=cut
+
+sub HasOverdues {
+ my ( $borrowernumber ) = @_;
+
+ my $sql = "SELECT COUNT(*) FROM issues WHERE date_due < NOW() AND borrowernumber = ?";
+ my $sth = C4::Context->dbh->prepare( $sql );
+ $sth->execute( $borrowernumber );
+ my ( $count ) = $sth->fetchrow_array();
+
+ return $count;
+}
+
+=head2 DeleteExpiredOpacRegistrations
+
+ Delete accounts that haven't been upgraded from the 'temporary' category
+ Returns the number of removed patrons
+
+=cut
+
+sub DeleteExpiredOpacRegistrations {
+
+ my $delay = C4::Context->preference('PatronSelfRegistrationExpireTemporaryAccountsDelay');
+ my $category_code = C4::Context->preference('PatronSelfRegistrationDefaultCategory');
+
+ return 0 if not $category_code or not defined $delay or $delay eq q||;
+
+ my $query = qq|
+SELECT borrowernumber
+FROM borrowers
+WHERE categorycode = ? AND DATEDIFF( NOW(), dateenrolled ) > ? |;
+
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare($query);
+ $sth->execute( $category_code, $delay );
+ my $cnt=0;
+ while ( my ($borrowernumber) = $sth->fetchrow_array() ) {
+ DelMember($borrowernumber);
+ $cnt++;
+ }
+ return $cnt;
+}
+
+=head2 DeleteUnverifiedOpacRegistrations
+
+ Delete all unverified self registrations in borrower_modifications,
+ older than the specified number of days.
+
+=cut
+
+sub DeleteUnverifiedOpacRegistrations {
+ my ( $days ) = @_;
+ my $dbh = C4::Context->dbh;
+ my $sql=qq|
+DELETE FROM borrower_modifications
+WHERE borrowernumber = 0 AND DATEDIFF( NOW(), timestamp ) > ?|;
+ my $cnt=$dbh->do($sql, undef, ($days) );
+ return $cnt eq '0E0'? 0: $cnt;
+}
+
+sub GetOverduesForPatron {
+ my ( $borrowernumber ) = @_;
+
+ my $sql = "
+ SELECT *
+ FROM issues, items, biblio, biblioitems
+ WHERE items.itemnumber=issues.itemnumber
+ AND biblio.biblionumber = items.biblionumber
+ AND biblio.biblionumber = biblioitems.biblionumber
+ AND issues.borrowernumber = ?
+ AND date_due < NOW()
+ ";
+
+ my $sth = C4::Context->dbh->prepare( $sql );
+ $sth->execute( $borrowernumber );
+
+ return $sth->fetchall_arrayref({});
+}
+
END { } # module clean-up code here (global destructor)
1;