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;
50 our (@ISA,@EXPORT,@EXPORT_OK,$debug);
52 use Module::Load::Conditional qw( can_load );
53 if ( ! can_load( modules => { 'Koha::NorwegianPatronDB' => undef } ) ) {
54 $debug && warn "Unable to load Koha::NorwegianPatronDB";
59 $debug = $ENV{DEBUG} || 0;
67 &GetBorrowersToExpunge
95 C4::Members - Perl Module containing convenience functions for member handling
103 This module contains routines for adding, modifying and deleting members/patrons/borrowers
109 $flags = &patronflags($patron);
111 This function is not exported.
113 The following will be set where applicable:
114 $flags->{CHARGES}->{amount} Amount of debt
115 $flags->{CHARGES}->{noissues} Set if debt amount >$5.00 (or syspref noissuescharge)
116 $flags->{CHARGES}->{message} Message -- deprecated
118 $flags->{CREDITS}->{amount} Amount of credit
119 $flags->{CREDITS}->{message} Message -- deprecated
121 $flags->{ GNA } Patron has no valid address
122 $flags->{ GNA }->{noissues} Set for each GNA
123 $flags->{ GNA }->{message} "Borrower has no valid address" -- deprecated
125 $flags->{ LOST } Patron's card reported lost
126 $flags->{ LOST }->{noissues} Set for each LOST
127 $flags->{ LOST }->{message} Message -- deprecated
129 $flags->{DBARRED} Set if patron debarred, no access
130 $flags->{DBARRED}->{noissues} Set for each DBARRED
131 $flags->{DBARRED}->{message} Message -- deprecated
134 $flags->{ NOTES }->{message} The note itself. NOT deprecated
136 $flags->{ ODUES } Set if patron has overdue books.
137 $flags->{ ODUES }->{message} "Yes" -- deprecated
138 $flags->{ ODUES }->{itemlist} ref-to-array: list of overdue books
139 $flags->{ ODUES }->{itemlisttext} Text list of overdue items -- deprecated
141 $flags->{WAITING} Set if any of patron's reserves are available
142 $flags->{WAITING}->{message} Message -- deprecated
143 $flags->{WAITING}->{itemlist} ref-to-array: list of available items
147 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlist}> is a reference-to-array listing the
148 overdue items. Its elements are references-to-hash, each describing an
149 overdue item. The keys are selected fields from the issues, biblio,
150 biblioitems, and items tables of the Koha database.
152 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlisttext}> is a string giving a text listing of
153 the overdue items, one per line. Deprecated.
155 =item C<$flags-E<gt>{WAITING}-E<gt>{itemlist}> is a reference-to-array listing the
156 available items. Each element is a reference-to-hash whose keys are
157 fields from the reserves table of the Koha database.
161 All the "message" fields that include language generated in this function are deprecated,
162 because such strings belong properly in the display layer.
164 The "message" field that comes from the DB is OK.
168 # TODO: use {anonymous => hashes} instead of a dozen %flaginfo
169 # FIXME rename this function.
170 # DEPRECATED Do not use this subroutine!
173 my ( $patroninformation) = @_;
174 my $dbh=C4::Context->dbh;
175 my $patron = Koha::Patrons->find( $patroninformation->{borrowernumber} );
176 my $account = $patron->account;
177 my $owing = $account->non_issues_charges;
180 my $noissuescharge = C4::Context->preference("noissuescharge") || 5;
181 $flaginfo{'message'} = sprintf 'Patron owes %.02f', $owing;
182 $flaginfo{'amount'} = sprintf "%.02f", $owing;
183 if ( $owing > $noissuescharge && !C4::Context->preference("AllowFineOverride") ) {
184 $flaginfo{'noissues'} = 1;
186 $flags{'CHARGES'} = \%flaginfo;
188 elsif ( ( my $balance = $account->balance ) < 0 ) {
190 $flaginfo{'message'} = sprintf 'Patron has credit of %.02f', -$balance;
191 $flaginfo{'amount'} = sprintf "%.02f", $balance;
192 $flags{'CREDITS'} = \%flaginfo;
195 # Check the debt of the guarntees of this patron
196 my $no_issues_charge_guarantees = C4::Context->preference("NoIssuesChargeGuarantees");
197 $no_issues_charge_guarantees = undef unless looks_like_number( $no_issues_charge_guarantees );
198 if ( defined $no_issues_charge_guarantees ) {
199 my $p = Koha::Patrons->find( $patroninformation->{borrowernumber} );
200 my @guarantees = $p->guarantees();
201 my $guarantees_non_issues_charges;
202 foreach my $g ( @guarantees ) {
203 $guarantees_non_issues_charges += $g->account->non_issues_charges;
206 if ( $guarantees_non_issues_charges > $no_issues_charge_guarantees ) {
208 $flaginfo{'message'} = sprintf 'patron guarantees owe %.02f', $guarantees_non_issues_charges;
209 $flaginfo{'amount'} = $guarantees_non_issues_charges;
210 $flaginfo{'noissues'} = 1 unless C4::Context->preference("allowfineoverride");
211 $flags{'CHARGES_GUARANTEES'} = \%flaginfo;
215 if ( $patroninformation->{'gonenoaddress'}
216 && $patroninformation->{'gonenoaddress'} == 1 )
219 $flaginfo{'message'} = 'Borrower has no valid address.';
220 $flaginfo{'noissues'} = 1;
221 $flags{'GNA'} = \%flaginfo;
223 if ( $patroninformation->{'lost'} && $patroninformation->{'lost'} == 1 ) {
225 $flaginfo{'message'} = 'Borrower\'s card reported lost.';
226 $flaginfo{'noissues'} = 1;
227 $flags{'LOST'} = \%flaginfo;
229 if ( $patroninformation->{'debarred'} && check_date( split( /-/, $patroninformation->{'debarred'} ) ) ) {
230 if ( Date_to_Days(Date::Calc::Today) < Date_to_Days( split( /-/, $patroninformation->{'debarred'} ) ) ) {
232 $flaginfo{'debarredcomment'} = $patroninformation->{'debarredcomment'};
233 $flaginfo{'message'} = $patroninformation->{'debarredcomment'};
234 $flaginfo{'noissues'} = 1;
235 $flaginfo{'dateend'} = $patroninformation->{'debarred'};
236 $flags{'DBARRED'} = \%flaginfo;
239 if ( $patroninformation->{'borrowernotes'}
240 && $patroninformation->{'borrowernotes'} )
243 $flaginfo{'message'} = $patroninformation->{'borrowernotes'};
244 $flags{'NOTES'} = \%flaginfo;
246 my ( $odues, $itemsoverdue ) = C4::Overdues::checkoverdues($patroninformation->{'borrowernumber'});
247 if ( $odues && $odues > 0 ) {
249 $flaginfo{'message'} = "Yes";
250 $flaginfo{'itemlist'} = $itemsoverdue;
251 foreach ( sort { $a->{'date_due'} cmp $b->{'date_due'} }
254 $flaginfo{'itemlisttext'} .=
255 "$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n"; # newline is display layer
257 $flags{'ODUES'} = \%flaginfo;
260 my $waiting_holds = $patron->holds->search({ found => 'W' });
261 my $nowaiting = $waiting_holds->count;
262 if ( $nowaiting > 0 ) {
264 $flaginfo{'message'} = "Reserved items available";
265 $flaginfo{'itemlist'} = $waiting_holds->unblessed;
266 $flags{'WAITING'} = \%flaginfo;
274 my $success = ModMember(borrowernumber => $borrowernumber,
275 [ field => value ]... );
277 Modify borrower's data. All date fields should ALREADY be in ISO format.
280 true on success, or false on failure
287 # trim whitespace from data which has some non-whitespace in it.
288 foreach my $field_name (keys(%data)) {
289 if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
290 $data{$field_name} =~ s/^\s*|\s*$//g;
294 # test to know if you must update or not the borrower password
295 if (exists $data{password}) {
296 if ($data{password} eq '****' or $data{password} eq '') {
297 delete $data{password};
299 if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
300 warn "C4::Members::ModMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
301 # Update the hashed PIN in borrower_sync.hashed_pin, before Koha hashes it
302 Koha::NorwegianPatronDB::NLUpdateHashedPIN( $data{'borrowernumber'}, $data{password} );
304 $data{password} = hash_password($data{password});
308 my $old_categorycode = Koha::Patrons->find( $data{borrowernumber} )->categorycode;
310 # get only the columns of a borrower
311 my $schema = Koha::Database->new()->schema;
312 my @columns = $schema->source('Borrower')->columns;
313 my $new_borrower = { map { join(' ', @columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) };
315 $new_borrower->{dateofbirth} ||= undef if exists $new_borrower->{dateofbirth};
316 $new_borrower->{dateenrolled} ||= undef if exists $new_borrower->{dateenrolled};
317 $new_borrower->{dateexpiry} ||= undef if exists $new_borrower->{dateexpiry};
318 $new_borrower->{debarred} ||= undef if exists $new_borrower->{debarred};
319 $new_borrower->{sms_provider_id} ||= undef if exists $new_borrower->{sms_provider_id};
320 $new_borrower->{guarantorid} ||= undef if exists $new_borrower->{guarantorid};
322 my $patron = Koha::Patrons->find( $new_borrower->{borrowernumber} );
324 my $borrowers_log = C4::Context->preference("BorrowersLog");
325 if ( $borrowers_log && $patron->cardnumber ne $new_borrower->{cardnumber} )
330 $data{'borrowernumber'},
333 cardnumber_replaced => {
334 previous_cardnumber => $patron->cardnumber,
335 new_cardnumber => $new_borrower->{cardnumber},
338 { utf8 => 1, pretty => 1 }
343 delete $new_borrower->{userid} if exists $new_borrower->{userid} and not $new_borrower->{userid};
345 my $execute_success = $patron->store if $patron->set($new_borrower);
347 if ($execute_success) { # only proceed if the update was a success
348 # If the patron changes to a category with enrollment fee, we add a fee
349 if ( $data{categorycode} and $data{categorycode} ne $old_categorycode ) {
350 if ( C4::Context->preference('FeeOnChangePatronCategory') ) {
351 $patron->add_enrolment_fee_if_needed;
355 # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
356 # cronjob will use for syncing with NL
357 if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
358 warn "C4::Members::ModMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
359 my $borrowersync = Koha::Database->new->schema->resultset('BorrowerSync')->find({
360 'synctype' => 'norwegianpatrondb',
361 'borrowernumber' => $data{'borrowernumber'}
363 # Do not set to "edited" if syncstatus is "new". We need to sync as new before
364 # we can sync as changed. And the "new sync" will pick up all changes since
365 # the patron was created anyway.
366 if ( $borrowersync->syncstatus ne 'new' && $borrowersync->syncstatus ne 'delete' ) {
367 $borrowersync->update( { 'syncstatus' => 'edited' } );
369 # Set the value of 'sync'
370 $borrowersync->update( { 'sync' => $data{'sync'} } );
371 # Try to do the live sync
372 Koha::NorwegianPatronDB::NLSync({ 'borrowernumber' => $data{'borrowernumber'} });
375 logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if $borrowers_log;
377 return $execute_success;
382 $borrowernumber = &AddMember(%borrower);
384 insert new borrower into table
386 (%borrower keys are database columns. Database columns could be
387 different in different versions. Please look into database for correct
390 Returns the borrowernumber upon success
392 Returns as undef upon any db error without further processing
399 my $dbh = C4::Context->dbh;
400 my $schema = Koha::Database->new()->schema;
402 my $category = Koha::Patron::Categories->find( $data{categorycode} );
404 Koha::Exceptions::Object::FKConstraint->throw(
405 broken_fk => 'categorycode',
406 value => $data{categorycode},
410 # trim whitespace from data which has some non-whitespace in it.
411 foreach my $field_name (keys(%data)) {
412 if ( defined $data{$field_name} && $data{$field_name} =~ /\S/ ) {
413 $data{$field_name} =~ s/^\s*|\s*$//g;
417 my $p = Koha::Patron->new( { userid => $data{userid}, firstname => $data{firstname}, surname => $data{surname} } );
418 # generate a proper login if none provided
419 $data{'userid'} = $p->generate_userid
420 if ( $data{'userid'} eq '' || ! $p->has_valid_userid );
422 # add expiration date if it isn't already there
423 $data{dateexpiry} ||= $category->get_expiry_date;
425 # add enrollment date if it isn't already there
426 unless ( $data{'dateenrolled'} ) {
427 $data{'dateenrolled'} = output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } );
430 if ( C4::Context->preference("autoMemberNum") ) {
431 if ( not exists $data{cardnumber} or not defined $data{cardnumber} or $data{cardnumber} eq '' ) {
432 $data{cardnumber} = fixup_cardnumber( $data{cardnumber} );
437 $category->default_privacy() eq 'default' ? 1
438 : $category->default_privacy() eq 'never' ? 2
439 : $category->default_privacy() eq 'forever' ? 0
442 $data{'privacy_guarantor_checkouts'} = 0 unless defined( $data{'privacy_guarantor_checkouts'} );
444 # Make a copy of the plain text password for later use
445 my $plain_text_password = $data{'password'};
447 # create a disabled account if no password provided
448 $data{'password'} = ($data{'password'})? hash_password($data{'password'}) : '!';
450 # we don't want invalid dates in the db (mysql has a bad habit of inserting 0000-00-00
451 $data{'dateofbirth'} = undef if ( not $data{'dateofbirth'} );
452 $data{'debarred'} = undef if ( not $data{'debarred'} );
453 $data{'sms_provider_id'} = undef if ( not $data{'sms_provider_id'} );
454 $data{'guarantorid'} = undef if ( not $data{'guarantorid'} );
456 # get only the columns of Borrower
457 # FIXME Do we really need this check?
458 my @columns = $schema->source('Borrower')->columns;
459 my $new_member = { map { join(' ',@columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) } ;
461 delete $new_member->{borrowernumber};
463 my $patron = Koha::Patron->new( $new_member )->store;
464 $data{borrowernumber} = $patron->borrowernumber;
466 # If NorwegianPatronDBEnable is enabled, we set syncstatus to something that a
467 # cronjob will use for syncing with NL
468 if ( exists $data{'borrowernumber'} && C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
469 warn "C4::Members::AddMember - NorwegianPatronDB hooks will be deprecated as of 18.11.0\n";
470 Koha::Database->new->schema->resultset('BorrowerSync')->create({
471 'borrowernumber' => $data{'borrowernumber'},
472 'synctype' => 'norwegianpatrondb',
474 'syncstatus' => 'new',
475 'hashed_pin' => Koha::NorwegianPatronDB::NLEncryptPIN( $plain_text_password ),
479 logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
481 $patron->add_enrolment_fee_if_needed;
483 return $data{borrowernumber};
486 =head2 fixup_cardnumber
488 Warning: The caller is responsible for locking the members table in write
489 mode, to avoid database corruption.
493 sub fixup_cardnumber {
494 my ($cardnumber) = @_;
495 my $autonumber_members = C4::Context->boolean_preference('autoMemberNum') || 0;
497 # Find out whether member numbers should be generated
498 # automatically. Should be either "1" or something else.
499 # Defaults to "0", which is interpreted as "no".
501 ($autonumber_members) or return $cardnumber;
502 my $dbh = C4::Context->dbh;
504 my $sth = $dbh->prepare(
505 'SELECT MAX( CAST( cardnumber AS SIGNED ) ) FROM borrowers WHERE cardnumber REGEXP "^-?[0-9]+$"'
508 my ($result) = $sth->fetchrow;
514 $issues = &GetAllIssues($borrowernumber, $sortkey, $limit);
516 Looks up what the patron with the given borrowernumber has borrowed,
517 and sorts the results.
519 C<$sortkey> is the name of a field on which to sort the results. This
520 should be the name of a field in the C<issues>, C<biblio>,
521 C<biblioitems>, or C<items> table in the Koha database.
523 C<$limit> is the maximum number of results to return.
525 C<&GetAllIssues> an arrayref, C<$issues>, of hashrefs, the keys of which
526 are the fields from the C<issues>, C<biblio>, C<biblioitems>, and
527 C<items> tables of the Koha database.
533 my ( $borrowernumber, $order, $limit ) = @_;
535 return unless $borrowernumber;
536 $order = 'date_due desc' unless $order;
538 my $dbh = C4::Context->dbh;
540 'SELECT *, issues.timestamp as issuestimestamp, issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp
542 LEFT JOIN items on items.itemnumber=issues.itemnumber
543 LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
544 LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
545 WHERE borrowernumber=?
547 SELECT *, old_issues.timestamp as issuestimestamp, old_issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp
549 LEFT JOIN items on items.itemnumber=old_issues.itemnumber
550 LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
551 LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
552 WHERE borrowernumber=? AND old_issues.itemnumber IS NOT NULL
555 $query .= " limit $limit";
558 my $sth = $dbh->prepare($query);
559 $sth->execute( $borrowernumber, $borrowernumber );
560 return $sth->fetchall_arrayref( {} );
563 sub checkcardnumber {
564 my ( $cardnumber, $borrowernumber ) = @_;
566 # If cardnumber is null, we assume they're allowed.
567 return 0 unless defined $cardnumber;
569 my $dbh = C4::Context->dbh;
570 my $query = "SELECT * FROM borrowers WHERE cardnumber=?";
571 $query .= " AND borrowernumber <> ?" if ($borrowernumber);
572 my $sth = $dbh->prepare($query);
575 ( $borrowernumber ? $borrowernumber : () )
578 return 1 if $sth->fetchrow_hashref;
580 my ( $min_length, $max_length ) = get_cardnumber_length();
582 if length $cardnumber > $max_length
583 or length $cardnumber < $min_length;
588 =head2 get_cardnumber_length
590 my ($min, $max) = C4::Members::get_cardnumber_length()
592 Returns the minimum and maximum length for patron cardnumbers as
593 determined by the CardnumberLength system preference, the
594 BorrowerMandatoryField system preference, and the width of the
599 sub get_cardnumber_length {
600 my $borrower = Koha::Database->new->schema->resultset('Borrower');
601 my $field_size = $borrower->result_source->column_info('cardnumber')->{size};
602 my ( $min, $max ) = ( 0, $field_size ); # borrowers.cardnumber is a nullable varchar(20)
603 $min = 1 if C4::Context->preference('BorrowerMandatoryField') =~ /cardnumber/;
604 if ( my $cardnumber_length = C4::Context->preference('CardnumberLength') ) {
605 # Is integer and length match
606 if ( $cardnumber_length =~ m|^\d+$| ) {
607 $min = $max = $cardnumber_length
608 if $cardnumber_length >= $min
609 and $cardnumber_length <= $max;
611 # Else assuming it is a range
612 elsif ( $cardnumber_length =~ m|(\d*),(\d*)| ) {
613 $min = $1 if $1 and $min < $1;
614 $max = $2 if $2 and $max > $2;
618 $min = $max if $min > $max;
619 return ( $min, $max );
622 =head2 GetBorrowersToExpunge
624 $borrowers = &GetBorrowersToExpunge(
625 not_borrowed_since => $not_borrowed_since,
626 expired_before => $expired_before,
627 category_code => $category_code,
628 patron_list_id => $patron_list_id,
629 branchcode => $branchcode
632 This function get all borrowers based on the given criteria.
636 sub GetBorrowersToExpunge {
639 my $filterdate = $params->{'not_borrowed_since'};
640 my $filterexpiry = $params->{'expired_before'};
641 my $filterlastseen = $params->{'last_seen'};
642 my $filtercategory = $params->{'category_code'};
643 my $filterbranch = $params->{'branchcode'} ||
644 ((C4::Context->preference('IndependentBranches')
645 && C4::Context->userenv
646 && !C4::Context->IsSuperLibrarian()
647 && C4::Context->userenv->{branch})
648 ? C4::Context->userenv->{branch}
650 my $filterpatronlist = $params->{'patron_list_id'};
652 my $dbh = C4::Context->dbh;
656 SELECT borrowers.borrowernumber,
657 MAX(old_issues.timestamp) AS latestissue,
658 MAX(issues.timestamp) AS currentissue
660 JOIN categories USING (categorycode)
664 WHERE guarantorid IS NOT NULL
666 ) as tmp ON borrowers.borrowernumber=tmp.guarantorid
667 LEFT JOIN old_issues USING (borrowernumber)
668 LEFT JOIN issues USING (borrowernumber)|;
669 if ( $filterpatronlist ){
670 $query .= q| LEFT JOIN patron_list_patrons USING (borrowernumber)|;
672 $query .= q| WHERE category_type <> 'S'
673 AND tmp.guarantorid IS NULL
676 if ( $filterbranch && $filterbranch ne "" ) {
677 $query.= " AND borrowers.branchcode = ? ";
678 push( @query_params, $filterbranch );
680 if ( $filterexpiry ) {
681 $query .= " AND dateexpiry < ? ";
682 push( @query_params, $filterexpiry );
684 if ( $filterlastseen ) {
685 $query .= ' AND lastseen < ? ';
686 push @query_params, $filterlastseen;
688 if ( $filtercategory ) {
689 $query .= " AND categorycode = ? ";
690 push( @query_params, $filtercategory );
692 if ( $filterpatronlist ){
693 $query.=" AND patron_list_id = ? ";
694 push( @query_params, $filterpatronlist );
696 $query .= " GROUP BY borrowers.borrowernumber";
698 ) xxx WHERE currentissue IS NULL|;
700 $query.=" AND ( latestissue < ? OR latestissue IS NULL ) ";
701 push @query_params,$filterdate;
704 warn $query if $debug;
706 my $sth = $dbh->prepare($query);
707 if (scalar(@query_params)>0){
708 $sth->execute(@query_params);
715 while ( my $data = $sth->fetchrow_hashref ) {
716 push @results, $data;
723 IssueSlip($branchcode, $borrowernumber, $quickslip)
725 Returns letter hash ( see C4::Letters::GetPreparedLetter )
727 $quickslip is boolean, to indicate whether we want a quick slip
729 IssueSlip populates ISSUESLIP and ISSUEQSLIP, and will make the following expansions:
765 NOTE: Fields from tables issues, items, biblio and biblioitems are available
770 my ($branch, $borrowernumber, $quickslip) = @_;
772 # FIXME Check callers before removing this statement
773 #return unless $borrowernumber;
775 my $patron = Koha::Patrons->find( $borrowernumber );
776 return unless $patron;
778 my $pending_checkouts = $patron->pending_checkouts; # Should be $patron->checkouts->pending?
780 my ($letter_code, %repeat, %loops);
782 my $today_start = dt_from_string->set( hour => 0, minute => 0, second => 0 );
783 my $today_end = dt_from_string->set( hour => 23, minute => 59, second => 0 );
784 $today_start = Koha::Database->new->schema->storage->datetime_parser->format_datetime( $today_start );
785 $today_end = Koha::Database->new->schema->storage->datetime_parser->format_datetime( $today_end );
786 $letter_code = 'ISSUEQSLIP';
788 # issue date or lastreneweddate is today
789 my $todays_checkouts = $pending_checkouts->search(
793 '>=' => $today_start,
797 { '>=' => $today_start, '<=' => $today_end, }
802 while ( my $c = $todays_checkouts->next ) {
803 my $all = $c->unblessed_all_relateds;
813 checkedout => \@checkouts, # Historical syntax
816 issues => [ map { $_->{issues}{itemnumber} } @checkouts ], # TT syntax
820 my $today = Koha::Database->new->schema->storage->datetime_parser->format_datetime( dt_from_string );
821 # Checkouts due in the future
822 my $checkouts = $pending_checkouts->search({ date_due => { '>' => $today } });
823 my @checkouts; my @overdues;
824 while ( my $c = $checkouts->next ) {
825 my $all = $c->unblessed_all_relateds;
834 # Checkouts due in the past are overdues
835 my $overdues = $pending_checkouts->search({ date_due => { '<=' => $today } });
836 while ( my $o = $overdues->next ) {
837 my $all = $o->unblessed_all_relateds;
845 my $news = GetNewsToDisplay( "slip", $branch );
847 $_->{'timestamp'} = $_->{'newdate'};
850 $letter_code = 'ISSUESLIP';
852 checkedout => \@checkouts,
853 overdue => \@overdues,
857 issues => [ map { $_->{issues}{itemnumber} } @checkouts ],
858 overdues => [ map { $_->{issues}{itemnumber} } @overdues ],
859 opac_news => [ map { $_->{opac_news}{idnew} } @news ],
863 return C4::Letters::GetPreparedLetter (
864 module => 'circulation',
865 letter_code => $letter_code,
866 branchcode => $branch,
867 lang => $patron->lang,
869 'branches' => $branch,
870 'borrowers' => $borrowernumber,
877 =head2 AddMember_Auto
882 my ( %borrower ) = @_;
884 $borrower{'cardnumber'} ||= fixup_cardnumber();
886 $borrower{'borrowernumber'} = AddMember(%borrower);
888 return ( %borrower );
891 =head2 AddMember_Opac
896 my ( %borrower ) = @_;
898 $borrower{'categorycode'} //= C4::Context->preference('PatronSelfRegistrationDefaultCategory');
899 if (not defined $borrower{'password'}){
900 my $sr = new String::Random;
901 $sr->{'A'} = [ 'A'..'Z', 'a'..'z' ];
902 my $password = $sr->randpattern("AAAAAAAAAA");
903 $borrower{'password'} = $password;
906 %borrower = AddMember_Auto(%borrower);
908 return ( $borrower{'borrowernumber'}, $borrower{'password'} );
911 =head2 DeleteExpiredOpacRegistrations
913 Delete accounts that haven't been upgraded from the 'temporary' category
914 Returns the number of removed patrons
918 sub DeleteExpiredOpacRegistrations {
920 my $delay = C4::Context->preference('PatronSelfRegistrationExpireTemporaryAccountsDelay');
921 my $category_code = C4::Context->preference('PatronSelfRegistrationDefaultCategory');
923 return 0 if not $category_code or not defined $delay or $delay eq q||;
926 SELECT borrowernumber
928 WHERE categorycode = ? AND DATEDIFF( NOW(), dateenrolled ) > ? |;
930 my $dbh = C4::Context->dbh;
931 my $sth = $dbh->prepare($query);
932 $sth->execute( $category_code, $delay );
934 while ( my ($borrowernumber) = $sth->fetchrow_array() ) {
935 Koha::Patrons->find($borrowernumber)->delete;
941 =head2 DeleteUnverifiedOpacRegistrations
943 Delete all unverified self registrations in borrower_modifications,
944 older than the specified number of days.
948 sub DeleteUnverifiedOpacRegistrations {
950 my $dbh = C4::Context->dbh;
952 DELETE FROM borrower_modifications
953 WHERE borrowernumber = 0 AND DATEDIFF( NOW(), timestamp ) > ?|;
954 my $cnt=$dbh->do($sql, undef, ($days) );
955 return $cnt eq '0E0'? 0: $cnt;
958 END { } # module clean-up code here (global destructor)