use C4::Accounts;
use C4::Biblio;
use C4::Letters;
-use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
use C4::Members::Attributes qw(SearchIdMatchingAttribute UpdateBorrowerAttribute);
use C4::NewsChannels; #get slip news
use DateTime;
use Text::Unaccent qw( unac_string );
use Koha::AuthUtils qw(hash_password);
use Koha::Database;
-use Module::Load;
-if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
- load Koha::NorwegianPatronDB, qw( NLUpdateHashedPIN NLEncryptPIN NLSync );
-}
+require Koha::NorwegianPatronDB;
our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
&GetBorrowersWithIssuesHistoryOlderThan
&GetExpiryDate
+ &GetUpcomingMembershipExpires
&AddMessage
&DeleteMessage
GetBorrowersWithEmail
HasOverdues
+ GetOverduesForPatron
);
#Modify data
&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, $not_attributes ) = @_;
-
- 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 && !$not_attributes ) {
- 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("IndependentBranches") ) { # && !$showallbranches){
- if ( my $userenv = C4::Context->userenv ) {
- my $branch = $userenv->{'branch'};
- if ( !C4::Context->IsSuperLibrarian() && $branch ){
- 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 ($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 or a manual debarment, in which case
+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
} else {
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
# Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
- NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
+ Koha::NorwegianPatronDB::NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
}
$data{password} = hash_password($data{password});
}
}
my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
- my $execute_success=UpdateInTable("borrowers",\%data);
- if ($execute_success) { # only proceed if the update was a success
+
+ # 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
# If the patron changes to a category with enrollment fee, we add a fee
if ( $data{categorycode} and $data{categorycode} ne $old_categorycode ) {
- AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+ if ( C4::Context->preference('FeeOnChangePatronCategory') ) {
+ AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
+ }
}
# If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
# Set the value of 'sync'
$borrowersync->update( { 'sync' => $data{'sync'} } );
# Try to do the live sync
- NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
+ Koha::NorwegianPatronDB::NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
}
logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog");
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'} )
$data{'dateenrolled'} = C4::Dates->new()->output("iso");
}
- my $patron_category =
- Koha::Database->new()->schema()->resultset('Category')
- ->find( $data{'categorycode'} );
+ 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
# create a disabled account if no password provided
$data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
- $data{'borrowernumber'}=InsertInTable("borrowers",\%data);
+
+ # 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
'synctype' => 'norwegianpatrondb',
'sync' => 1,
'syncstatus' => 'new',
- 'hashed_pin' => NLEncryptPIN( $plain_text_password ),
+ 'hashed_pin' => Koha::NorwegianPatronDB::NLEncryptPIN( $plain_text_password ),
});
}
AddEnrolmentFeeIfNeeded( $data{categorycode}, $data{borrowernumber} );
- return $data{'borrowernumber'};
+ return $data{borrowernumber};
}
=head2 Check_Userid
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_sql} = $_->{date_due};
# FIXME no need to have this value
SELECT *
FROM accountlines
WHERE borrowernumber=?);
- $strsth.=" ORDER BY date desc,timestamp DESC";
+ $strsth.=" ORDER BY accountlines_id desc";
my $sth= $dbh->prepare( $strsth );
$sth->execute( $borrowernumber );
}
}
+=head2 GetUpcomingMembershipExpires
+
+ my $upcoming_mem_expires = GetUpcomingMembershipExpires();
+
+=cut
+
+sub GetUpcomingMembershipExpires {
+ my $dbh = C4::Context->dbh;
+ 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
($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
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 $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);
return @result;
}
+=head2 AddMember_Opac
+
+=cut
+
sub AddMember_Opac {
my ( %borrower ) = @_;
}
}
+=head2 HasOverdues
+
+=cut
+
sub HasOverdues {
my ( $borrowernumber ) = @_;
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;