Bug 6303: Display Organisation and Parent Organisation names when viewing a borrower...
[koha.git] / C4 / Members.pm
1 package C4::Members;
2
3 # Copyright 2000-2003 Katipo Communications
4 # Copyright 2010 BibLibre
5 # Parts Copyright 2010 Catalyst IT
6 #
7 # This file is part of Koha.
8 #
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
12 # version.
13 #
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
23 use strict;
24 #use warnings; FIXME - Bug 2505
25 use C4::Context;
26 use C4::Dates qw(format_date_in_iso);
27 use Digest::MD5 qw(md5_base64);
28 use Date::Calc qw/Today Add_Delta_YM/;
29 use C4::Log; # logaction
30 use C4::Overdues;
31 use C4::Reserves;
32 use C4::Accounts;
33 use C4::Biblio;
34 use C4::SQLHelper qw(InsertInTable UpdateInTable SearchInTable);
35 use C4::Members::Attributes qw(SearchIdMatchingAttribute);
36
37 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
38
39 BEGIN {
40         $VERSION = 3.02;
41         $debug = $ENV{DEBUG} || 0;
42         require Exporter;
43         @ISA = qw(Exporter);
44         #Get data
45         push @EXPORT, qw(
46                 &Search
47                 &GetMemberDetails
48         &GetMemberRelatives
49                 &GetMember
50
51                 &GetGuarantees 
52
53                 &GetMemberIssuesAndFines
54                 &GetPendingIssues
55                 &GetAllIssues
56
57                 &get_institutions 
58                 &getzipnamecity 
59                 &getidcity
60
61                 &GetFirstValidEmailAddress
62
63                 &GetAge 
64                 &GetCities 
65                 &GetRoadTypes 
66                 &GetRoadTypeDetails 
67                 &GetSortDetails
68                 &GetTitles
69
70     &GetPatronImage
71     &PutPatronImage
72     &RmPatronImage
73
74                 &GetHideLostItemsPreference
75
76                 &IsMemberBlocked
77                 &GetMemberAccountRecords
78                 &GetBorNotifyAcctRecord
79
80                 &GetborCatFromCatType 
81                 &GetBorrowercategory
82     &GetBorrowercategoryList
83
84                 &GetBorrowersWhoHaveNotBorrowedSince
85                 &GetBorrowersWhoHaveNeverBorrowed
86                 &GetBorrowersWithIssuesHistoryOlderThan
87
88                 &GetExpiryDate
89
90                 &AddMessage
91                 &DeleteMessage
92                 &GetMessages
93                 &GetMessagesCount
94         );
95
96         #Modify data
97         push @EXPORT, qw(
98                 &ModMember
99                 &changepassword
100          &ModPrivacy
101         );
102
103         #Delete data
104         push @EXPORT, qw(
105                 &DelMember
106         );
107
108         #Insert data
109         push @EXPORT, qw(
110                 &AddMember
111                 &add_member_orgs
112                 &MoveMemberToDeleted
113                 &ExtendMemberSubscriptionTo
114         );
115
116         #Check data
117     push @EXPORT, qw(
118         &checkuniquemember
119         &checkuserpassword
120         &Check_Userid
121         &Generate_Userid
122         &fixEthnicity
123         &ethnicitycategories
124         &fixup_cardnumber
125         &checkcardnumber
126     );
127 }
128
129 =head1 NAME
130
131 C4::Members - Perl Module containing convenience functions for member handling
132
133 =head1 SYNOPSIS
134
135 use C4::Members;
136
137 =head1 DESCRIPTION
138
139 This module contains routines for adding, modifying and deleting members/patrons/borrowers 
140
141 =head1 FUNCTIONS
142
143 =head2 Search
144
145   $borrowers_result_array_ref = &Search($filter,$orderby, $limit, 
146                        $columns_out, $search_on_fields,$searchtype);
147
148 Looks up patrons (borrowers) on filter. A wrapper for SearchInTable('borrowers').
149
150 For C<$filter>, C<$orderby>, C<$limit>, C<&columns_out>, C<&search_on_fields> and C<&searchtype>
151 refer to C4::SQLHelper:SearchInTable().
152
153 Special C<$filter> key '' is effectively expanded to search on surname firstname othernamescw
154 and cardnumber unless C<&search_on_fields> is defined
155
156 Examples:
157
158   $borrowers = Search('abcd', 'cardnumber');
159
160   $borrowers = Search({''=>'abcd', category_type=>'I'}, 'surname');
161
162 =cut
163
164 sub _express_member_find {
165     my ($filter) = @_;
166
167     # this is used by circulation everytime a new borrowers cardnumber is scanned
168     # so we can check an exact match first, if that works return, otherwise do the rest
169     my $dbh   = C4::Context->dbh;
170     my $query = "SELECT borrowernumber FROM borrowers WHERE cardnumber = ?";
171     if ( my $borrowernumber = $dbh->selectrow_array($query, undef, $filter) ) {
172         return( {"borrowernumber"=>$borrowernumber} );
173     }
174
175     my ($search_on_fields, $searchtype);
176     if ( length($filter) == 1 ) {
177         $search_on_fields = [ qw(surname) ];
178         $searchtype = 'start_with';
179     } else {
180         $search_on_fields = [ qw(surname firstname othernames cardnumber) ];
181         $searchtype = 'contain';
182     }
183
184     return (undef, $search_on_fields, $searchtype);
185 }
186
187 sub Search {
188     my ( $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype ) = @_;
189
190     my $search_string;
191     my $found_borrower;
192
193     if ( my $fr = ref $filter ) {
194         if ( $fr eq "HASH" ) {
195             if ( my $search_string = $filter->{''} ) {
196                 my ($member_filter, $member_search_on_fields, $member_searchtype) = _express_member_find($search_string);
197                 if ($member_filter) {
198                     $filter = $member_filter;
199                     $found_borrower = 1;
200                 } else {
201                     $search_on_fields ||= $member_search_on_fields;
202                     $searchtype ||= $member_searchtype;
203                 }
204             }
205         }
206         else {
207             $search_string = $filter;
208         }
209     }
210     else {
211         $search_string = $filter;
212         my ($member_filter, $member_search_on_fields, $member_searchtype) = _express_member_find($search_string);
213         if ($member_filter) {
214             $filter = $member_filter;
215             $found_borrower = 1;
216         } else {
217             $search_on_fields ||= $member_search_on_fields;
218             $searchtype ||= $member_searchtype;
219         }
220     }
221
222     if ( !$found_borrower && C4::Context->preference('ExtendedPatronAttributes') && $search_string ) {
223         my $matching_records = C4::Members::Attributes::SearchIdMatchingAttribute($search_string);
224         if(scalar(@$matching_records)>0) {
225             if ( my $fr = ref $filter ) {
226                 if ( $fr eq "HASH" ) {
227                     my %f = %$filter;
228                     $filter = [ $filter ];
229                     delete $f{''};
230                     push @$filter, { %f, "borrowernumber"=>$$matching_records };
231                 }
232                 else {
233                     push @$filter, {"borrowernumber"=>$matching_records};
234                 }
235             }
236             else {
237                 $filter = [ $filter ];
238                 push @$filter, {"borrowernumber"=>$matching_records};
239             }
240                 }
241     }
242
243     # $showallbranches was not used at the time SearchMember() was mainstreamed into Search().
244     # Mentioning for the reference
245
246     if ( C4::Context->preference("IndependantBranches") ) { # && !$showallbranches){
247         if ( my $userenv = C4::Context->userenv ) {
248             my $branch =  $userenv->{'branch'};
249             if ( ($userenv->{flags} % 2 !=1) &&
250                  $branch && $branch ne "insecure" ){
251
252                 if (my $fr = ref $filter) {
253                     if ( $fr eq "HASH" ) {
254                         $filter->{branchcode} = $branch;
255                     }
256                     else {
257                         foreach (@$filter) {
258                             $_ = { '' => $_ } unless ref $_;
259                             $_->{branchcode} = $branch;
260                         }
261                     }
262                 }
263                 else {
264                     $filter = { '' => $filter, branchcode => $branch };
265                 }
266             }      
267         }
268     }
269
270     if ($found_borrower) {
271         $searchtype = "exact";
272     }
273     $searchtype ||= "start_with";
274
275         return SearchInTable( "borrowers", $filter, $orderby, $limit, $columns_out, $search_on_fields, $searchtype );
276 }
277
278 =head2 GetMemberDetails
279
280 ($borrower) = &GetMemberDetails($borrowernumber, $cardnumber);
281
282 Looks up a patron and returns information about him or her. If
283 C<$borrowernumber> is true (nonzero), C<&GetMemberDetails> looks
284 up the borrower by number; otherwise, it looks up the borrower by card
285 number.
286
287 C<$borrower> is a reference-to-hash whose keys are the fields of the
288 borrowers table in the Koha database. In addition,
289 C<$borrower-E<gt>{flags}> is a hash giving more detailed information
290 about the patron. Its keys act as flags :
291
292     if $borrower->{flags}->{LOST} {
293         # Patron's card was reported lost
294     }
295
296 If the state of a flag means that the patron should not be
297 allowed to borrow any more books, then it will have a C<noissues> key
298 with a true value.
299
300 See patronflags for more details.
301
302 C<$borrower-E<gt>{authflags}> is a hash giving more detailed information
303 about the top-level permissions flags set for the borrower.  For example,
304 if a user has the "editcatalogue" permission,
305 C<$borrower-E<gt>{authflags}-E<gt>{editcatalogue}> will exist and have
306 the value "1".
307
308 =cut
309
310 sub GetMemberDetails {
311     my ( $borrowernumber, $cardnumber ) = @_;
312     my $dbh = C4::Context->dbh;
313     my $query;
314     my $sth;
315     if ($borrowernumber) {
316         $sth = $dbh->prepare("SELECT borrowers.*,category_type,categories.description,reservefee,enrolmentperiod FROM borrowers LEFT JOIN categories ON borrowers.categorycode=categories.categorycode WHERE  borrowernumber=?");
317         $sth->execute($borrowernumber);
318     }
319     elsif ($cardnumber) {
320         $sth = $dbh->prepare("SELECT borrowers.*,category_type,categories.description,reservefee,enrolmentperiod FROM borrowers LEFT JOIN categories ON borrowers.categorycode=categories.categorycode WHERE cardnumber=?");
321         $sth->execute($cardnumber);
322     }
323     else {
324         return undef;
325     }
326     my $borrower = $sth->fetchrow_hashref;
327     my ($amount) = GetMemberAccountRecords( $borrowernumber);
328     $borrower->{'amountoutstanding'} = $amount;
329     # FIXME - patronflags calls GetMemberAccountRecords... just have patronflags return $amount
330     my $flags = patronflags( $borrower);
331     my $accessflagshash;
332
333     $sth = $dbh->prepare("select bit,flag from userflags");
334     $sth->execute;
335     while ( my ( $bit, $flag ) = $sth->fetchrow ) {
336         if ( $borrower->{'flags'} && $borrower->{'flags'} & 2**$bit ) {
337             $accessflagshash->{$flag} = 1;
338         }
339     }
340     $borrower->{'flags'}     = $flags;
341     $borrower->{'authflags'} = $accessflagshash;
342
343     # For the purposes of making templates easier, we'll define a
344     # 'showname' which is the alternate form the user's first name if 
345     # 'other name' is defined.
346     if ($borrower->{category_type} eq 'I') {
347         $borrower->{'showname'} = $borrower->{'othernames'};
348         $borrower->{'showname'} .= " $borrower->{'firstname'}" if $borrower->{'firstname'};
349     } else {
350         $borrower->{'showname'} = $borrower->{'firstname'};
351     }
352
353     return ($borrower);    #, $flags, $accessflagshash);
354 }
355
356 =head2 patronflags
357
358  $flags = &patronflags($patron);
359
360 This function is not exported.
361
362 The following will be set where applicable:
363  $flags->{CHARGES}->{amount}        Amount of debt
364  $flags->{CHARGES}->{noissues}      Set if debt amount >$5.00 (or syspref noissuescharge)
365  $flags->{CHARGES}->{message}       Message -- deprecated
366
367  $flags->{CREDITS}->{amount}        Amount of credit
368  $flags->{CREDITS}->{message}       Message -- deprecated
369
370  $flags->{  GNA  }                  Patron has no valid address
371  $flags->{  GNA  }->{noissues}      Set for each GNA
372  $flags->{  GNA  }->{message}       "Borrower has no valid address" -- deprecated
373
374  $flags->{ LOST  }                  Patron's card reported lost
375  $flags->{ LOST  }->{noissues}      Set for each LOST
376  $flags->{ LOST  }->{message}       Message -- deprecated
377
378  $flags->{DBARRED}                  Set if patron debarred, no access
379  $flags->{DBARRED}->{noissues}      Set for each DBARRED
380  $flags->{DBARRED}->{message}       Message -- deprecated
381
382  $flags->{ NOTES }
383  $flags->{ NOTES }->{message}       The note itself.  NOT deprecated
384
385  $flags->{ ODUES }                  Set if patron has overdue books.
386  $flags->{ ODUES }->{message}       "Yes"  -- deprecated
387  $flags->{ ODUES }->{itemlist}      ref-to-array: list of overdue books
388  $flags->{ ODUES }->{itemlisttext}  Text list of overdue items -- deprecated
389
390  $flags->{WAITING}                  Set if any of patron's reserves are available
391  $flags->{WAITING}->{message}       Message -- deprecated
392  $flags->{WAITING}->{itemlist}      ref-to-array: list of available items
393
394 =over 
395
396 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlist}> is a reference-to-array listing the
397 overdue items. Its elements are references-to-hash, each describing an
398 overdue item. The keys are selected fields from the issues, biblio,
399 biblioitems, and items tables of the Koha database.
400
401 =item C<$flags-E<gt>{ODUES}-E<gt>{itemlisttext}> is a string giving a text listing of
402 the overdue items, one per line.  Deprecated.
403
404 =item C<$flags-E<gt>{WAITING}-E<gt>{itemlist}> is a reference-to-array listing the
405 available items. Each element is a reference-to-hash whose keys are
406 fields from the reserves table of the Koha database.
407
408 =back
409
410 All the "message" fields that include language generated in this function are deprecated, 
411 because such strings belong properly in the display layer.
412
413 The "message" field that comes from the DB is OK.
414
415 =cut
416
417 # TODO: use {anonymous => hashes} instead of a dozen %flaginfo
418 # FIXME rename this function.
419 sub patronflags {
420     my %flags;
421     my ( $patroninformation) = @_;
422     my $dbh=C4::Context->dbh;
423     my ($amount) = GetMemberAccountRecords( $patroninformation->{'borrowernumber'});
424     if ( $amount > 0 ) {
425         my %flaginfo;
426         my $noissuescharge = C4::Context->preference("noissuescharge") || 5;
427         $flaginfo{'message'} = sprintf "Patron owes \$%.02f", $amount;
428         $flaginfo{'amount'}  = sprintf "%.02f", $amount;
429         if ( $amount > $noissuescharge && !C4::Context->preference("AllowFineOverride") ) {
430             $flaginfo{'noissues'} = 1;
431         }
432         $flags{'CHARGES'} = \%flaginfo;
433     }
434     elsif ( $amount < 0 ) {
435         my %flaginfo;
436         $flaginfo{'message'} = sprintf "Patron has credit of \$%.02f", -$amount;
437         $flaginfo{'amount'}  = sprintf "%.02f", $amount;
438         $flags{'CREDITS'} = \%flaginfo;
439     }
440     if (   $patroninformation->{'gonenoaddress'}
441         && $patroninformation->{'gonenoaddress'} == 1 )
442     {
443         my %flaginfo;
444         $flaginfo{'message'}  = 'Borrower has no valid address.';
445         $flaginfo{'noissues'} = 1;
446         $flags{'GNA'}         = \%flaginfo;
447     }
448     if ( $patroninformation->{'lost'} && $patroninformation->{'lost'} == 1 ) {
449         my %flaginfo;
450         $flaginfo{'message'}  = 'Borrower\'s card reported lost.';
451         $flaginfo{'noissues'} = 1;
452         $flags{'LOST'}        = \%flaginfo;
453     }
454     if (   $patroninformation->{'debarred'}
455         && $patroninformation->{'debarred'} == 1 )
456     {
457         my %flaginfo;
458         $flaginfo{'message'}  = 'Borrower is Debarred.';
459         $flaginfo{'noissues'} = 1;
460         $flags{'DBARRED'}     = \%flaginfo;
461     }
462     if (   $patroninformation->{'borrowernotes'}
463         && $patroninformation->{'borrowernotes'} )
464     {
465         my %flaginfo;
466         $flaginfo{'message'} = $patroninformation->{'borrowernotes'};
467         $flags{'NOTES'}      = \%flaginfo;
468     }
469     my ( $odues, $itemsoverdue ) = checkoverdues($patroninformation->{'borrowernumber'});
470     if ( $odues && $odues > 0 ) {
471         my %flaginfo;
472         $flaginfo{'message'}  = "Yes";
473         $flaginfo{'itemlist'} = $itemsoverdue;
474         foreach ( sort { $a->{'date_due'} cmp $b->{'date_due'} }
475             @$itemsoverdue )
476         {
477             $flaginfo{'itemlisttext'} .=
478               "$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n";  # newline is display layer
479         }
480         $flags{'ODUES'} = \%flaginfo;
481     }
482     my @itemswaiting = C4::Reserves::GetReservesFromBorrowernumber( $patroninformation->{'borrowernumber'},'W' );
483     my $nowaiting = scalar @itemswaiting;
484     if ( $nowaiting > 0 ) {
485         my %flaginfo;
486         $flaginfo{'message'}  = "Reserved items available";
487         $flaginfo{'itemlist'} = \@itemswaiting;
488         $flags{'WAITING'}     = \%flaginfo;
489     }
490     return ( \%flags );
491 }
492
493
494 =head2 GetMember
495
496   $borrower = &GetMember(%information);
497
498 Retrieve the first patron record meeting on criteria listed in the
499 C<%information> hash, which should contain one or more
500 pairs of borrowers column names and values, e.g.,
501
502    $borrower = GetMember(borrowernumber => id);
503
504 C<&GetBorrower> returns a reference-to-hash whose keys are the fields of
505 the C<borrowers> table in the Koha database.
506
507 FIXME: GetMember() is used throughout the code as a lookup
508 on a unique key such as the borrowernumber, but this meaning is not
509 enforced in the routine itself.
510
511 =cut
512
513 #'
514 sub GetMember {
515     my ( %information ) = @_;
516     if (exists $information{borrowernumber} && !defined $information{borrowernumber}) {
517         #passing mysql's kohaadmin?? Makes no sense as a query
518         return;
519     }
520     my $dbh = C4::Context->dbh;
521     my $select =
522     q{SELECT borrowers.*, categories.category_type, categories.description
523     FROM borrowers 
524     LEFT JOIN categories on borrowers.categorycode=categories.categorycode WHERE };
525     my $more_p = 0;
526     my @values = ();
527     for (keys %information ) {
528         if ($more_p) {
529             $select .= ' AND ';
530         }
531         else {
532             $more_p++;
533         }
534
535         if (defined $information{$_}) {
536             $select .= "$_ = ?";
537             push @values, $information{$_};
538         }
539         else {
540             $select .= "$_ IS NULL";
541         }
542     }
543     $debug && warn $select, " ",values %information;
544     my $sth = $dbh->prepare("$select");
545     $sth->execute(map{$information{$_}} keys %information);
546     my $data = $sth->fetchall_arrayref({});
547     #FIXME interface to this routine now allows generation of a result set
548     #so whole array should be returned but bowhere in the current code expects this
549     if (@{$data} ) {
550         return $data->[0];
551     }
552
553     return;
554 }
555
556 =head2 GetMemberRelatives
557
558  @borrowernumbers = GetMemberRelatives($borrowernumber);
559
560  C<GetMemberRelatives> returns a borrowersnumber's list of guarantor/guarantees of the member given in parameter
561
562 =cut 
563 sub GetMemberRelatives {
564     my $borrowernumber = shift;
565     my $dbh = C4::Context->dbh;
566     my @glist;
567
568     # Getting guarantor
569     my $query = "SELECT guarantorid FROM borrowers WHERE borrowernumber=?";
570     my $sth = $dbh->prepare($query);
571     $sth->execute($borrowernumber);
572     my $data = $sth->fetchrow_arrayref();
573     push @glist, $data->[0] if $data->[0];
574     my $guarantor = $data->[0] if $data->[0];
575
576     # Getting guarantees
577     $query = "SELECT borrowernumber FROM borrowers WHERE guarantorid=?";
578     $sth = $dbh->prepare($query);
579     $sth->execute($borrowernumber);
580     while ($data = $sth->fetchrow_arrayref()) {
581        push @glist, $data->[0];
582     }
583
584     # Getting sibling guarantees
585     if ($guarantor) {
586         $query = "SELECT borrowernumber FROM borrowers WHERE guarantorid=?";
587         $sth = $dbh->prepare($query);
588         $sth->execute($guarantor);
589         while ($data = $sth->fetchrow_arrayref()) {
590            push @glist, $data->[0] if ($data->[0] != $borrowernumber);
591         }
592     }
593
594     return @glist;
595 }
596
597 =head2 IsMemberBlocked
598
599   my ($block_status, $count) = IsMemberBlocked( $borrowernumber );
600
601 Returns whether a patron has overdue items that may result
602 in a block or whether the patron has active fine days
603 that would block circulation privileges.
604
605 C<$block_status> can have the following values:
606
607 1 if the patron has outstanding fine days, in which case C<$count> is the number of them
608
609 -1 if the patron has overdue items, in which case C<$count> is the number of them
610
611 0 if the patron has no overdue items or outstanding fine days, in which case C<$count> is 0
612
613 Outstanding fine days are checked before current overdue items
614 are.
615
616 FIXME: this needs to be split into two functions; a potential block
617 based on the number of current overdue items could be orthogonal
618 to a block based on whether the patron has any fine days accrued.
619
620 =cut
621
622 sub IsMemberBlocked {
623     my $borrowernumber = shift;
624     my $dbh            = C4::Context->dbh;
625
626     # does patron have current fine days?
627         my $strsth=qq{
628             SELECT
629             ADDDATE(returndate, finedays * DATEDIFF(returndate,date_due) ) AS blockingdate,
630             DATEDIFF(ADDDATE(returndate, finedays * DATEDIFF(returndate,date_due)),NOW()) AS blockedcount
631             FROM old_issues
632         };
633     if(C4::Context->preference("item-level_itypes")){
634         $strsth.=
635                 qq{ LEFT JOIN items ON (items.itemnumber=old_issues.itemnumber)
636             LEFT JOIN issuingrules ON (issuingrules.itemtype=items.itype)}
637     }else{
638         $strsth .= 
639                 qq{ LEFT JOIN items ON (items.itemnumber=old_issues.itemnumber)
640             LEFT JOIN biblioitems ON (biblioitems.biblioitemnumber=items.biblioitemnumber)
641             LEFT JOIN issuingrules ON (issuingrules.itemtype=biblioitems.itemtype) };
642     }
643         $strsth.=
644         qq{ WHERE finedays IS NOT NULL
645             AND  date_due < returndate
646             AND borrowernumber = ?
647             ORDER BY blockingdate DESC, blockedcount DESC
648             LIMIT 1};
649         my $sth=$dbh->prepare($strsth);
650     $sth->execute($borrowernumber);
651     my $row = $sth->fetchrow_hashref;
652     my $blockeddate  = $row->{'blockeddate'};
653     my $blockedcount = $row->{'blockedcount'};
654
655     return (1, $blockedcount) if $blockedcount > 0;
656
657     # if he have late issues
658     $sth = $dbh->prepare(
659         "SELECT COUNT(*) as latedocs
660          FROM issues
661          WHERE borrowernumber = ?
662          AND date_due < curdate()"
663     );
664     $sth->execute($borrowernumber);
665     my $latedocs = $sth->fetchrow_hashref->{'latedocs'};
666
667     return (-1, $latedocs) if $latedocs > 0;
668
669     return (0, 0);
670 }
671
672 =head2 GetMemberIssuesAndFines
673
674   ($overdue_count, $issue_count, $total_fines) = &GetMemberIssuesAndFines($borrowernumber);
675
676 Returns aggregate data about items borrowed by the patron with the
677 given borrowernumber.
678
679 C<&GetMemberIssuesAndFines> returns a three-element array.  C<$overdue_count> is the
680 number of overdue items the patron currently has borrowed. C<$issue_count> is the
681 number of books the patron currently has borrowed.  C<$total_fines> is
682 the total fine currently due by the borrower.
683
684 =cut
685
686 #'
687 sub GetMemberIssuesAndFines {
688     my ( $borrowernumber ) = @_;
689     my $dbh   = C4::Context->dbh;
690     my $query = "SELECT COUNT(*) FROM issues WHERE borrowernumber = ?";
691
692     $debug and warn $query."\n";
693     my $sth = $dbh->prepare($query);
694     $sth->execute($borrowernumber);
695     my $issue_count = $sth->fetchrow_arrayref->[0];
696
697     $sth = $dbh->prepare(
698         "SELECT COUNT(*) FROM issues 
699          WHERE borrowernumber = ? 
700          AND date_due < curdate()"
701     );
702     $sth->execute($borrowernumber);
703     my $overdue_count = $sth->fetchrow_arrayref->[0];
704
705     $sth = $dbh->prepare("SELECT SUM(amountoutstanding) FROM accountlines WHERE borrowernumber = ?");
706     $sth->execute($borrowernumber);
707     my $total_fines = $sth->fetchrow_arrayref->[0];
708
709     return ($overdue_count, $issue_count, $total_fines);
710 }
711
712 sub columns(;$) {
713     return @{C4::Context->dbh->selectcol_arrayref("SHOW columns from borrowers")};
714 }
715
716 =head2 ModMember
717
718   my $success = ModMember(borrowernumber => $borrowernumber,
719                                             [ field => value ]... );
720
721 Modify borrower's data.  All date fields should ALREADY be in ISO format.
722
723 return :
724 true on success, or false on failure
725
726 =cut
727
728 sub ModMember {
729     my (%data) = @_;
730     # test to know if you must update or not the borrower password
731     if (exists $data{password}) {
732         if ($data{password} eq '****' or $data{password} eq '') {
733             delete $data{password};
734         } else {
735             $data{password} = md5_base64($data{password});
736         }
737     }
738         my $execute_success=UpdateInTable("borrowers",\%data);
739     if ($execute_success) { # only proceed if the update was a success
740         # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
741         # so when we update information for an adult we should check for guarantees and update the relevant part
742         # of their records, ie addresses and phone numbers
743         my $borrowercategory= GetBorrowercategory( $data{'category_type'} );
744         if ( exists  $borrowercategory->{'category_type'} && $borrowercategory->{'category_type'} eq ('A' || 'S') ) {
745             # is adult check guarantees;
746             UpdateGuarantees(%data);
747         }
748         logaction("MEMBERS", "MODIFY", $data{'borrowernumber'}, "UPDATE (executed w/ arg: $data{'borrowernumber'})") if C4::Context->preference("BorrowersLog");
749     }
750     return $execute_success;
751 }
752
753
754 =head2 AddMember
755
756   $borrowernumber = &AddMember(%borrower);
757
758 insert new borrower into table
759 Returns the borrowernumber upon success
760
761 Returns as undef upon any db error without further processing
762
763 =cut
764
765 #'
766 sub AddMember {
767     my (%data) = @_;
768     my $dbh = C4::Context->dbh;
769         # generate a proper login if none provided
770         $data{'userid'} = Generate_Userid($data{'borrowernumber'}, $data{'firstname'}, $data{'surname'}) if $data{'userid'} eq '';
771         # create a disabled account if no password provided
772         $data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
773         $data{'borrowernumber'}=InsertInTable("borrowers",\%data);      
774     # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
775     logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
776     
777     # check for enrollment fee & add it if needed
778     my $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
779     $sth->execute($data{'categorycode'});
780     my ($enrolmentfee) = $sth->fetchrow;
781     if ($sth->err) {
782         warn sprintf('Database returned the following error: %s', $sth->errstr);
783         return;
784     }
785     if ($enrolmentfee && $enrolmentfee > 0) {
786         # insert fee in patron debts
787         manualinvoice($data{'borrowernumber'}, '', '', 'A', $enrolmentfee);
788     }
789
790     return $data{'borrowernumber'};
791 }
792
793
794 sub Check_Userid {
795     my ($uid,$member) = @_;
796     my $dbh = C4::Context->dbh;
797     # Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
798     # Then we need to tell the user and have them create a new one.
799     my $sth =
800       $dbh->prepare(
801         "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
802     $sth->execute( $uid, $member );
803     if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
804         return 0;
805     }
806     else {
807         return 1;
808     }
809 }
810
811 sub Generate_Userid {
812   my ($borrowernumber, $firstname, $surname) = @_;
813   my $newuid;
814   my $offset = 0;
815   do {
816     $firstname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
817     $surname =~ s/[[:digit:][:space:][:blank:][:punct:][:cntrl:]]//g;
818     $newuid = lc(($firstname)? "$firstname.$surname" : $surname);
819     $newuid .= $offset unless $offset == 0;
820     $offset++;
821
822    } while (!Check_Userid($newuid,$borrowernumber));
823
824    return $newuid;
825 }
826
827 sub changepassword {
828     my ( $uid, $member, $digest ) = @_;
829     my $dbh = C4::Context->dbh;
830
831 #Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
832 #Then we need to tell the user and have them create a new one.
833     my $resultcode;
834     my $sth =
835       $dbh->prepare(
836         "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
837     $sth->execute( $uid, $member );
838     if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
839         $resultcode=0;
840     }
841     else {
842         #Everything is good so we can update the information.
843         $sth =
844           $dbh->prepare(
845             "update borrowers set userid=?, password=? where borrowernumber=?");
846         $sth->execute( $uid, $digest, $member );
847         $resultcode=1;
848     }
849     
850     logaction("MEMBERS", "CHANGE PASS", $member, "") if C4::Context->preference("BorrowersLog");
851     return $resultcode;    
852 }
853
854
855
856 =head2 fixup_cardnumber
857
858 Warning: The caller is responsible for locking the members table in write
859 mode, to avoid database corruption.
860
861 =cut
862
863 use vars qw( @weightings );
864 my @weightings = ( 8, 4, 6, 3, 5, 2, 1 );
865
866 sub fixup_cardnumber ($) {
867     my ($cardnumber) = @_;
868     my $autonumber_members = C4::Context->boolean_preference('autoMemberNum') || 0;
869
870     # Find out whether member numbers should be generated
871     # automatically. Should be either "1" or something else.
872     # Defaults to "0", which is interpreted as "no".
873
874     #     if ($cardnumber !~ /\S/ && $autonumber_members) {
875     ($autonumber_members) or return $cardnumber;
876     my $checkdigit = C4::Context->preference('checkdigit');
877     my $dbh = C4::Context->dbh;
878     if ( $checkdigit and $checkdigit eq 'katipo' ) {
879
880         # if checkdigit is selected, calculate katipo-style cardnumber.
881         # otherwise, just use the max()
882         # purpose: generate checksum'd member numbers.
883         # We'll assume we just got the max value of digits 2-8 of member #'s
884         # from the database and our job is to increment that by one,
885         # determine the 1st and 9th digits and return the full string.
886         my $sth = $dbh->prepare(
887             "select max(substring(borrowers.cardnumber,2,7)) as new_num from borrowers"
888         );
889         $sth->execute;
890         my $data = $sth->fetchrow_hashref;
891         $cardnumber = $data->{new_num};
892         if ( !$cardnumber ) {    # If DB has no values,
893             $cardnumber = 1000000;    # start at 1000000
894         } else {
895             $cardnumber += 1;
896         }
897
898         my $sum = 0;
899         for ( my $i = 0 ; $i < 8 ; $i += 1 ) {
900             # read weightings, left to right, 1 char at a time
901             my $temp1 = $weightings[$i];
902
903             # sequence left to right, 1 char at a time
904             my $temp2 = substr( $cardnumber, $i, 1 );
905
906             # mult each char 1-7 by its corresponding weighting
907             $sum += $temp1 * $temp2;
908         }
909
910         my $rem = ( $sum % 11 );
911         $rem = 'X' if $rem == 10;
912
913         return "V$cardnumber$rem";
914      } else {
915
916      # MODIFIED BY JF: mysql4.1 allows casting as an integer, which is probably
917      # better. I'll leave the original in in case it needs to be changed for you
918      # my $sth=$dbh->prepare("select max(borrowers.cardnumber) from borrowers");
919         my $sth = $dbh->prepare(
920             "select max(cast(cardnumber as signed)) from borrowers"
921         );
922         $sth->execute;
923         my ($result) = $sth->fetchrow;
924         return $result + 1;
925     }
926     return $cardnumber;     # just here as a fallback/reminder 
927 }
928
929 =head2 GetGuarantees
930
931   ($num_children, $children_arrayref) = &GetGuarantees($parent_borrno);
932   $child0_cardno = $children_arrayref->[0]{"cardnumber"};
933   $child0_borrno = $children_arrayref->[0]{"borrowernumber"};
934
935 C<&GetGuarantees> takes a borrower number (e.g., that of a patron
936 with children) and looks up the borrowers who are guaranteed by that
937 borrower (i.e., the patron's children).
938
939 C<&GetGuarantees> returns two values: an integer giving the number of
940 borrowers guaranteed by C<$parent_borrno>, and a reference to an array
941 of references to hash, which gives the actual results.
942
943 =cut
944
945 #'
946 sub GetGuarantees {
947     my ($borrowernumber) = @_;
948     my $dbh              = C4::Context->dbh;
949     my $sth              =
950       $dbh->prepare(
951 "select cardnumber,borrowernumber, firstname, surname from borrowers where guarantorid=?"
952       );
953     $sth->execute($borrowernumber);
954
955     my @dat;
956     my $data = $sth->fetchall_arrayref({}); 
957     return ( scalar(@$data), $data );
958 }
959
960 =head2 UpdateGuarantees
961
962   &UpdateGuarantees($parent_borrno);
963   
964
965 C<&UpdateGuarantees> borrower data for an adult and updates all the guarantees
966 with the modified information
967
968 =cut
969
970 #'
971 sub UpdateGuarantees {
972     my %data = shift;
973     my $dbh = C4::Context->dbh;
974     my ( $count, $guarantees ) = GetGuarantees( $data{'borrowernumber'} );
975     foreach my $guarantee (@$guarantees){
976         my $guaquery = qq|UPDATE borrowers 
977               SET address=?,fax=?,B_city=?,mobile=?,city=?,phone=?
978               WHERE borrowernumber=?
979         |;
980         my $sth = $dbh->prepare($guaquery);
981         $sth->execute($data{'address'},$data{'fax'},$data{'B_city'},$data{'mobile'},$data{'city'},$data{'phone'},$guarantee->{'borrowernumber'});
982     }
983 }
984 =head2 GetPendingIssues
985
986   my $issues = &GetPendingIssues(@borrowernumber);
987
988 Looks up what the patron with the given borrowernumber has borrowed.
989
990 C<&GetPendingIssues> returns a
991 reference-to-array where each element is a reference-to-hash; the
992 keys are the fields from the C<issues>, C<biblio>, and C<items> tables.
993 The keys include C<biblioitems> fields except marc and marcxml.
994
995 =cut
996
997 #'
998 sub GetPendingIssues {
999     my @borrowernumbers = @_;
1000
1001     unless (@borrowernumbers ) { # return a ref_to_array
1002         return \@borrowernumbers; # to not cause surprise to caller
1003     }
1004
1005     # Borrowers part of the query
1006     my $bquery = '';
1007     for (my $i = 0; $i < @borrowernumbers; $i++) {
1008         $bquery .= ' issues.borrowernumber = ?';
1009         if ($i < $#borrowernumbers ) {
1010             $bquery .= ' OR';
1011         }
1012     }
1013
1014     # must avoid biblioitems.* to prevent large marc and marcxml fields from killing performance
1015     # FIXME: namespace collision: each table has "timestamp" fields.  Which one is "timestamp" ?
1016     # FIXME: circ/ciculation.pl tries to sort by timestamp!
1017     # FIXME: C4::Print::printslip tries to sort by timestamp!
1018     # FIXME: namespace collision: other collisions possible.
1019     # FIXME: most of this data isn't really being used by callers.
1020     my $query =
1021    "SELECT issues.*,
1022             items.*,
1023            biblio.*,
1024            biblioitems.volume,
1025            biblioitems.number,
1026            biblioitems.itemtype,
1027            biblioitems.isbn,
1028            biblioitems.issn,
1029            biblioitems.publicationyear,
1030            biblioitems.publishercode,
1031            biblioitems.volumedate,
1032            biblioitems.volumedesc,
1033            biblioitems.lccn,
1034            biblioitems.url,
1035            borrowers.firstname,
1036            borrowers.surname,
1037            borrowers.cardnumber,
1038            issues.timestamp AS timestamp,
1039            issues.renewals  AS renewals,
1040            issues.borrowernumber AS borrowernumber,
1041             items.renewals  AS totalrenewals
1042     FROM   issues
1043     LEFT JOIN items       ON items.itemnumber       =      issues.itemnumber
1044     LEFT JOIN biblio      ON items.biblionumber     =      biblio.biblionumber
1045     LEFT JOIN biblioitems ON items.biblioitemnumber = biblioitems.biblioitemnumber
1046     LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber
1047     WHERE
1048       $bquery
1049     ORDER BY issues.issuedate"
1050     ;
1051
1052     my $sth = C4::Context->dbh->prepare($query);
1053     $sth->execute(@borrowernumbers);
1054     my $data = $sth->fetchall_arrayref({});
1055     my $today = C4::Dates->new->output('iso');
1056     foreach (@{$data}) {
1057         if ($_->{date_due}  and $_->{date_due} lt $today) {
1058             $_->{overdue} = 1;
1059         }
1060     }
1061     return $data;
1062 }
1063
1064 =head2 GetAllIssues
1065
1066   $issues = &GetAllIssues($borrowernumber, $sortkey, $limit);
1067
1068 Looks up what the patron with the given borrowernumber has borrowed,
1069 and sorts the results.
1070
1071 C<$sortkey> is the name of a field on which to sort the results. This
1072 should be the name of a field in the C<issues>, C<biblio>,
1073 C<biblioitems>, or C<items> table in the Koha database.
1074
1075 C<$limit> is the maximum number of results to return.
1076
1077 C<&GetAllIssues> an arrayref, C<$issues>, of hashrefs, the keys of which
1078 are the fields from the C<issues>, C<biblio>, C<biblioitems>, and
1079 C<items> tables of the Koha database.
1080
1081 =cut
1082
1083 #'
1084 sub GetAllIssues {
1085     my ( $borrowernumber, $order, $limit ) = @_;
1086
1087     #FIXME: sanity-check order and limit
1088     my $dbh   = C4::Context->dbh;
1089     my $query =
1090   "SELECT *, issues.timestamp as issuestimestamp, issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp 
1091   FROM issues 
1092   LEFT JOIN items on items.itemnumber=issues.itemnumber
1093   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
1094   LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
1095   WHERE borrowernumber=? 
1096   UNION ALL
1097   SELECT *, old_issues.timestamp as issuestimestamp, old_issues.renewals AS renewals,items.renewals AS totalrenewals,items.timestamp AS itemstimestamp 
1098   FROM old_issues 
1099   LEFT JOIN items on items.itemnumber=old_issues.itemnumber
1100   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
1101   LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
1102   WHERE borrowernumber=? AND old_issues.itemnumber IS NOT NULL
1103   order by $order";
1104     if ( $limit != 0 ) {
1105         $query .= " limit $limit";
1106     }
1107
1108     my $sth = $dbh->prepare($query);
1109     $sth->execute($borrowernumber, $borrowernumber);
1110     my @result;
1111     my $i = 0;
1112     while ( my $data = $sth->fetchrow_hashref ) {
1113         push @result, $data;
1114     }
1115
1116     return \@result;
1117 }
1118
1119
1120 =head2 GetMemberAccountRecords
1121
1122   ($total, $acctlines, $count) = &GetMemberAccountRecords($borrowernumber);
1123
1124 Looks up accounting data for the patron with the given borrowernumber.
1125
1126 C<&GetMemberAccountRecords> returns a three-element array. C<$acctlines> is a
1127 reference-to-array, where each element is a reference-to-hash; the
1128 keys are the fields of the C<accountlines> table in the Koha database.
1129 C<$count> is the number of elements in C<$acctlines>. C<$total> is the
1130 total amount outstanding for all of the account lines.
1131
1132 =cut
1133
1134 #'
1135 sub GetMemberAccountRecords {
1136     my ($borrowernumber,$date) = @_;
1137     my $dbh = C4::Context->dbh;
1138     my @acctlines;
1139     my $numlines = 0;
1140     my $strsth      = qq(
1141                         SELECT * 
1142                         FROM accountlines 
1143                         WHERE borrowernumber=?);
1144     my @bind = ($borrowernumber);
1145     if ($date && $date ne ''){
1146             $strsth.=" AND date < ? ";
1147             push(@bind,$date);
1148     }
1149     $strsth.=" ORDER BY date desc,timestamp DESC";
1150     my $sth= $dbh->prepare( $strsth );
1151     $sth->execute( @bind );
1152     my $total = 0;
1153     while ( my $data = $sth->fetchrow_hashref ) {
1154         if ( $data->{itemnumber} ) {
1155             my $biblio = GetBiblioFromItemNumber( $data->{itemnumber} );
1156             $data->{biblionumber} = $biblio->{biblionumber};
1157             $data->{title}        = $biblio->{title};
1158         }
1159         $acctlines[$numlines] = $data;
1160         $numlines++;
1161         $total += int(1000 * $data->{'amountoutstanding'}); # convert float to integer to avoid round-off errors
1162     }
1163     $total /= 1000;
1164     return ( $total, \@acctlines,$numlines);
1165 }
1166
1167 =head2 GetBorNotifyAcctRecord
1168
1169   ($total, $acctlines, $count) = &GetBorNotifyAcctRecord($params,$notifyid);
1170
1171 Looks up accounting data for the patron with the given borrowernumber per file number.
1172
1173 C<&GetBorNotifyAcctRecord> returns a three-element array. C<$acctlines> is a
1174 reference-to-array, where each element is a reference-to-hash; the
1175 keys are the fields of the C<accountlines> table in the Koha database.
1176 C<$count> is the number of elements in C<$acctlines>. C<$total> is the
1177 total amount outstanding for all of the account lines.
1178
1179 =cut
1180
1181 sub GetBorNotifyAcctRecord {
1182     my ( $borrowernumber, $notifyid ) = @_;
1183     my $dbh = C4::Context->dbh;
1184     my @acctlines;
1185     my $numlines = 0;
1186     my $sth = $dbh->prepare(
1187             "SELECT * 
1188                 FROM accountlines 
1189                 WHERE borrowernumber=? 
1190                     AND notify_id=? 
1191                     AND amountoutstanding != '0' 
1192                 ORDER BY notify_id,accounttype
1193                 ");
1194
1195     $sth->execute( $borrowernumber, $notifyid );
1196     my $total = 0;
1197     while ( my $data = $sth->fetchrow_hashref ) {
1198         $acctlines[$numlines] = $data;
1199         $numlines++;
1200         $total += int(100 * $data->{'amountoutstanding'});
1201     }
1202     $total /= 100;
1203     return ( $total, \@acctlines, $numlines );
1204 }
1205
1206 =head2 checkuniquemember (OUEST-PROVENCE)
1207
1208   ($result,$categorycode)  = &checkuniquemember($collectivity,$surname,$firstname,$dateofbirth);
1209
1210 Checks that a member exists or not in the database.
1211
1212 C<&result> is nonzero (=exist) or 0 (=does not exist)
1213 C<&categorycode> is from categorycode table
1214 C<&collectivity> is 1 (= we add a collectivity) or 0 (= we add a physical member)
1215 C<&surname> is the surname
1216 C<&firstname> is the firstname (only if collectivity=0)
1217 C<&dateofbirth> is the date of birth in ISO format (only if collectivity=0)
1218
1219 =cut
1220
1221 # FIXME: This function is not legitimate.  Multiple patrons might have the same first/last name and birthdate.
1222 # This is especially true since first name is not even a required field.
1223
1224 sub checkuniquemember {
1225     my ( $collectivity, $surname, $firstname, $dateofbirth ) = @_;
1226     my $dbh = C4::Context->dbh;
1227     my $request = ($collectivity) ?
1228         "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? " :
1229             ($dateofbirth) ?
1230             "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?  and dateofbirth=?" :
1231             "SELECT borrowernumber,categorycode FROM borrowers WHERE surname=? and firstname=?";
1232     my $sth = $dbh->prepare($request);
1233     if ($collectivity) {
1234         $sth->execute( uc($surname) );
1235     } elsif($dateofbirth){
1236         $sth->execute( uc($surname), ucfirst($firstname), $dateofbirth );
1237     }else{
1238         $sth->execute( uc($surname), ucfirst($firstname));
1239     }
1240     my @data = $sth->fetchrow;
1241     ( $data[0] ) and return $data[0], $data[1];
1242     return 0;
1243 }
1244
1245 sub checkcardnumber {
1246     my ($cardnumber,$borrowernumber) = @_;
1247     # If cardnumber is null, we assume they're allowed.
1248     return 0 if !defined($cardnumber);
1249     my $dbh = C4::Context->dbh;
1250     my $query = "SELECT * FROM borrowers WHERE cardnumber=?";
1251     $query .= " AND borrowernumber <> ?" if ($borrowernumber);
1252   my $sth = $dbh->prepare($query);
1253   if ($borrowernumber) {
1254    $sth->execute($cardnumber,$borrowernumber);
1255   } else { 
1256      $sth->execute($cardnumber);
1257   } 
1258     if (my $data= $sth->fetchrow_hashref()){
1259         return 1;
1260     }
1261     else {
1262         return 0;
1263     }
1264 }  
1265
1266
1267 =head2 getzipnamecity (OUEST-PROVENCE)
1268
1269 take all info from table city for the fields city and  zip
1270 check for the name and the zip code of the city selected
1271
1272 =cut
1273
1274 sub getzipnamecity {
1275     my ($cityid) = @_;
1276     my $dbh      = C4::Context->dbh;
1277     my $sth      =
1278       $dbh->prepare(
1279         "select city_name,city_state,city_zipcode,city_country from cities where cityid=? ");
1280     $sth->execute($cityid);
1281     my @data = $sth->fetchrow;
1282     return $data[0], $data[1], $data[2], $data[3];
1283 }
1284
1285
1286 =head2 getdcity (OUEST-PROVENCE)
1287
1288 recover cityid  with city_name condition
1289
1290 =cut
1291
1292 sub getidcity {
1293     my ($city_name) = @_;
1294     my $dbh = C4::Context->dbh;
1295     my $sth = $dbh->prepare("select cityid from cities where city_name=? ");
1296     $sth->execute($city_name);
1297     my $data = $sth->fetchrow;
1298     return $data;
1299 }
1300
1301 =head2 GetFirstValidEmailAddress
1302
1303   $email = GetFirstValidEmailAddress($borrowernumber);
1304
1305 Return the first valid email address for a borrower, given the borrowernumber.  For now, the order 
1306 is defined as email, emailpro, B_email.  Returns the empty string if the borrower has no email 
1307 addresses.
1308
1309 =cut
1310
1311 sub GetFirstValidEmailAddress {
1312     my $borrowernumber = shift;
1313     my $dbh = C4::Context->dbh;
1314     my $sth = $dbh->prepare( "SELECT email, emailpro, B_email FROM borrowers where borrowernumber = ? ");
1315     $sth->execute( $borrowernumber );
1316     my $data = $sth->fetchrow_hashref;
1317
1318     if ($data->{'email'}) {
1319        return $data->{'email'};
1320     } elsif ($data->{'emailpro'}) {
1321        return $data->{'emailpro'};
1322     } elsif ($data->{'B_email'}) {
1323        return $data->{'B_email'};
1324     } else {
1325        return '';
1326     }
1327 }
1328
1329 =head2 GetExpiryDate 
1330
1331   $expirydate = GetExpiryDate($categorycode, $dateenrolled);
1332
1333 Calculate expiry date given a categorycode and starting date.  Date argument must be in ISO format.
1334 Return date is also in ISO format.
1335
1336 =cut
1337
1338 sub GetExpiryDate {
1339     my ( $categorycode, $dateenrolled ) = @_;
1340     my $enrolments;
1341     if ($categorycode) {
1342         my $dbh = C4::Context->dbh;
1343         my $sth = $dbh->prepare("SELECT enrolmentperiod,enrolmentperioddate FROM categories WHERE categorycode=?");
1344         $sth->execute($categorycode);
1345         $enrolments = $sth->fetchrow_hashref;
1346     }
1347     # die "GetExpiryDate: for enrollmentperiod $enrolmentperiod (category '$categorycode') starting $dateenrolled.\n";
1348     my @date = split (/-/,$dateenrolled);
1349     if($enrolments->{enrolmentperiod}){
1350         return sprintf("%04d-%02d-%02d", Add_Delta_YM(@date,0,$enrolments->{enrolmentperiod}));
1351     }else{
1352         return $enrolments->{enrolmentperioddate};
1353     }
1354 }
1355
1356 =head2 checkuserpassword (OUEST-PROVENCE)
1357
1358 check for the password and login are not used
1359 return the number of record 
1360 0=> NOT USED 1=> USED
1361
1362 =cut
1363
1364 sub checkuserpassword {
1365     my ( $borrowernumber, $userid, $password ) = @_;
1366     $password = md5_base64($password);
1367     my $dbh = C4::Context->dbh;
1368     my $sth =
1369       $dbh->prepare(
1370 "Select count(*) from borrowers where borrowernumber !=? and userid =? and password=? "
1371       );
1372     $sth->execute( $borrowernumber, $userid, $password );
1373     my $number_rows = $sth->fetchrow;
1374     return $number_rows;
1375
1376 }
1377
1378 =head2 GetborCatFromCatType
1379
1380   ($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
1381
1382 Looks up the different types of borrowers in the database. Returns two
1383 elements: a reference-to-array, which lists the borrower category
1384 codes, and a reference-to-hash, which maps the borrower category codes
1385 to category descriptions.
1386
1387 =cut
1388
1389 #'
1390 sub GetborCatFromCatType {
1391     my ( $category_type, $action ) = @_;
1392         # FIXME - This API  seems both limited and dangerous. 
1393     my $dbh     = C4::Context->dbh;
1394     my $request = qq|   SELECT categorycode,description 
1395             FROM categories 
1396             $action
1397             ORDER BY categorycode|;
1398     my $sth = $dbh->prepare($request);
1399         if ($action) {
1400         $sth->execute($category_type);
1401     }
1402     else {
1403         $sth->execute();
1404     }
1405
1406     my %labels;
1407     my @codes;
1408
1409     while ( my $data = $sth->fetchrow_hashref ) {
1410         push @codes, $data->{'categorycode'};
1411         $labels{ $data->{'categorycode'} } = $data->{'description'};
1412     }
1413     return ( \@codes, \%labels );
1414 }
1415
1416 =head2 GetBorrowercategory
1417
1418   $hashref = &GetBorrowercategory($categorycode);
1419
1420 Given the borrower's category code, the function returns the corresponding
1421 data hashref for a comprehensive information display.
1422
1423   $arrayref_hashref = &GetBorrowercategory;
1424
1425 If no category code provided, the function returns all the categories.
1426
1427 =cut
1428
1429 sub GetBorrowercategory {
1430     my ($catcode) = @_;
1431     my $dbh       = C4::Context->dbh;
1432     if ($catcode){
1433         my $sth       =
1434         $dbh->prepare(
1435     "SELECT description,dateofbirthrequired,upperagelimit,category_type 
1436     FROM categories 
1437     WHERE categorycode = ?"
1438         );
1439         $sth->execute($catcode);
1440         my $data =
1441         $sth->fetchrow_hashref;
1442         return $data;
1443     } 
1444     return;  
1445 }    # sub getborrowercategory
1446
1447 =head2 GetBorrowercategoryList
1448
1449   $arrayref_hashref = &GetBorrowercategoryList;
1450 If no category code provided, the function returns all the categories.
1451
1452 =cut
1453
1454 sub GetBorrowercategoryList {
1455     my $dbh       = C4::Context->dbh;
1456     my $sth       =
1457     $dbh->prepare(
1458     "SELECT * 
1459     FROM categories 
1460     ORDER BY description"
1461         );
1462     $sth->execute;
1463     my $data =
1464     $sth->fetchall_arrayref({});
1465     return $data;
1466 }    # sub getborrowercategory
1467
1468 =head2 ethnicitycategories
1469
1470   ($codes_arrayref, $labels_hashref) = &ethnicitycategories();
1471
1472 Looks up the different ethnic types in the database. Returns two
1473 elements: a reference-to-array, which lists the ethnicity codes, and a
1474 reference-to-hash, which maps the ethnicity codes to ethnicity
1475 descriptions.
1476
1477 =cut
1478
1479 #'
1480
1481 sub ethnicitycategories {
1482     my $dbh = C4::Context->dbh;
1483     my $sth = $dbh->prepare("Select code,name from ethnicity order by name");
1484     $sth->execute;
1485     my %labels;
1486     my @codes;
1487     while ( my $data = $sth->fetchrow_hashref ) {
1488         push @codes, $data->{'code'};
1489         $labels{ $data->{'code'} } = $data->{'name'};
1490     }
1491     return ( \@codes, \%labels );
1492 }
1493
1494 =head2 fixEthnicity
1495
1496   $ethn_name = &fixEthnicity($ethn_code);
1497
1498 Takes an ethnicity code (e.g., "european" or "pi") and returns the
1499 corresponding descriptive name from the C<ethnicity> table in the
1500 Koha database ("European" or "Pacific Islander").
1501
1502 =cut
1503
1504 #'
1505
1506 sub fixEthnicity {
1507     my $ethnicity = shift;
1508     return unless $ethnicity;
1509     my $dbh       = C4::Context->dbh;
1510     my $sth       = $dbh->prepare("Select name from ethnicity where code = ?");
1511     $sth->execute($ethnicity);
1512     my $data = $sth->fetchrow_hashref;
1513     return $data->{'name'};
1514 }    # sub fixEthnicity
1515
1516 =head2 GetAge
1517
1518   $dateofbirth,$date = &GetAge($date);
1519
1520 this function return the borrowers age with the value of dateofbirth
1521
1522 =cut
1523
1524 #'
1525 sub GetAge{
1526     my ( $date, $date_ref ) = @_;
1527
1528     if ( not defined $date_ref ) {
1529         $date_ref = sprintf( '%04d-%02d-%02d', Today() );
1530     }
1531
1532     my ( $year1, $month1, $day1 ) = split /-/, $date;
1533     my ( $year2, $month2, $day2 ) = split /-/, $date_ref;
1534
1535     my $age = $year2 - $year1;
1536     if ( $month1 . $day1 > $month2 . $day2 ) {
1537         $age--;
1538     }
1539
1540     return $age;
1541 }    # sub get_age
1542
1543 =head2 get_institutions
1544
1545   $insitutions = get_institutions();
1546
1547 Just returns a list of all the borrowers of type I, borrownumber and name
1548
1549 =cut
1550
1551 #'
1552 sub get_institutions {
1553     my $dbh = C4::Context->dbh();
1554     my $sth =
1555       $dbh->prepare(
1556 "SELECT borrowernumber,surname FROM borrowers WHERE categorycode=? ORDER BY surname"
1557       );
1558     $sth->execute('I');
1559     my %orgs;
1560     while ( my $data = $sth->fetchrow_hashref() ) {
1561         $orgs{ $data->{'borrowernumber'} } = $data;
1562     }
1563     return ( \%orgs );
1564
1565 }    # sub get_institutions
1566
1567 =head2 add_member_orgs
1568
1569   add_member_orgs($borrowernumber,$borrowernumbers);
1570
1571 Takes a borrowernumber and a list of other borrowernumbers and inserts them into the borrowers_to_borrowers table
1572
1573 =cut
1574
1575 #'
1576 sub add_member_orgs {
1577     my ( $borrowernumber, $otherborrowers ) = @_;
1578     my $dbh   = C4::Context->dbh();
1579     my $query =
1580       "INSERT INTO borrowers_to_borrowers (borrower1,borrower2) VALUES (?,?)";
1581     my $sth = $dbh->prepare($query);
1582     foreach my $otherborrowernumber (@$otherborrowers) {
1583         $sth->execute( $borrowernumber, $otherborrowernumber );
1584     }
1585
1586 }    # sub add_member_orgs
1587
1588 =head2 GetCities
1589
1590   $cityarrayref = GetCities();
1591
1592   Returns an array_ref of the entries in the cities table
1593   If there are entries in the table an empty row is returned
1594   This is currently only used to populate a popup in memberentry
1595
1596 =cut
1597
1598 sub GetCities {
1599
1600     my $dbh   = C4::Context->dbh;
1601     my $city_arr = $dbh->selectall_arrayref(
1602         q|SELECT cityid,city_zipcode,city_name,city_state,city_country FROM cities ORDER BY city_name|,
1603         { Slice => {} });
1604     if ( @{$city_arr} ) {
1605         unshift @{$city_arr}, {
1606             city_zipcode => q{},
1607             city_name    => q{},
1608             cityid       => q{},
1609             city_state   => q{},
1610             city_country => q{},
1611         };
1612     }
1613
1614     return  $city_arr;
1615 }
1616
1617 =head2 GetSortDetails (OUEST-PROVENCE)
1618
1619   ($lib) = &GetSortDetails($category,$sortvalue);
1620
1621 Returns the authorized value  details
1622 C<&$lib>return value of authorized value details
1623 C<&$sortvalue>this is the value of authorized value 
1624 C<&$category>this is the value of authorized value category
1625
1626 =cut
1627
1628 sub GetSortDetails {
1629     my ( $category, $sortvalue ) = @_;
1630     my $dbh   = C4::Context->dbh;
1631     my $query = qq|SELECT lib 
1632         FROM authorised_values 
1633         WHERE category=?
1634         AND authorised_value=? |;
1635     my $sth = $dbh->prepare($query);
1636     $sth->execute( $category, $sortvalue );
1637     my $lib = $sth->fetchrow;
1638     return ($lib) if ($lib);
1639     return ($sortvalue) unless ($lib);
1640 }
1641
1642 =head2 MoveMemberToDeleted
1643
1644   $result = &MoveMemberToDeleted($borrowernumber);
1645
1646 Copy the record from borrowers to deletedborrowers table.
1647
1648 =cut
1649
1650 # FIXME: should do it in one SQL statement w/ subquery
1651 # Otherwise, we should return the @data on success
1652
1653 sub MoveMemberToDeleted {
1654     my ($member) = shift or return;
1655     my $dbh = C4::Context->dbh;
1656     my $query = qq|SELECT * 
1657           FROM borrowers 
1658           WHERE borrowernumber=?|;
1659     my $sth = $dbh->prepare($query);
1660     $sth->execute($member);
1661     my @data = $sth->fetchrow_array;
1662     (@data) or return;  # if we got a bad borrowernumber, there's nothing to insert
1663     $sth =
1664       $dbh->prepare( "INSERT INTO deletedborrowers VALUES ("
1665           . ( "?," x ( scalar(@data) - 1 ) )
1666           . "?)" );
1667     $sth->execute(@data);
1668 }
1669
1670 =head2 DelMember
1671
1672     DelMember($borrowernumber);
1673
1674 This function remove directly a borrower whitout writing it on deleteborrower.
1675 + Deletes reserves for the borrower
1676
1677 =cut
1678
1679 sub DelMember {
1680     my $dbh            = C4::Context->dbh;
1681     my $borrowernumber = shift;
1682     #warn "in delmember with $borrowernumber";
1683     return unless $borrowernumber;    # borrowernumber is mandatory.
1684
1685     my $query = qq|DELETE 
1686           FROM  reserves 
1687           WHERE borrowernumber=?|;
1688     my $sth = $dbh->prepare($query);
1689     $sth->execute($borrowernumber);
1690     $query = "
1691        DELETE
1692        FROM borrowers
1693        WHERE borrowernumber = ?
1694    ";
1695     $sth = $dbh->prepare($query);
1696     $sth->execute($borrowernumber);
1697     logaction("MEMBERS", "DELETE", $borrowernumber, "") if C4::Context->preference("BorrowersLog");
1698     return $sth->rows;
1699 }
1700
1701 =head2 ExtendMemberSubscriptionTo (OUEST-PROVENCE)
1702
1703     $date = ExtendMemberSubscriptionTo($borrowerid, $date);
1704
1705 Extending the subscription to a given date or to the expiry date calculated on ISO date.
1706 Returns ISO date.
1707
1708 =cut
1709
1710 sub ExtendMemberSubscriptionTo {
1711     my ( $borrowerid,$date) = @_;
1712     my $dbh = C4::Context->dbh;
1713     my $borrower = GetMember('borrowernumber'=>$borrowerid);
1714     unless ($date){
1715       $date=POSIX::strftime("%Y-%m-%d",localtime());
1716       $date = GetExpiryDate( $borrower->{'categorycode'}, $date );
1717     }
1718     my $sth = $dbh->do(<<EOF);
1719 UPDATE borrowers 
1720 SET  dateexpiry='$date' 
1721 WHERE borrowernumber='$borrowerid'
1722 EOF
1723     # add enrolmentfee if needed
1724     $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
1725     $sth->execute($borrower->{'categorycode'});
1726     my ($enrolmentfee) = $sth->fetchrow;
1727     if ($enrolmentfee && $enrolmentfee > 0) {
1728         # insert fee in patron debts
1729         manualinvoice($borrower->{'borrowernumber'}, '', '', 'A', $enrolmentfee);
1730     }
1731      logaction("MEMBERS", "RENEW", $borrower->{'borrowernumber'}, "Membership renewed")if C4::Context->preference("BorrowersLog");
1732     return $date if ($sth);
1733     return 0;
1734 }
1735
1736 =head2 GetRoadTypes (OUEST-PROVENCE)
1737
1738   ($idroadtypearrayref, $roadttype_hashref) = &GetRoadTypes();
1739
1740 Looks up the different road type . Returns two
1741 elements: a reference-to-array, which lists the id_roadtype
1742 codes, and a reference-to-hash, which maps the road type of the road .
1743
1744 =cut
1745
1746 sub GetRoadTypes {
1747     my $dbh   = C4::Context->dbh;
1748     my $query = qq|
1749 SELECT roadtypeid,road_type 
1750 FROM roadtype 
1751 ORDER BY road_type|;
1752     my $sth = $dbh->prepare($query);
1753     $sth->execute();
1754     my %roadtype;
1755     my @id;
1756
1757     #    insert empty value to create a empty choice in cgi popup
1758
1759     while ( my $data = $sth->fetchrow_hashref ) {
1760
1761         push @id, $data->{'roadtypeid'};
1762         $roadtype{ $data->{'roadtypeid'} } = $data->{'road_type'};
1763     }
1764
1765 #test to know if the table contain some records if no the function return nothing
1766     my $id = @id;
1767     if ( $id eq 0 ) {
1768         return ();
1769     }
1770     else {
1771         unshift( @id, "" );
1772         return ( \@id, \%roadtype );
1773     }
1774 }
1775
1776
1777
1778 =head2 GetTitles (OUEST-PROVENCE)
1779
1780   ($borrowertitle)= &GetTitles();
1781
1782 Looks up the different title . Returns array  with all borrowers title
1783
1784 =cut
1785
1786 sub GetTitles {
1787     my @borrowerTitle = split (/,|\|/,C4::Context->preference('BorrowersTitles'));
1788     unshift( @borrowerTitle, "" );
1789     my $count=@borrowerTitle;
1790     if ($count == 1){
1791         return ();
1792     }
1793     else {
1794         return ( \@borrowerTitle);
1795     }
1796 }
1797
1798 =head2 GetPatronImage
1799
1800     my ($imagedata, $dberror) = GetPatronImage($cardnumber);
1801
1802 Returns the mimetype and binary image data of the image for the patron with the supplied cardnumber.
1803
1804 =cut
1805
1806 sub GetPatronImage {
1807     my ($cardnumber) = @_;
1808     warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
1809     my $dbh = C4::Context->dbh;
1810     my $query = 'SELECT mimetype, imagefile FROM patronimage WHERE cardnumber = ?';
1811     my $sth = $dbh->prepare($query);
1812     $sth->execute($cardnumber);
1813     my $imagedata = $sth->fetchrow_hashref;
1814     warn "Database error!" if $sth->errstr;
1815     return $imagedata, $sth->errstr;
1816 }
1817
1818 =head2 PutPatronImage
1819
1820     PutPatronImage($cardnumber, $mimetype, $imgfile);
1821
1822 Stores patron binary image data and mimetype in database.
1823 NOTE: This function is good for updating images as well as inserting new images in the database.
1824
1825 =cut
1826
1827 sub PutPatronImage {
1828     my ($cardnumber, $mimetype, $imgfile) = @_;
1829     warn "Parameters passed in: Cardnumber=$cardnumber, Mimetype=$mimetype, " . ($imgfile ? "Imagefile" : "No Imagefile") if $debug;
1830     my $dbh = C4::Context->dbh;
1831     my $query = "INSERT INTO patronimage (cardnumber, mimetype, imagefile) VALUES (?,?,?) ON DUPLICATE KEY UPDATE imagefile = ?;";
1832     my $sth = $dbh->prepare($query);
1833     $sth->execute($cardnumber,$mimetype,$imgfile,$imgfile);
1834     warn "Error returned inserting $cardnumber.$mimetype." if $sth->errstr;
1835     return $sth->errstr;
1836 }
1837
1838 =head2 RmPatronImage
1839
1840     my ($dberror) = RmPatronImage($cardnumber);
1841
1842 Removes the image for the patron with the supplied cardnumber.
1843
1844 =cut
1845
1846 sub RmPatronImage {
1847     my ($cardnumber) = @_;
1848     warn "Cardnumber passed to GetPatronImage is $cardnumber" if $debug;
1849     my $dbh = C4::Context->dbh;
1850     my $query = "DELETE FROM patronimage WHERE cardnumber = ?;";
1851     my $sth = $dbh->prepare($query);
1852     $sth->execute($cardnumber);
1853     my $dberror = $sth->errstr;
1854     warn "Database error!" if $sth->errstr;
1855     return $dberror;
1856 }
1857
1858 =head2 GetHideLostItemsPreference
1859
1860   $hidelostitemspref = &GetHideLostItemsPreference($borrowernumber);
1861
1862 Returns the HideLostItems preference for the patron category of the supplied borrowernumber
1863 C<&$hidelostitemspref>return value of function, 0 or 1
1864
1865 =cut
1866
1867 sub GetHideLostItemsPreference {
1868     my ($borrowernumber) = @_;
1869     my $dbh = C4::Context->dbh;
1870     my $query = "SELECT hidelostitems FROM borrowers,categories WHERE borrowers.categorycode = categories.categorycode AND borrowernumber = ?";
1871     my $sth = $dbh->prepare($query);
1872     $sth->execute($borrowernumber);
1873     my $hidelostitems = $sth->fetchrow;    
1874     return $hidelostitems;    
1875 }
1876
1877 =head2 GetRoadTypeDetails (OUEST-PROVENCE)
1878
1879   ($roadtype) = &GetRoadTypeDetails($roadtypeid);
1880
1881 Returns the description of roadtype
1882 C<&$roadtype>return description of road type
1883 C<&$roadtypeid>this is the value of roadtype s
1884
1885 =cut
1886
1887 sub GetRoadTypeDetails {
1888     my ($roadtypeid) = @_;
1889     my $dbh          = C4::Context->dbh;
1890     my $query        = qq|
1891 SELECT road_type 
1892 FROM roadtype 
1893 WHERE roadtypeid=?|;
1894     my $sth = $dbh->prepare($query);
1895     $sth->execute($roadtypeid);
1896     my $roadtype = $sth->fetchrow;
1897     return ($roadtype);
1898 }
1899
1900 =head2 GetBorrowersWhoHaveNotBorrowedSince
1901
1902   &GetBorrowersWhoHaveNotBorrowedSince($date)
1903
1904 this function get all borrowers who haven't borrowed since the date given on input arg.
1905
1906 =cut
1907
1908 sub GetBorrowersWhoHaveNotBorrowedSince {
1909     my $filterdate = shift||POSIX::strftime("%Y-%m-%d",localtime());
1910     my $filterexpiry = shift;
1911     my $filterbranch = shift || 
1912                         ((C4::Context->preference('IndependantBranches') 
1913                              && C4::Context->userenv 
1914                              && C4::Context->userenv->{flags} % 2 !=1 
1915                              && C4::Context->userenv->{branch})
1916                          ? C4::Context->userenv->{branch}
1917                          : "");  
1918     my $dbh   = C4::Context->dbh;
1919     my $query = "
1920         SELECT borrowers.borrowernumber,
1921                max(old_issues.timestamp) as latestissue,
1922                max(issues.timestamp) as currentissue
1923         FROM   borrowers
1924         JOIN   categories USING (categorycode)
1925         LEFT JOIN old_issues USING (borrowernumber)
1926         LEFT JOIN issues USING (borrowernumber) 
1927         WHERE  category_type <> 'S'
1928         AND borrowernumber NOT IN (SELECT guarantorid FROM borrowers WHERE guarantorid IS NOT NULL AND guarantorid <> 0) 
1929    ";
1930     my @query_params;
1931     if ($filterbranch && $filterbranch ne ""){ 
1932         $query.=" AND borrowers.branchcode= ?";
1933         push @query_params,$filterbranch;
1934     }
1935     if($filterexpiry){
1936         $query .= " AND dateexpiry < ? ";
1937         push @query_params,$filterdate;
1938     }
1939     $query.=" GROUP BY borrowers.borrowernumber";
1940     if ($filterdate){ 
1941         $query.=" HAVING (latestissue < ? OR latestissue IS NULL) 
1942                   AND currentissue IS NULL";
1943         push @query_params,$filterdate;
1944     }
1945     warn $query if $debug;
1946     my $sth = $dbh->prepare($query);
1947     if (scalar(@query_params)>0){  
1948         $sth->execute(@query_params);
1949     } 
1950     else {
1951         $sth->execute;
1952     }      
1953     
1954     my @results;
1955     while ( my $data = $sth->fetchrow_hashref ) {
1956         push @results, $data;
1957     }
1958     return \@results;
1959 }
1960
1961 =head2 GetBorrowersWhoHaveNeverBorrowed
1962
1963   $results = &GetBorrowersWhoHaveNeverBorrowed
1964
1965 This function get all borrowers who have never borrowed.
1966
1967 I<$result> is a ref to an array which all elements are a hasref.
1968
1969 =cut
1970
1971 sub GetBorrowersWhoHaveNeverBorrowed {
1972     my $filterbranch = shift || 
1973                         ((C4::Context->preference('IndependantBranches') 
1974                              && C4::Context->userenv 
1975                              && C4::Context->userenv->{flags} % 2 !=1 
1976                              && C4::Context->userenv->{branch})
1977                          ? C4::Context->userenv->{branch}
1978                          : "");  
1979     my $dbh   = C4::Context->dbh;
1980     my $query = "
1981         SELECT borrowers.borrowernumber,max(timestamp) as latestissue
1982         FROM   borrowers
1983           LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
1984         WHERE issues.borrowernumber IS NULL
1985    ";
1986     my @query_params;
1987     if ($filterbranch && $filterbranch ne ""){ 
1988         $query.=" AND borrowers.branchcode= ?";
1989         push @query_params,$filterbranch;
1990     }
1991     warn $query if $debug;
1992   
1993     my $sth = $dbh->prepare($query);
1994     if (scalar(@query_params)>0){  
1995         $sth->execute(@query_params);
1996     } 
1997     else {
1998         $sth->execute;
1999     }      
2000     
2001     my @results;
2002     while ( my $data = $sth->fetchrow_hashref ) {
2003         push @results, $data;
2004     }
2005     return \@results;
2006 }
2007
2008 =head2 GetBorrowersWithIssuesHistoryOlderThan
2009
2010   $results = &GetBorrowersWithIssuesHistoryOlderThan($date)
2011
2012 this function get all borrowers who has an issue history older than I<$date> given on input arg.
2013
2014 I<$result> is a ref to an array which all elements are a hashref.
2015 This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
2016
2017 =cut
2018
2019 sub GetBorrowersWithIssuesHistoryOlderThan {
2020     my $dbh  = C4::Context->dbh;
2021     my $date = shift ||POSIX::strftime("%Y-%m-%d",localtime());
2022     my $filterbranch = shift || 
2023                         ((C4::Context->preference('IndependantBranches') 
2024                              && C4::Context->userenv 
2025                              && C4::Context->userenv->{flags} % 2 !=1 
2026                              && C4::Context->userenv->{branch})
2027                          ? C4::Context->userenv->{branch}
2028                          : "");  
2029     my $query = "
2030        SELECT count(borrowernumber) as n,borrowernumber
2031        FROM old_issues
2032        WHERE returndate < ?
2033          AND borrowernumber IS NOT NULL 
2034     "; 
2035     my @query_params;
2036     push @query_params, $date;
2037     if ($filterbranch){
2038         $query.="   AND branchcode = ?";
2039         push @query_params, $filterbranch;
2040     }    
2041     $query.=" GROUP BY borrowernumber ";
2042     warn $query if $debug;
2043     my $sth = $dbh->prepare($query);
2044     $sth->execute(@query_params);
2045     my @results;
2046
2047     while ( my $data = $sth->fetchrow_hashref ) {
2048         push @results, $data;
2049     }
2050     return \@results;
2051 }
2052
2053 =head2 GetBorrowersNamesAndLatestIssue
2054
2055   $results = &GetBorrowersNamesAndLatestIssueList(@borrowernumbers)
2056
2057 this function get borrowers Names and surnames and Issue information.
2058
2059 I<@borrowernumbers> is an array which all elements are borrowernumbers.
2060 This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
2061
2062 =cut
2063
2064 sub GetBorrowersNamesAndLatestIssue {
2065     my $dbh  = C4::Context->dbh;
2066     my @borrowernumbers=@_;  
2067     my $query = "
2068        SELECT surname,lastname, phone, email,max(timestamp)
2069        FROM borrowers 
2070          LEFT JOIN issues ON borrowers.borrowernumber=issues.borrowernumber
2071        GROUP BY borrowernumber
2072    ";
2073     my $sth = $dbh->prepare($query);
2074     $sth->execute;
2075     my $results = $sth->fetchall_arrayref({});
2076     return $results;
2077 }
2078
2079 =head2 DebarMember
2080
2081   my $success = DebarMember( $borrowernumber );
2082
2083 marks a Member as debarred, and therefore unable to checkout any more
2084 items.
2085
2086 return :
2087 true on success, false on failure
2088
2089 =cut
2090
2091 sub DebarMember {
2092     my $borrowernumber = shift;
2093
2094     return unless defined $borrowernumber;
2095     return unless $borrowernumber =~ /^\d+$/;
2096
2097     return ModMember( borrowernumber => $borrowernumber,
2098                       debarred       => 1 );
2099     
2100 }
2101
2102 =head2 ModPrivacy
2103
2104 =over 4
2105
2106 my $success = ModPrivacy( $borrowernumber, $privacy );
2107
2108 Update the privacy of a patron.
2109
2110 return :
2111 true on success, false on failure
2112
2113 =back
2114
2115 =cut
2116
2117 sub ModPrivacy {
2118     my $borrowernumber = shift;
2119     my $privacy = shift;
2120     return unless defined $borrowernumber;
2121     return unless $borrowernumber =~ /^\d+$/;
2122
2123     return ModMember( borrowernumber => $borrowernumber,
2124                       privacy        => $privacy );
2125 }
2126
2127 =head2 AddMessage
2128
2129   AddMessage( $borrowernumber, $message_type, $message, $branchcode );
2130
2131 Adds a message to the messages table for the given borrower.
2132
2133 Returns:
2134   True on success
2135   False on failure
2136
2137 =cut
2138
2139 sub AddMessage {
2140     my ( $borrowernumber, $message_type, $message, $branchcode ) = @_;
2141
2142     my $dbh  = C4::Context->dbh;
2143
2144     if ( ! ( $borrowernumber && $message_type && $message && $branchcode ) ) {
2145       return;
2146     }
2147
2148     my $query = "INSERT INTO messages ( borrowernumber, branchcode, message_type, message ) VALUES ( ?, ?, ?, ? )";
2149     my $sth = $dbh->prepare($query);
2150     $sth->execute( $borrowernumber, $branchcode, $message_type, $message );
2151
2152     return 1;
2153 }
2154
2155 =head2 GetMessages
2156
2157   GetMessages( $borrowernumber, $type );
2158
2159 $type is message type, B for borrower, or L for Librarian.
2160 Empty type returns all messages of any type.
2161
2162 Returns all messages for the given borrowernumber
2163
2164 =cut
2165
2166 sub GetMessages {
2167     my ( $borrowernumber, $type, $branchcode ) = @_;
2168
2169     if ( ! $type ) {
2170       $type = '%';
2171     }
2172
2173     my $dbh  = C4::Context->dbh;
2174
2175     my $query = "SELECT
2176                   branches.branchname,
2177                   messages.*,
2178                   message_date,
2179                   messages.branchcode LIKE '$branchcode' AS can_delete
2180                   FROM messages, branches
2181                   WHERE borrowernumber = ?
2182                   AND message_type LIKE ?
2183                   AND messages.branchcode = branches.branchcode
2184                   ORDER BY message_date DESC";
2185     my $sth = $dbh->prepare($query);
2186     $sth->execute( $borrowernumber, $type ) ;
2187     my @results;
2188
2189     while ( my $data = $sth->fetchrow_hashref ) {
2190         my $d = C4::Dates->new( $data->{message_date}, 'iso' );
2191         $data->{message_date_formatted} = $d->output;
2192         push @results, $data;
2193     }
2194     return \@results;
2195
2196 }
2197
2198 =head2 GetMessages
2199
2200   GetMessagesCount( $borrowernumber, $type );
2201
2202 $type is message type, B for borrower, or L for Librarian.
2203 Empty type returns all messages of any type.
2204
2205 Returns the number of messages for the given borrowernumber
2206
2207 =cut
2208
2209 sub GetMessagesCount {
2210     my ( $borrowernumber, $type, $branchcode ) = @_;
2211
2212     if ( ! $type ) {
2213       $type = '%';
2214     }
2215
2216     my $dbh  = C4::Context->dbh;
2217
2218     my $query = "SELECT COUNT(*) as MsgCount FROM messages WHERE borrowernumber = ? AND message_type LIKE ?";
2219     my $sth = $dbh->prepare($query);
2220     $sth->execute( $borrowernumber, $type ) ;
2221     my @results;
2222
2223     my $data = $sth->fetchrow_hashref;
2224     my $count = $data->{'MsgCount'};
2225
2226     return $count;
2227 }
2228
2229
2230
2231 =head2 DeleteMessage
2232
2233   DeleteMessage( $message_id );
2234
2235 =cut
2236
2237 sub DeleteMessage {
2238     my ( $message_id ) = @_;
2239
2240     my $dbh = C4::Context->dbh;
2241
2242     my $query = "DELETE FROM messages WHERE message_id = ?";
2243     my $sth = $dbh->prepare($query);
2244     $sth->execute( $message_id );
2245
2246 }
2247
2248 END { }    # module clean-up code here (global destructor)
2249
2250 1;
2251
2252 __END__
2253
2254 =head1 AUTHOR
2255
2256 Koha Team
2257
2258 =cut