3 # Copyright 2000-2003 Katipo Communications
4 # Copyright 2010 BibLibre
5 # Parts Copyright 2010 Catalyst IT
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # Koha is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24 #use warnings; FIXME - Bug 2505
26 use String::Random qw( random_string );
27 use Scalar::Util qw( looks_like_number );
28 use Date::Calc qw/Today check_date Date_to_Days/;
29 use List::MoreUtils qw( uniq );
31 use C4::Log; # logaction
37 use C4::Members::Attributes qw(SearchIdMatchingAttribute UpdateBorrowerAttribute);
38 use C4::NewsChannels; #get slip news
42 use Text::Unaccent qw( unac_string );
43 use Koha::AuthUtils qw(hash_password);
46 use Koha::List::Patron;
48 use Koha::Patron::Categories;
51 our (@ISA,@EXPORT,@EXPORT_OK,$debug);
53 use Module::Load::Conditional qw( can_load );
54 if ( ! can_load( modules => { 'Koha::NorwegianPatronDB' => undef } ) ) {
55 $debug && warn "Unable to load Koha::NorwegianPatronDB";
60 $debug = $ENV{DEBUG} || 0;
68 &GetBorrowersToExpunge
96 C4::Members - Perl Module containing convenience functions for member handling
104 This module contains routines for adding, modifying and deleting members/patrons/borrowers
110 $flags = &patronflags($patron);
112 This function is not exported.
114 The following will be set where applicable:
115 $flags->{CHARGES}->{amount} Amount of debt
116 $flags->{CHARGES}->{noissues} Set if debt amount >$5.00 (or syspref noissuescharge)
117 $flags->{CHARGES}->{message} Message -- deprecated
119 $flags->{CREDITS}->{amount} Amount of credit
120 $flags->{CREDITS}->{message} Message -- deprecated
122 $flags->{ GNA } Patron has no valid address
123 $flags->{ GNA }->{noissues} Set for each GNA
124 $flags->{ GNA }->{message} "Borrower has no valid address" -- deprecated
126 $flags->{ LOST } Patron's card reported lost
127 $flags->{ LOST }->{noissues} Set for each LOST
128 $flags->{ LOST }->{message} Message -- deprecated
130 $flags->{DBARRED} Set if patron debarred, no access
131 $flags->{DBARRED}->{noissues} Set for each DBARRED
132 $flags->{DBARRED}->{message} Message -- deprecated
135 $flags->{ NOTES }->{message} The note itself. NOT deprecated
137 $flags->{ ODUES } Set if patron has overdue books.
138 $flags->{ ODUES }->{message} "Yes" -- deprecated
139 $flags->{ ODUES }->{itemlist} ref-to-array: list of overdue books
140 $flags->{ ODUES }->{itemlisttext} Text list of overdue items -- deprecated
142 $flags->{WAITING} Set if any of patron's reserves are available
143 $flags->{WAITING}->{message} Message -- deprecated
144 $flags->{WAITING}->{itemlist} ref-to-array: list of available items
148 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlist}> is a reference-to-array listing the
149 overdue items. Its elements are references-to-hash, each describing an
150 overdue item. The keys are selected fields from the issues, biblio,
151 biblioitems, and items tables of the Koha database.
153 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlisttext}> is a string giving a text listing of
154 the overdue items, one per line. Deprecated.
156 =item C<$flags-E<gt>{WAITING}-E<gt>{itemlist}> is a reference-to-array listing the
157 available items. Each element is a reference-to-hash whose keys are
158 fields from the reserves table of the Koha database.
162 All the "message" fields that include language generated in this function are deprecated,
163 because such strings belong properly in the display layer.
165 The "message" field that comes from the DB is OK.
169 # TODO: use {anonymous => hashes} instead of a dozen %flaginfo
170 # FIXME rename this function.
171 # DEPRECATED Do not use this subroutine!
174 my ( $patroninformation) = @_;
175 my $dbh=C4::Context->dbh;
176 my $patron = Koha::Patrons->find( $patroninformation->{borrowernumber} );
177 my $account = $patron->account;
178 my $owing = $account->non_issues_charges;
181 my $noissuescharge = C4::Context->preference("noissuescharge") || 5;
182 $flaginfo{'message'} = sprintf 'Patron owes %.02f', $owing;
183 $flaginfo{'amount'} = sprintf "%.02f", $owing;
184 if ( $owing > $noissuescharge && !C4::Context->preference("AllowFineOverride") ) {
185 $flaginfo{'noissues'} = 1;
187 $flags{'CHARGES'} = \%flaginfo;
189 elsif ( ( my $balance = $account->balance ) < 0 ) {
191 $flaginfo{'message'} = sprintf 'Patron has credit of %.02f', -$balance;
192 $flaginfo{'amount'} = sprintf "%.02f", $balance;
193 $flags{'CREDITS'} = \%flaginfo;
196 # Check the debt of the guarntees of this patron
197 my $no_issues_charge_guarantees = C4::Context->preference("NoIssuesChargeGuarantees");
198 $no_issues_charge_guarantees = undef unless looks_like_number( $no_issues_charge_guarantees );
199 if ( defined $no_issues_charge_guarantees ) {
200 my $p = Koha::Patrons->find( $patroninformation->{borrowernumber} );
201 my @guarantees = $p->guarantees();
202 my $guarantees_non_issues_charges;
203 foreach my $g ( @guarantees ) {
204 $guarantees_non_issues_charges += $g->account->non_issues_charges;
207 if ( $guarantees_non_issues_charges > $no_issues_charge_guarantees ) {
209 $flaginfo{'message'} = sprintf 'patron guarantees owe %.02f', $guarantees_non_issues_charges;
210 $flaginfo{'amount'} = $guarantees_non_issues_charges;
211 $flaginfo{'noissues'} = 1 unless C4::Context->preference("allowfineoverride");
212 $flags{'CHARGES_GUARANTEES'} = \%flaginfo;
216 if ( $patroninformation->{'gonenoaddress'}
217 && $patroninformation->{'gonenoaddress'} == 1 )
220 $flaginfo{'message'} = 'Borrower has no valid address.';
221 $flaginfo{'noissues'} = 1;
222 $flags{'GNA'} = \%flaginfo;
224 if ( $patroninformation->{'lost'} && $patroninformation->{'lost'} == 1 ) {
226 $flaginfo{'message'} = 'Borrower\'s card reported lost.';
227 $flaginfo{'noissues'} = 1;
228 $flags{'LOST'} = \%flaginfo;
230 if ( $patroninformation->{'debarred'} && check_date( split( /-/, $patroninformation->{'debarred'} ) ) ) {
231 if ( Date_to_Days(Date::Calc::Today) < Date_to_Days( split( /-/, $patroninformation->{'debarred'} ) ) ) {
233 $flaginfo{'debarredcomment'} = $patroninformation->{'debarredcomment'};
234 $flaginfo{'message'} = $patroninformation->{'debarredcomment'};
235 $flaginfo{'noissues'} = 1;
236 $flaginfo{'dateend'} = $patroninformation->{'debarred'};
237 $flags{'DBARRED'} = \%flaginfo;
240 if ( $patroninformation->{'borrowernotes'}
241 && $patroninformation->{'borrowernotes'} )
244 $flaginfo{'message'} = $patroninformation->{'borrowernotes'};
245 $flags{'NOTES'} = \%flaginfo;
247 my ( $odues, $itemsoverdue ) = C4::Overdues::checkoverdues($patroninformation->{'borrowernumber'});
248 if ( $odues && $odues > 0 ) {
250 $flaginfo{'message'} = "Yes";
251 $flaginfo{'itemlist'} = $itemsoverdue;
252 foreach ( sort { $a->{'date_due'} cmp $b->{'date_due'} }
255 $flaginfo{'itemlisttext'} .=
256 "$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n"; # newline is display layer
258 $flags{'ODUES'} = \%flaginfo;
261 my $waiting_holds = $patron->holds->search({ found => 'W' });
262 my $nowaiting = $waiting_holds->count;
263 if ( $nowaiting > 0 ) {
265 $flaginfo{'message'} = "Reserved items available";
266 $flaginfo{'itemlist'} = $waiting_holds->unblessed;
267 $flags{'WAITING'} = \%flaginfo;
275 my $success = ModMember(borrowernumber => $borrowernumber,
276 [ field => value ]... );
278 Modify borrower's data. All date fields should ALREADY be in ISO format.
281 true on success, or false on failure
288 # trim whitespace from data which has some non-whitespace in it.
289 foreach my $field_name (keys(%data)) {
290 if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
291 $data{$field_name} =~ s/^\s*|\s*$//g;
295 # test to know if you must update or not the borrower password
296 if (exists $data{password}) {
297 if ($data{password} eq '****' or $data{password} eq '') {
298 delete $data{password};
300 if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
301 warn "C4::Members::ModMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
302 # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
303 Koha::NorwegianPatronDB::NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
305 $data{password} = hash_password($data{password});
309 my $old_categorycode = Koha::Patrons->find( $data{borrowernumber} )->categorycode;
311 # get only the columns of a borrower
312 my $schema = Koha::Database->new()->schema;
313 my @columns = $schema->source('Borrower')->columns;
314 my $new_borrower = { map { join(' ', @columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) };
316 $new_borrower->{dateofbirth} ||= undef if exists $new_borrower->{dateofbirth};
317 $new_borrower->{dateenrolled} ||= undef if exists $new_borrower->{dateenrolled};
318 $new_borrower->{dateexpiry} ||= undef if exists $new_borrower->{dateexpiry};
319 $new_borrower->{debarred} ||= undef if exists $new_borrower->{debarred};
320 $new_borrower->{sms_provider_id} ||= undef if exists $new_borrower->{sms_provider_id};
321 $new_borrower->{guarantorid} ||= undef if exists $new_borrower->{guarantorid};
323 my $patron = Koha::Patrons->find( $new_borrower->{borrowernumber} );
325 my $borrowers_log = C4::Context->preference("BorrowersLog");
326 if ( $borrowers_log && $patron->cardnumber ne $new_borrower->{cardnumber} )
331 $data{'borrowernumber'},
334 cardnumber_replaced => {
335 previous_cardnumber => $patron->cardnumber,
336 new_cardnumber => $new_borrower->{cardnumber},
339 { utf8 => 1, pretty => 1 }
344 delete $new_borrower->{userid} if exists $new_borrower->{userid} and not $new_borrower->{userid};
346 my $execute_success = $patron->store if $patron->set($new_borrower);
348 if ($execute_success) { # only proceed if the update was a success
349 # If the patron changes to a category with enrollment fee, we add a fee
350 if ( $data{categorycode} and $data{categorycode} ne $old_categorycode ) {
351 if ( C4::Context->preference('FeeOnChangePatronCategory') ) {
352 $patron->add_enrolment_fee_if_needed;
356 # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
357 # cronjob will use for syncing with NL
358 if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
359 warn "C4::Members::ModMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
360 my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
361 'synctype' => 'norwegianpatrondb',
362 'borrowernumber' => $data{'borrowernumber'}
364 # Do not set to "edited" if syncstatus is "new". We need to sync as new before
365 # we can sync as changed. And the "new sync" will pick up all changes since
366 # the patron was created anyway.
367 if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) {
368 $borrowersync->update( { 'syncstatus' => 'edited' } );
370 # Set the value of 'sync'
371 $borrowersync->update( { 'sync' => $data{'sync'} } );
372 # Try to do the live sync
373 Koha::NorwegianPatronDB::NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
376 logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if $borrowers_log;
378 return $execute_success;
383 $borrowernumber = &AddMember(%borrower);
385 insert new borrower into table
387 (%borrower keys are database columns. Database columns could be
388 different in different versions. Please look into database for correct
391 Returns the borrowernumber upon success
393 Returns as undef upon any db error without further processing
400 my $dbh = C4::Context->dbh;
401 my $schema = Koha::Database->new()->schema;
403 my $category = Koha::Patron::Categories->find( $data{categorycode} );
405 Koha::Exceptions::Object::FKConstraint->throw(
406 broken_fk => 'categorycode',
407 value => $data{categorycode},
411 # trim whitespace from data which has some non-whitespace in it.
412 foreach my $field_name (keys(%data)) {
413 if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
414 $data{$field_name} =~ s/^\s*|\s*$//g;
418 my $p = Koha::Patron->new( { userid => $data{userid}, firstname => $data{firstname}, surname => $data{surname} } );
419 # generate a proper login if none provided
420 $data{'userid'} = $p->generate_userid
421 if ( $data{'userid'} eq '' || ! $p->has_valid_userid );
423 # add expiration date if it isn't already there
424 $data{dateexpiry} ||= $category->get_expiry_date;
426 # add enrollment date if it isn't already there
427 unless ( $data{'dateenrolled'} ) {
428 $data{'dateenrolled'} = output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } );
431 if ( C4::Context->preference("autoMemberNum") ) {
432 if ( not exists $data{cardnumber} or not defined $data{cardnumber} or $data{cardnumber} eq '' ) {
433 $data{cardnumber} = fixup_cardnumber( $data{cardnumber} );
438 $category->default_privacy() eq 'default' ? 1
439 : $category->default_privacy() eq 'never' ? 2
440 : $category->default_privacy() eq 'forever' ? 0
443 $data{'privacy_guarantor_checkouts'} = 0 unless defined( $data{'privacy_guarantor_checkouts'} );
445 # Make a copy of the plain text password for later use
446 my $plain_text_password = $data{'password'};
448 # create a disabled account if no password provided
449 $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
451 # we don't want invalid dates in the db (mysql has a bad habit of inserting 0000-00-00
452 $data{'dateofbirth'} = undef if ( not $data{'dateofbirth'} );
453 $data{'debarred'} = undef if ( not $data{'debarred'} );
454 $data{'sms_provider_id'} = undef if ( not $data{'sms_provider_id'} );
455 $data{'guarantorid'} = undef if ( not $data{'guarantorid'} );
457 # get only the columns of Borrower
458 # FIXME Do we really need this check?
459 my @columns = $schema->source('Borrower')->columns;
460 my $new_member = { map { join(' ',@columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) } ;
462 delete $new_member->{borrowernumber};
464 my $patron = Koha::Patron->new( $new_member )->store;
465 $data{borrowernumber} = $patron->borrowernumber;
467 # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
468 # cronjob will use for syncing with NL
469 if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
470 warn "C4::Members::AddMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
471 Koha::Database->new->schema->resultset('BorrowerSync')->create({
472 'borrowernumber' => $data{'borrowernumber'},
473 'synctype' => 'norwegianpatrondb',
475 'syncstatus' => 'new',
476 'hashed_pin' => Koha::NorwegianPatronDB::NLEncryptPIN( $plain_text_password ),
480 logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
482 $patron->add_enrolment_fee_if_needed;
484 return $data{borrowernumber};
487 =head2 fixup_cardnumber
489 Warning: The caller is responsible for locking the members table in write
490 mode, to avoid database corruption.
494 sub fixup_cardnumber {
495 my ($cardnumber) = @_;
496 my $autonumber_members = C4::Context->boolean_preference('autoMemberNum') || 0;
498 # Find out whether member numbers should be generated
499 # automatically. Should be either "1" or something else.
500 # Defaults to "0", which is interpreted as "no".
502 ($autonumber_members) or return $cardnumber;
503 my $dbh = C4::Context->dbh;
505 my $sth = $dbh->prepare(
506 'SELECT MAX( CAST( cardnumber AS SIGNED ) ) FROM borrowers WHERE cardnumber REGEXP "^-?[0-9]+$"'
509 my ($result) = $sth->fetchrow;
515 $issues = &GetAllIssues($borrowernumber, $sortkey, $limit);
517 Looks up what the patron with the given borrowernumber has borrowed,
518 and sorts the results.
520 C<$sortkey> is the name of a field on which to sort the results. This
521 should be the name of a field in the C<issues>, C<biblio>,
522 C<biblioitems>, or C<items> table in the Koha database.
524 C<$limit> is the maximum number of results to return.
526 C<&GetAllIssues> an arrayref, C<$issues>, of hashrefs, the keys of which
527 are the fields from the C<issues>, C<biblio>, C<biblioitems>, and
528 C<items> tables of the Koha database.
534 my ( $borrowernumber, $order, $limit ) = @_;
536 return unless $borrowernumber;
537 $order = 'date_due desc' unless $order;
539 my $dbh = C4::Context->dbh;
541 'SELECT *, issues.timestamp as issuestimestamp, issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp
543 LEFT JOIN items on items.itemnumber=issues.itemnumber
544 LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
545 LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
546 WHERE borrowernumber=?
548 SELECT *, old_issues.timestamp as issuestimestamp, old_issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp
550 LEFT JOIN items on items.itemnumber=old_issues.itemnumber
551 LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
552 LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
553 WHERE borrowernumber=? AND old_issues.itemnumber IS NOT NULL
556 $query .= " limit $limit";
559 my $sth = $dbh->prepare($query);
560 $sth->execute( $borrowernumber, $borrowernumber );
561 return $sth->fetchall_arrayref( {} );
564 sub checkcardnumber {
565 my ( $cardnumber, $borrowernumber ) = @_;
567 # If cardnumber is null, we assume they're allowed.
568 return 0 unless defined $cardnumber;
570 my $dbh = C4::Context->dbh;
571 my $query = "SELECT * FROM borrowers WHERE cardnumber=?";
572 $query .= " AND borrowernumber <> ?" if ($borrowernumber);
573 my $sth = $dbh->prepare($query);
576 ( $borrowernumber ? $borrowernumber : () )
579 return 1 if $sth->fetchrow_hashref;
581 my ( $min_length, $max_length ) = get_cardnumber_length();
583 if length $cardnumber > $max_length
584 or length $cardnumber < $min_length;
589 =head2 get_cardnumber_length
591 my ($min, $max) = C4::Members::get_cardnumber_length()
593 Returns the minimum and maximum length for patron cardnumbers as
594 determined by the CardnumberLength system preference, the
595 BorrowerMandatoryField system preference, and the width of the
600 sub get_cardnumber_length {
601 my $borrower = Koha::Schema->resultset('Borrower');
602 my $field_size = $borrower->result_source->column_info('cardnumber')->{size};
603 my ( $min, $max ) = ( 0, $field_size ); # borrowers.cardnumber is a nullable varchar(20)
604 $min = 1 if C4::Context->preference('BorrowerMandatoryField') =~ /cardnumber/;
605 if ( my $cardnumber_length = C4::Context->preference('CardnumberLength') ) {
606 # Is integer and length match
607 if ( $cardnumber_length =~ m|^\d+$| ) {
608 $min = $max = $cardnumber_length
609 if $cardnumber_length >= $min
610 and $cardnumber_length <= $max;
612 # Else assuming it is a range
613 elsif ( $cardnumber_length =~ m|(\d*),(\d*)| ) {
614 $min = $1 if $1 and $min < $1;
615 $max = $2 if $2 and $max > $2;
619 $min = $max if $min > $max;
620 return ( $min, $max );
623 =head2 GetBorrowersToExpunge
625 $borrowers = &GetBorrowersToExpunge(
626 not_borrowed_since => $not_borrowed_since,
627 expired_before => $expired_before,
628 category_code => $category_code,
629 patron_list_id => $patron_list_id,
630 branchcode => $branchcode
633 This function get all borrowers based on the given criteria.
637 sub GetBorrowersToExpunge {
640 my $filterdate = $params->{'not_borrowed_since'};
641 my $filterexpiry = $params->{'expired_before'};
642 my $filterlastseen = $params->{'last_seen'};
643 my $filtercategory = $params->{'category_code'};
644 my $filterbranch = $params->{'branchcode'} ||
645 ((C4::Context->preference('IndependentBranches')
646 && C4::Context->userenv
647 && !C4::Context->IsSuperLibrarian()
648 && C4::Context->userenv->{branch})
649 ? C4::Context->userenv->{branch}
651 my $filterpatronlist = $params->{'patron_list_id'};
653 my $dbh = C4::Context->dbh;
657 SELECT borrowers.borrowernumber,
658 MAX(old_issues.timestamp) AS latestissue,
659 MAX(issues.timestamp) AS currentissue
661 JOIN categories USING (categorycode)
665 WHERE guarantorid IS NOT NULL
667 ) as tmp ON borrowers.borrowernumber=tmp.guarantorid
668 LEFT JOIN old_issues USING (borrowernumber)
669 LEFT JOIN issues USING (borrowernumber)|;
670 if ( $filterpatronlist ){
671 $query .= q| LEFT JOIN patron_list_patrons USING (borrowernumber)|;
673 $query .= q| WHERE category_type <> 'S'
674 AND tmp.guarantorid IS NULL
677 if ( $filterbranch && $filterbranch ne "" ) {
678 $query.= " AND borrowers.branchcode = ? ";
679 push( @query_params, $filterbranch );
681 if ( $filterexpiry ) {
682 $query .= " AND dateexpiry < ? ";
683 push( @query_params, $filterexpiry );
685 if ( $filterlastseen ) {
686 $query .= ' AND lastseen < ? ';
687 push @query_params, $filterlastseen;
689 if ( $filtercategory ) {
690 $query .= " AND categorycode = ? ";
691 push( @query_params, $filtercategory );
693 if ( $filterpatronlist ){
694 $query.=" AND patron_list_id = ? ";
695 push( @query_params, $filterpatronlist );
697 $query .= " GROUP BY borrowers.borrowernumber";
699 ) xxx WHERE currentissue IS NULL|;
701 $query.=" AND ( latestissue < ? OR latestissue IS NULL ) ";
702 push @query_params,$filterdate;
705 warn $query if $debug;
707 my $sth = $dbh->prepare($query);
708 if (scalar(@query_params)>0){
709 $sth->execute(@query_params);
716 while ( my $data = $sth->fetchrow_hashref ) {
717 push @results, $data;
724 IssueSlip($branchcode, $borrowernumber, $quickslip)
726 Returns letter hash ( see C4::Letters::GetPreparedLetter )
728 $quickslip is boolean, to indicate whether we want a quick slip
730 IssueSlip populates ISSUESLIP and ISSUEQSLIP, and will make the following expansions:
766 NOTE: Fields from tables issues, items, biblio and biblioitems are available
771 my ($branch, $borrowernumber, $quickslip) = @_;
773 # FIXME Check callers before removing this statement
774 #return unless $borrowernumber;
776 my $patron = Koha::Patrons->find( $borrowernumber );
777 return unless $patron;
779 my $pending_checkouts = $patron->pending_checkouts; # Should be $patron->checkouts->pending?
781 my ($letter_code, %repeat, %loops);
783 my $today_start = dt_from_string->set( hour => 0, minute => 0, second => 0 );
784 my $today_end = dt_from_string->set( hour => 23, minute => 59, second => 0 );
785 $today_start = Koha::Database->new->schema->storage->datetime_parser->format_datetime( $today_start );
786 $today_end = Koha::Database->new->schema->storage->datetime_parser->format_datetime( $today_end );
787 $letter_code = 'ISSUEQSLIP';
789 # issue date or lastreneweddate is today
790 my $todays_checkouts = $pending_checkouts->search(
794 '>=' => $today_start,
798 { '>=' => $today_start, '<=' => $today_end, }
803 while ( my $c = $todays_checkouts->next ) {
804 my $all = $c->unblessed_all_relateds;
814 checkedout => \@checkouts, # Historical syntax
817 issues => [ map { $_->{issues}{itemnumber} } @checkouts ], # TT syntax
821 my $today = Koha::Database->new->schema->storage->datetime_parser->format_datetime( dt_from_string );
822 # Checkouts due in the future
823 my $checkouts = $pending_checkouts->search({ date_due => { '>' => $today } });
824 my @checkouts; my @overdues;
825 while ( my $c = $checkouts->next ) {
826 my $all = $c->unblessed_all_relateds;
835 # Checkouts due in the past are overdues
836 my $overdues = $pending_checkouts->search({ date_due => { '<=' => $today } });
837 while ( my $o = $overdues->next ) {
838 my $all = $o->unblessed_all_relateds;
846 my $news = GetNewsToDisplay( "slip", $branch );
848 $_->{'timestamp'} = $_->{'newdate'};
851 $letter_code = 'ISSUESLIP';
853 checkedout => \@checkouts,
854 overdue => \@overdues,
858 issues => [ map { $_->{issues}{itemnumber} } @checkouts ],
859 overdues => [ map { $_->{issues}{itemnumber} } @overdues ],
860 opac_news => [ map { $_->{opac_news}{idnew} } @news ],
864 return C4::Letters::GetPreparedLetter (
865 module => 'circulation',
866 letter_code => $letter_code,
867 branchcode => $branch,
868 lang => $patron->lang,
870 'branches' => $branch,
871 'borrowers' => $borrowernumber,
878 =head2 AddMember_Auto
883 my ( %borrower ) = @_;
885 $borrower{'cardnumber'} ||= fixup_cardnumber();
887 $borrower{'borrowernumber'} = AddMember(%borrower);
889 return ( %borrower );
892 =head2 AddMember_Opac
897 my ( %borrower ) = @_;
899 $borrower{'categorycode'} //= C4::Context->preference('PatronSelfRegistrationDefaultCategory');
900 if (not defined $borrower{'password'}){
901 my $sr = new String::Random;
902 $sr->{'A'} = [ 'A'..'Z', 'a'..'z' ];
903 my $password = $sr->randpattern("AAAAAAAAAA");
904 $borrower{'password'} = $password;
907 %borrower = AddMember_Auto(%borrower);
909 return ( $borrower{'borrowernumber'}, $borrower{'password'} );
912 =head2 DeleteExpiredOpacRegistrations
914 Delete accounts that haven't been upgraded from the 'temporary' category
915 Returns the number of removed patrons
919 sub DeleteExpiredOpacRegistrations {
921 my $delay = C4::Context->preference('PatronSelfRegistrationExpireTemporaryAccountsDelay');
922 my $category_code = C4::Context->preference('PatronSelfRegistrationDefaultCategory');
924 return 0 if not $category_code or not defined $delay or $delay eq q||;
927 SELECT borrowernumber
929 WHERE categorycode = ? AND DATEDIFF( NOW(), dateenrolled ) > ? |;
931 my $dbh = C4::Context->dbh;
932 my $sth = $dbh->prepare($query);
933 $sth->execute( $category_code, $delay );
935 while ( my ($borrowernumber) = $sth->fetchrow_array() ) {
936 Koha::Patrons->find($borrowernumber)->delete;
942 =head2 DeleteUnverifiedOpacRegistrations
944 Delete all unverified self registrations in borrower_modifications,
945 older than the specified number of days.
949 sub DeleteUnverifiedOpacRegistrations {
951 my $dbh = C4::Context->dbh;
953 DELETE FROM borrower_modifications
954 WHERE borrowernumber = 0 AND DATEDIFF( NOW(), timestamp ) > ?|;
955 my $cnt=$dbh->do($sql, undef, ($days) );
956 return $cnt eq '0E0'? 0: $cnt;
959 END { } # module clean-up code here (global destructor)