add registration fees on registration renewal if needed
[koha.git] / C4 / Members.pm
1 package C4::Members;
2
3 # Copyright 2000-2003 Katipo Communications
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20
21 use strict;
22 require Exporter;
23 use C4::Context;
24 use C4::Date;
25 use Digest::MD5 qw(md5_base64);
26 use Date::Calc qw/Today Add_Delta_YM/;
27 use C4::Log; # logaction
28 use C4::Overdues;
29 use C4::Reserves;
30 use C4::Accounts;
31
32 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
33
34 BEGIN {
35         $VERSION = 3.01;
36         $debug = $ENV{DEBUG} || 0;
37 }
38
39 =head1 NAME
40
41 C4::Members - Perl Module containing convenience functions for member handling
42
43 =head1 SYNOPSIS
44
45 use C4::Members;
46
47 =head1 DESCRIPTION
48
49 This module contains routines for adding, modifying and deleting members/patrons/borrowers 
50
51 =head1 FUNCTIONS
52
53 =over 2
54
55 =cut
56
57 @ISA = qw(Exporter);
58
59 #Get data
60 push @EXPORT, qw(
61   &SearchMember 
62   &GetMemberDetails
63   &GetMember
64   
65   &GetGuarantees 
66   
67   &GetMemberIssuesAndFines
68   &GetPendingIssues
69   &GetAllIssues
70   
71   &get_institutions 
72   &getzipnamecity 
73   &getidcity
74    
75   &GetAge 
76   &GetCities 
77   &GetRoadTypes 
78   &GetRoadTypeDetails 
79   &GetSortDetails
80   &GetTitles    
81   
82   &GetMemberAccountRecords
83   &GetBorNotifyAcctRecord
84   
85   &GetborCatFromCatType 
86   &GetBorrowercategory
87   
88   
89   &GetBorrowersWhoHaveNotBorrowedSince
90   &GetBorrowersWhoHaveNeverBorrowed
91   &GetBorrowersWithIssuesHistoryOlderThan
92   
93   &GetExpiryDate
94 );
95
96 #Modify data
97 push @EXPORT, qw(
98   &ModMember
99   &changepassword
100 );
101   
102 #Delete data
103 push @EXPORT, qw(
104   &DelMember
105 );
106
107 #Insert data
108 push @EXPORT, qw(
109   &AddMember
110   &add_member_orgs
111   &MoveMemberToDeleted
112   &ExtendMemberSubscriptionTo 
113 );
114
115 #Check data
116 push @EXPORT, qw(
117   &checkuniquemember 
118   &checkuserpassword
119         &Check_Userid
120   &fixEthnicity
121   &ethnicitycategories 
122   &fixup_cardnumber
123         &checkcardnumber
124 );
125
126 =item SearchMember
127
128   ($count, $borrowers) = &SearchMember($searchstring, $type,$category_type,$filter,$showallbranches);
129
130 Looks up patrons (borrowers) by name.
131
132 BUGFIX 499: C<$type> is now used to determine type of search.
133 if $type is "simple", search is performed on the first letter of the
134 surname only.
135
136 $category_type is used to get a specified type of user. 
137 (mainly adults when creating a child.)
138
139 C<$searchstring> is a space-separated list of search terms. Each term
140 must match the beginning a borrower's surname, first name, or other
141 name.
142
143 C<$filter> is assumed to be a list of elements to filter results on
144
145 C<$showallbranches> is used in IndependantBranches Context to display all branches results.
146
147 C<&SearchMember> returns a two-element list. C<$borrowers> is a
148 reference-to-array; each element is a reference-to-hash, whose keys
149 are the fields of the C<borrowers> table in the Koha database.
150 C<$count> is the number of elements in C<$borrowers>.
151
152 =cut
153
154 #'
155 #used by member enquiries from the intranet
156 #called by member.pl and circ/circulation.pl
157 sub SearchMember {
158         my ($searchstring, $orderby, $type,$category_type,$filter,$showallbranches ) = @_;
159         my $dbh   = C4::Context->dbh;
160         my $query = "";
161         my $count;
162         my @data;
163         my @bind = ();
164         
165         # this is used by circulation everytime a new borrowers cardnumber is scanned
166         # so we can check an exact match first, if that works return, otherwise do the rest
167         $query = "SELECT * FROM borrowers
168                 LEFT JOIN categories ON borrowers.categorycode=categories.categorycode
169                 ";
170         my $sth = $dbh->prepare("$query WHERE cardnumber = ?");
171         $sth->execute($searchstring);
172         my $data = $sth->fetchall_arrayref({});
173         if (@$data){
174                 return ( scalar(@$data), $data );
175         }
176     $sth->finish;
177
178     if ( $type eq "simple" )    # simple search for one letter only
179     {
180         $query .= ($category_type ? " AND category_type = ".$dbh->quote($category_type) : ""); 
181         $query .= " WHERE (surname LIKE ? OR cardnumber like ?) ";
182         if (C4::Context->preference("IndependantBranches") && !$showallbranches){
183           if (C4::Context->userenv && C4::Context->userenv->{flags}!=1 && C4::Context->userenv->{'branch'}){
184             $query.=" AND borrowers.branchcode =".$dbh->quote(C4::Context->userenv->{'branch'}) unless (C4::Context->userenv->{'branch'} eq "insecure");
185           }
186         }
187         $query.=" ORDER BY $orderby";
188         @bind = ("$searchstring%","$searchstring");
189     }
190     else    # advanced search looking in surname, firstname and othernames
191     {
192         @data  = split( ' ', $searchstring );
193         $count = @data;
194         $query .= " WHERE ";
195         if (C4::Context->preference("IndependantBranches") && !$showallbranches){
196           if (C4::Context->userenv && C4::Context->userenv->{flags}!=1 && C4::Context->userenv->{'branch'}){
197             $query.=" borrowers.branchcode =".$dbh->quote(C4::Context->userenv->{'branch'})." AND " unless (C4::Context->userenv->{'branch'} eq "insecure");
198           }      
199                 }     
200                 $query.="((surname LIKE ? OR surname LIKE ?
201                                 OR firstname  LIKE ? OR firstname LIKE ?
202                                 OR othernames LIKE ? OR othernames LIKE ?)
203                 " .
204                 ($category_type?" AND category_type = ".$dbh->quote($category_type):"");
205                 @bind = (
206                         "$data[0]%", "% $data[0]%", "$data[0]%", "% $data[0]%",
207                         "$data[0]%", "% $data[0]%"
208                 );
209                 for ( my $i = 1 ; $i < $count ; $i++ ) {
210                         $query = $query . " AND (" . " surname LIKE ? OR surname LIKE ?
211                                 OR firstname  LIKE ? OR firstname LIKE ?
212                                 OR othernames LIKE ? OR othernames LIKE ?)";
213                         push( @bind,
214                                 "$data[$i]%",   "% $data[$i]%", "$data[$i]%",
215                                 "% $data[$i]%", "$data[$i]%",   "% $data[$i]%" );
216
217                         # FIXME - .= <<EOT;
218                 }
219                 $query = $query . ") OR cardnumber LIKE ?
220                 order by $orderby";
221                 push( @bind, $searchstring );
222
223                 # FIXME - .= <<EOT;
224     }
225
226     $sth = $dbh->prepare($query);
227
228         $debug and print STDERR "Q $orderby : $query\n";
229     $sth->execute(@bind);
230     my @results;
231     $data = $sth->fetchall_arrayref({});
232
233     $sth->finish;
234     return ( scalar(@$data), $data );
235 }
236
237 =head2 GetMemberDetails
238
239 ($borrower, $flags) = &GetMemberDetails($borrowernumber, $cardnumber);
240
241 Looks up a patron and returns information about him or her. If
242 C<$borrowernumber> is true (nonzero), C<&GetMemberDetails> looks
243 up the borrower by number; otherwise, it looks up the borrower by card
244 number.
245
246 C<$borrower> is a reference-to-hash whose keys are the fields of the
247 borrowers table in the Koha database. In addition,
248 C<$borrower-E<gt>{flags}> is a hash giving more detailed information
249 about the patron. Its keys act as flags :
250
251     if $borrower->{flags}->{LOST} {
252         # Patron's card was reported lost
253     }
254
255 Each flag has a C<message> key, giving a human-readable explanation of
256 the flag. If the state of a flag means that the patron should not be
257 allowed to borrow any more books, then it will have a C<noissues> key
258 with a true value.
259
260 The possible flags are:
261
262 =head3 CHARGES
263
264 =over 4
265
266 =item Shows the patron's credit or debt, if any.
267
268 =back
269
270 =head3 GNA
271
272 =over 4
273
274 =item (Gone, no address.) Set if the patron has left without giving a
275 forwarding address.
276
277 =back
278
279 =head3 LOST
280
281 =over 4
282
283 =item Set if the patron's card has been reported as lost.
284
285 =back
286
287 =head3 DBARRED
288
289 =over 4
290
291 =item Set if the patron has been debarred.
292
293 =back
294
295 =head3 NOTES
296
297 =over 4
298
299 =item Any additional notes about the patron.
300
301 =back
302
303 =head3 ODUES
304
305 =over 4
306
307 =item Set if the patron has overdue items. This flag has several keys:
308
309 C<$flags-E<gt>{ODUES}{itemlist}> is a reference-to-array listing the
310 overdue items. Its elements are references-to-hash, each describing an
311 overdue item. The keys are selected fields from the issues, biblio,
312 biblioitems, and items tables of the Koha database.
313
314 C<$flags-E<gt>{ODUES}{itemlist}> is a string giving a text listing of
315 the overdue items, one per line.
316
317 =back
318
319 =head3 WAITING
320
321 =over 4
322
323 =item Set if any items that the patron has reserved are available.
324
325 C<$flags-E<gt>{WAITING}{itemlist}> is a reference-to-array listing the
326 available items. Each element is a reference-to-hash whose keys are
327 fields from the reserves table of the Koha database.
328
329 =back
330
331 =cut
332
333 sub GetMemberDetails {
334     my ( $borrowernumber, $cardnumber ) = @_;
335     my $dbh = C4::Context->dbh;
336     my $query;
337     my $sth;
338     if ($borrowernumber) {
339         $sth = $dbh->prepare("select borrowers.*,category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where  borrowernumber=?");
340         $sth->execute($borrowernumber);
341     }
342     elsif ($cardnumber) {
343         $sth = $dbh->prepare("select borrowers.*,category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where cardnumber=?");
344         $sth->execute($cardnumber);
345     }
346     else {
347         return undef;
348     }
349     my $borrower = $sth->fetchrow_hashref;
350     my ($amount) = GetMemberAccountRecords( $borrowernumber);
351     $borrower->{'amountoutstanding'} = $amount;
352         # FIXME - patronflags calls GetMemberAccountRecords... just have patronflags return $amount
353     my $flags = patronflags( $borrower);
354     my $accessflagshash;
355
356     $sth = $dbh->prepare("select bit,flag from userflags");
357     $sth->execute;
358     while ( my ( $bit, $flag ) = $sth->fetchrow ) {
359         if ( $borrower->{'flags'} && $borrower->{'flags'} & 2**$bit ) {
360             $accessflagshash->{$flag} = 1;
361         }
362     }
363     $sth->finish;
364     $borrower->{'flags'}     = $flags;
365     $borrower->{'authflags'} = $accessflagshash;
366
367     # find out how long the membership lasts
368     $sth =
369       $dbh->prepare(
370         "select enrolmentperiod from categories where categorycode = ?");
371     $sth->execute( $borrower->{'categorycode'} );
372     my $enrolment = $sth->fetchrow;
373     $borrower->{'enrolmentperiod'} = $enrolment;
374     return ($borrower);    #, $flags, $accessflagshash);
375 }
376
377 =head2 patronflags
378
379  Not exported
380
381  NOTE!: If you change this function, be sure to update the POD for
382  &GetMemberDetails.
383
384  $flags = &patronflags($patron);
385
386  $flags->{CHARGES}
387         {message}    Message showing patron's credit or debt
388        {noissues}    Set if patron owes >$5.00
389          {GNA}            Set if patron gone w/o address
390         {message}    "Borrower has no valid address"
391         {noissues}    Set.
392         {LOST}        Set if patron's card reported lost
393         {message}    Message to this effect
394         {noissues}    Set.
395         {DBARRED}        Set is patron is debarred
396         {message}    Message to this effect
397         {noissues}    Set.
398          {NOTES}        Set if patron has notes
399         {message}    Notes about patron
400          {ODUES}        Set if patron has overdue books
401         {message}    "Yes"
402         {itemlist}    ref-to-array: list of overdue books
403         {itemlisttext}    Text list of overdue items
404          {WAITING}        Set if there are items available that the
405                 patron reserved
406         {message}    Message to this effect
407         {itemlist}    ref-to-array: list of available items
408
409 =cut
410 # FIXME rename this function.
411 sub patronflags {
412     my %flags;
413     my ( $patroninformation) = @_;
414     my $dbh=C4::Context->dbh;
415     my ($amount) = GetMemberAccountRecords( $patroninformation->{'borrowernumber'});
416     if ( $amount > 0 ) {
417         my %flaginfo;
418         my $noissuescharge = C4::Context->preference("noissuescharge");
419         $flaginfo{'message'} = sprintf "Patron owes \$%.02f", $amount;
420                 $flaginfo{'amount'} = sprintf "%.02f",$amount;
421         if ( $amount > $noissuescharge ) {
422             $flaginfo{'noissues'} = 1;
423         }
424         $flags{'CHARGES'} = \%flaginfo;
425     }
426     elsif ( $amount < 0 ) {
427         my %flaginfo;
428         $flaginfo{'message'} = sprintf "Patron has credit of \$%.02f", -$amount;
429         $flags{'CHARGES'} = \%flaginfo;
430     }
431     if (   $patroninformation->{'gonenoaddress'}
432         && $patroninformation->{'gonenoaddress'} == 1 )
433     {
434         my %flaginfo;
435         $flaginfo{'message'}  = 'Borrower has no valid address.';
436         $flaginfo{'noissues'} = 1;
437         $flags{'GNA'}         = \%flaginfo;
438     }
439     if ( $patroninformation->{'lost'} && $patroninformation->{'lost'} == 1 ) {
440         my %flaginfo;
441         $flaginfo{'message'}  = 'Borrower\'s card reported lost.';
442         $flaginfo{'noissues'} = 1;
443         $flags{'LOST'}        = \%flaginfo;
444     }
445     if (   $patroninformation->{'debarred'}
446         && $patroninformation->{'debarred'} == 1 )
447     {
448         my %flaginfo;
449         $flaginfo{'message'}  = 'Borrower is Debarred.';
450         $flaginfo{'noissues'} = 1;
451         $flags{'DBARRED'}     = \%flaginfo;
452     }
453     if (   $patroninformation->{'borrowernotes'}
454         && $patroninformation->{'borrowernotes'} )
455     {
456         my %flaginfo;
457         $flaginfo{'message'} = "$patroninformation->{'borrowernotes'}";
458         $flags{'NOTES'}      = \%flaginfo;
459     }
460     my ( $odues, $itemsoverdue ) =
461       checkoverdues( $patroninformation->{'borrowernumber'}, $dbh );
462     if ( $odues > 0 ) {
463         my %flaginfo;
464         $flaginfo{'message'}  = "Yes";
465         $flaginfo{'itemlist'} = $itemsoverdue;
466         foreach ( sort { $a->{'date_due'} cmp $b->{'date_due'} }
467             @$itemsoverdue )
468         {
469             $flaginfo{'itemlisttext'} .=
470               "$_->{'date_due'} $_->{'barcode'} $_->{'title'} \n";
471         }
472         $flags{'ODUES'} = \%flaginfo;
473     }
474     my @itemswaiting = C4::Reserves::GetReservesFromBorrowernumber( $patroninformation->{'borrowernumber'},'W' );
475     my $nowaiting = scalar @itemswaiting;
476     if ( $nowaiting > 0 ) {
477         my %flaginfo;
478         $flaginfo{'message'}  = "Reserved items available";
479         $flaginfo{'itemlist'} = \@itemswaiting;
480         $flags{'WAITING'}     = \%flaginfo;
481     }
482     return ( \%flags );
483 }
484
485
486 =item GetMember
487
488   $borrower = &GetMember($information, $type);
489
490 Looks up information about a patron (borrower) by either card number
491 ,firstname, or borrower number, depending on $type value.
492 If C<$type> == 'cardnumber', C<&GetBorrower>
493 searches by cardnumber then by firstname if not found in cardnumber; 
494 otherwise, it searches by borrowernumber.
495
496 C<&GetBorrower> returns a reference-to-hash whose keys are the fields of
497 the C<borrowers> table in the Koha database.
498
499 =cut
500
501 #'
502 sub GetMember {
503     my ( $information, $type ) = @_;
504     my $dbh = C4::Context->dbh;
505     my $sth;
506         my $select = "
507 SELECT borrowers.*, categories.category_type, categories.description
508 FROM borrowers 
509 LEFT JOIN categories on borrowers.categorycode=categories.categorycode 
510 ";
511         if ($type eq 'cardnumber' || $type eq 'firstname'|| $type eq 'userid'|| $type eq 'borrowernumber'){
512                 $information = uc $information;
513                 $sth = $dbh->prepare("$select WHERE $type=?");
514     } else {
515                 $sth = $dbh->prepare("$select WHERE borrowernumber=?");
516         }
517     $sth->execute($information);
518     my $data = $sth->fetchrow_hashref;
519     $sth->finish;
520     ($data) and return ($data);
521
522     if ($type eq 'cardnumber' || $type eq 'firstname') {    # otherwise, try with firstname
523                 $sth = $dbh->prepare("$select WHERE firstname like ?");
524                 $sth->execute($information);
525                 $data = $sth->fetchrow_hashref;
526                 $sth->finish;
527                 return ($data);
528     }
529         return undef;        
530 }
531
532 =item GetMemberIssuesAndFines
533
534   ($borrowed, $due, $fine) = &GetMemberIssuesAndFines($borrowernumber);
535
536 Returns aggregate data about items borrowed by the patron with the
537 given borrowernumber.
538
539 C<&GetMemberIssuesAndFines> returns a three-element array. C<$borrowed> is the
540 number of books the patron currently has borrowed. C<$due> is the
541 number of overdue items the patron currently has borrowed. C<$fine> is
542 the total fine currently due by the borrower.
543
544 =cut
545
546 #'
547 sub GetMemberIssuesAndFines {
548     my ( $borrowernumber ) = @_;
549     my $dbh   = C4::Context->dbh;
550     my $query =
551       "Select count(*) from issues where borrowernumber='$borrowernumber' and
552     returndate is NULL";
553
554     $debug and print $query, "\n";
555     my $sth = $dbh->prepare($query);
556     $sth->execute;
557     my $data = $sth->fetchrow_hashref;
558     $sth->finish;
559     $sth = $dbh->prepare(
560         "Select count(*) from issues where
561     borrowernumber='$borrowernumber' and date_due < now() and returndate is NULL"
562     );
563     $sth->execute;
564     my $data2 = $sth->fetchrow_hashref;
565     $sth->finish;
566     $sth = $dbh->prepare(
567         "Select sum(amountoutstanding) from accountlines where
568     borrowernumber='$borrowernumber'"
569     );
570     $sth->execute;
571     my $data3 = $sth->fetchrow_hashref;
572     $sth->finish;
573
574     return ( $data2->{'count(*)'}, $data->{'count(*)'},
575         $data3->{'sum(amountoutstanding)'} );
576 }
577
578 =head2
579
580 =item ModMember
581
582   &ModMember($borrowernumber);
583
584 Modify borrower's data
585
586 =cut
587
588 #'
589 sub ModMember {
590     my (%data) = @_;
591     my $dbh = C4::Context->dbh;
592     $data{'dateofbirth'}  = format_date_in_iso( $data{'dateofbirth' } ) if ($data{'dateofbirth' } );
593     $data{'dateexpiry'}   = format_date_in_iso( $data{ 'dateexpiry' } ) if ($data{ 'dateexpiry' } );
594     $data{'dateenrolled'} = format_date_in_iso( $data{'dateenrolled'} ) if ($data{'dateenrolled'} );
595     my $qborrower=$dbh->prepare("SHOW columns from borrowers");
596     $qborrower->execute;
597     my %hashborrowerfields;  
598     while (my ($field)=$qborrower->fetchrow){
599       $hashborrowerfields{$field}=1;
600     }  
601     my $query = "UPDATE borrowers SET \n";
602     my $sth;
603     my @parameters;  
604     
605     # test to know if you must update or not the borrower password
606     if ( $data{'password'} eq '****' ) {
607         delete $data{'password'};
608     } else {
609         $data{'password'} = md5_base64( $data{'password'} )  if ($data{'password'} ne "");
610         delete $data{'password'} if ($data{password} eq "");
611     }
612         foreach (keys %data)
613         { push @parameters,"$_ = ".$dbh->quote($data{$_}) if ($_ ne 'borrowernumber' and $_ ne 'flags' and $hashborrowerfields{$_}); }
614     $query .= join (',',@parameters) . "\n WHERE borrowernumber=? \n";
615         $debug and print STDERR "$query (executed w/ arg: $data{'borrowernumber'})";
616         $sth = $dbh->prepare($query);
617         $sth->execute($data{'borrowernumber'});
618         $sth->finish;
619
620 # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
621 # so when we update information for an adult we should check for guarantees and update the relevant part
622 # of their records, ie addresses and phone numbers
623     my $borrowercategory= GetBorrowercategory( $data{'category_type'} );
624     if ( $borrowercategory->{'category_type'} eq ('A' || 'S') ) {
625         # is adult check guarantees;
626         UpdateGuarantees(%data);
627     }
628     &logaction(C4::Context->userenv->{'number'},"MEMBERS","MODIFY",$data{'borrowernumber'},"") 
629         if C4::Context->preference("BorrowersLog");
630 }
631
632
633 =head2
634
635 =item AddMember
636
637   $borrowernumber = &AddMember(%borrower);
638
639 insert new borrower into table
640 Returns the borrowernumber
641
642 =cut
643
644 #'
645 sub AddMember {
646     my (%data) = @_;
647     my $dbh = C4::Context->dbh;
648     $data{'userid'} = '' unless $data{'password'};
649     $data{'password'} = md5_base64( $data{'password'} ) if $data{'password'};
650     $data{'dateofbirth'}  = format_date_in_iso( $data{'dateofbirth'} );
651     $data{'dateenrolled'} = format_date_in_iso( $data{'dateenrolled'});
652     $data{'dateexpiry'}   = format_date_in_iso( $data{'dateexpiry'}  );
653         # This query should be rewritten to use "?" at execute.
654     my $query =
655         "insert into borrowers set cardnumber=" . $dbh->quote( $data{'cardnumber'} )
656       . ",surname="     . $dbh->quote( $data{'surname'} )
657       . ",firstname="   . $dbh->quote( $data{'firstname'} )
658       . ",title="               . $dbh->quote( $data{'title'} )
659       . ",othernames="  . $dbh->quote( $data{'othernames'} )
660       . ",initials="    . $dbh->quote( $data{'initials'} )
661       . ",streetnumber=". $dbh->quote( $data{'streetnumber'} )
662       . ",streettype="  . $dbh->quote( $data{'streettype'} )
663       . ",address="     . $dbh->quote( $data{'address'} )
664       . ",address2="    . $dbh->quote( $data{'address2'} )
665       . ",zipcode="     . $dbh->quote( $data{'zipcode'} )
666       . ",city="                . $dbh->quote( $data{'city'} )
667       . ",phone="               . $dbh->quote( $data{'phone'} )
668       . ",email="               . $dbh->quote( $data{'email'} )
669       . ",mobile="              . $dbh->quote( $data{'mobile'} )
670       . ",phonepro="    . $dbh->quote( $data{'phonepro'} )
671       . ",opacnote="    . $dbh->quote( $data{'opacnote'} )
672       . ",guarantorid=" . $dbh->quote( $data{'guarantorid'} )
673       . ",dateofbirth=" . $dbh->quote( $data{'dateofbirth'} )
674       . ",branchcode="  . $dbh->quote( $data{'branchcode'} )
675       . ",categorycode=" . $dbh->quote( $data{'categorycode'} )
676       . ",dateenrolled=" . $dbh->quote( $data{'dateenrolled'} )
677       . ",contactname=" . $dbh->quote( $data{'contactname'} )
678       . ",borrowernotes=" . $dbh->quote( $data{'borrowernotes'} )
679       . ",dateexpiry="  . $dbh->quote( $data{'dateexpiry'} )
680       . ",contactnote=" . $dbh->quote( $data{'contactnote'} )
681       . ",B_address="   . $dbh->quote( $data{'B_address'} )
682       . ",B_zipcode="   . $dbh->quote( $data{'B_zipcode'} )
683       . ",B_city="              . $dbh->quote( $data{'B_city'} )
684       . ",B_phone="     . $dbh->quote( $data{'B_phone'} )
685       . ",B_email="     . $dbh->quote( $data{'B_email'} )
686       . ",password="    . $dbh->quote( $data{'password'} )
687       . ",userid="              . $dbh->quote( $data{'userid'} )
688       . ",sort1="               . $dbh->quote( $data{'sort1'} )
689       . ",sort2="               . $dbh->quote( $data{'sort2'} )
690       . ",contacttitle=" . $dbh->quote( $data{'contacttitle'} )
691       . ",emailpro="    . $dbh->quote( $data{'emailpro'} )
692       . ",contactfirstname=" . $dbh->quote( $data{'contactfirstname'} )
693           . ",sex="             . $dbh->quote( $data{'sex'} )
694           . ",fax="             . $dbh->quote( $data{'fax'} )
695       . ",relationship=" . $dbh->quote( $data{'relationship'} )
696       . ",B_streetnumber=" . $dbh->quote( $data{'B_streetnumber'} )
697       . ",B_streettype=" . $dbh->quote( $data{'B_streettype'} )
698       . ",gonenoaddress=" . $dbh->quote( $data{'gonenoaddress'} )
699       . ",lost="                . $dbh->quote( $data{'lost'} )
700       . ",debarred="    . $dbh->quote( $data{'debarred'} )
701       . ",ethnicity="   . $dbh->quote( $data{'ethnicity'} )
702       . ",ethnotes="    . $dbh->quote( $data{'ethnotes'} );
703     my $sth = $dbh->prepare($query);
704 #       print "Executing SQL: $query\n";
705     $sth->execute;
706     $sth->finish;
707     $data{'borrowernumber'} = $dbh->{'mysql_insertid'};
708     
709     &logaction(C4::Context->userenv->{'number'},"MEMBERS","CREATE",$data{'borrowernumber'},"") 
710         if C4::Context->preference("BorrowersLog");
711     
712     # check for enrollment fee & add it if needed
713     $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
714     $sth->execute($data{'categorycode'});
715     my ($enrolmentfee) = $sth->fetchrow;
716     if ($enrolmentfee) {
717         # insert fee in patron debts
718         manualinvoice($data{'borrowernumber'}, '', '', 'A', $enrolmentfee);
719     }
720     return $data{'borrowernumber'};
721 }
722
723 sub Check_Userid {
724         my ($uid,$member) = @_;
725         my $dbh = C4::Context->dbh;
726     # Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
727     # Then we need to tell the user and have them create a new one.
728     my $sth =
729       $dbh->prepare(
730         "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
731     $sth->execute( $uid, $member );
732     if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
733         return 0;
734     }
735         else {
736                 return 1;
737         }
738 }
739
740
741 sub changepassword {
742     my ( $uid, $member, $digest ) = @_;
743     my $dbh = C4::Context->dbh;
744
745 #Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
746 #Then we need to tell the user and have them create a new one.
747     my $sth =
748       $dbh->prepare(
749         "SELECT * FROM borrowers WHERE userid=? AND borrowernumber != ?");
750     $sth->execute( $uid, $member );
751     if ( ( $uid ne '' ) && ( my $row = $sth->fetchrow_hashref ) ) {
752         return 0;
753     }
754     else {
755         #Everything is good so we can update the information.
756         $sth =
757           $dbh->prepare(
758             "update borrowers set userid=?, password=? where borrowernumber=?");
759         $sth->execute( $uid, $digest, $member );
760         return 1;
761     }
762     
763     &logaction(C4::Context->userenv->{'number'},"MEMBERS","CHANGE PASS",$member,"") 
764         if C4::Context->preference("BorrowersLog");
765 }
766
767
768
769 =item fixup_cardnumber
770
771 Warning: The caller is responsible for locking the members table in write
772 mode, to avoid database corruption.
773
774 =cut
775
776 use vars qw( @weightings );
777 my @weightings = ( 8, 4, 6, 3, 5, 2, 1 );
778
779 sub fixup_cardnumber ($) {
780     my ($cardnumber) = @_;
781     my $autonumber_members = C4::Context->boolean_preference('autoMemberNum');
782     $autonumber_members = 0 unless defined $autonumber_members;
783
784     # Find out whether member numbers should be generated
785     # automatically. Should be either "1" or something else.
786     # Defaults to "0", which is interpreted as "no".
787
788     #     if ($cardnumber !~ /\S/ && $autonumber_members) {
789     if ($autonumber_members) {
790         my $dbh = C4::Context->dbh;
791         if ( C4::Context->preference('checkdigit') eq 'katipo' ) {
792
793             # if checkdigit is selected, calculate katipo-style cardnumber.
794             # otherwise, just use the max()
795             # purpose: generate checksum'd member numbers.
796             # We'll assume we just got the max value of digits 2-8 of member #'s
797             # from the database and our job is to increment that by one,
798             # determine the 1st and 9th digits and return the full string.
799             my $sth =
800               $dbh->prepare(
801                 "select max(substring(borrowers.cardnumber,2,7)) from borrowers"
802               );
803             $sth->execute;
804
805             my $data = $sth->fetchrow_hashref;
806             $cardnumber = $data->{'max(substring(borrowers.cardnumber,2,7))'};
807             $sth->finish;
808             if ( !$cardnumber ) {    # If DB has no values,
809                 $cardnumber = 1000000;    # start at 1000000
810             }
811             else {
812                 $cardnumber += 1;
813             }
814
815             my $sum = 0;
816             for ( my $i = 0 ; $i < 8 ; $i += 1 ) {
817
818                 # read weightings, left to right, 1 char at a time
819                 my $temp1 = $weightings[$i];
820
821                 # sequence left to right, 1 char at a time
822                 my $temp2 = substr( $cardnumber, $i, 1 );
823
824                 # mult each char 1-7 by its corresponding weighting
825                 $sum += $temp1 * $temp2;
826             }
827
828             my $rem = ( $sum % 11 );
829             $rem = 'X' if $rem == 10;
830
831             $cardnumber = "V$cardnumber$rem";
832         }
833         else {
834
835      # MODIFIED BY JF: mysql4.1 allows casting as an integer, which is probably
836      # better. I'll leave the original in in case it needs to be changed for you
837             my $sth =
838               $dbh->prepare(
839                 "select max(cast(cardnumber as signed)) from borrowers");
840
841       #my $sth=$dbh->prepare("select max(borrowers.cardnumber) from borrowers");
842
843             $sth->execute;
844
845             my ($result) = $sth->fetchrow;
846             $sth->finish;
847             $cardnumber = $result + 1;
848         }
849     }
850     return $cardnumber;
851 }
852
853 =head2 GetGuarantees
854
855   ($num_children, $children_arrayref) = &GetGuarantees($parent_borrno);
856   $child0_cardno = $children_arrayref->[0]{"cardnumber"};
857   $child0_borrno = $children_arrayref->[0]{"borrowernumber"};
858
859 C<&GetGuarantees> takes a borrower number (e.g., that of a patron
860 with children) and looks up the borrowers who are guaranteed by that
861 borrower (i.e., the patron's children).
862
863 C<&GetGuarantees> returns two values: an integer giving the number of
864 borrowers guaranteed by C<$parent_borrno>, and a reference to an array
865 of references to hash, which gives the actual results.
866
867 =cut
868
869 #'
870 sub GetGuarantees {
871     my ($borrowernumber) = @_;
872     my $dbh              = C4::Context->dbh;
873     my $sth              =
874       $dbh->prepare(
875 "select cardnumber,borrowernumber, firstname, surname from borrowers where guarantorid=?"
876       );
877     $sth->execute($borrowernumber);
878
879     my @dat;
880     my $data = $sth->fetchall_arrayref({}); 
881     $sth->finish;
882     return ( scalar(@$data), $data );
883 }
884
885 =head2 UpdateGuarantees
886
887   &UpdateGuarantees($parent_borrno);
888   
889
890 C<&UpdateGuarantees> borrower data for an adulte and updates all the guarantees
891 with the modified information
892
893 =cut
894
895 #'
896 sub UpdateGuarantees {
897     my (%data) = @_;
898     my $dbh = C4::Context->dbh;
899     my ( $count, $guarantees ) = GetGuarantees( $data{'borrowernumber'} );
900     for ( my $i = 0 ; $i < $count ; $i++ ) {
901
902         # FIXME
903         # It looks like the $i is only being returned to handle walking through
904         # the array, which is probably better done as a foreach loop.
905         #
906         my $guaquery = qq|UPDATE borrowers 
907                           SET address='$data{'address'}',fax='$data{'fax'}',
908                               B_city='$data{'B_city'}',mobile='$data{'mobile'}',city='$data{'city'}',phone='$data{'phone'}'
909                           WHERE borrowernumber='$guarantees->[$i]->{'borrowernumber'}'
910                 |;
911         my $sth3 = $dbh->prepare($guaquery);
912         $sth3->execute;
913         $sth3->finish;
914     }
915 }
916 =head2 GetPendingIssues
917
918   ($count, $issues) = &GetPendingIssues($borrowernumber);
919
920 Looks up what the patron with the given borrowernumber has borrowed.
921
922 C<&GetPendingIssues> returns a two-element array. C<$issues> is a
923 reference-to-array, where each element is a reference-to-hash; the
924 keys are the fields from the C<issues>, C<biblio>, and C<items> tables
925 in the Koha database. C<$count> is the number of elements in
926 C<$issues>.
927
928 =cut
929
930 #'
931 sub GetPendingIssues {
932     my ($borrowernumber) = @_;
933     my $dbh              = C4::Context->dbh;
934
935     my $sth              = $dbh->prepare(
936    "SELECT * FROM issues 
937       LEFT JOIN items ON issues.itemnumber=items.itemnumber
938       LEFT JOIN biblio ON     items.biblionumber=biblio.biblionumber 
939       LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
940     WHERE
941       borrowernumber=? 
942       AND returndate IS NULL
943     ORDER BY issues.issuedate"
944     );
945     $sth->execute($borrowernumber);
946     my $data = $sth->fetchall_arrayref({});
947     my $today = POSIX::strftime("%Y%m%d", localtime);
948     foreach( @$data ) {
949         my $datedue = $_->{'date_due'};
950         $datedue =~ s/-//g;
951         if ( $datedue < $today ) {
952             $_->{'overdue'} = 1;
953         }
954     }
955     $sth->finish;
956     return ( scalar(@$data), $data );
957 }
958
959 =head2 GetAllIssues
960
961   ($count, $issues) = &GetAllIssues($borrowernumber, $sortkey, $limit);
962
963 Looks up what the patron with the given borrowernumber has borrowed,
964 and sorts the results.
965
966 C<$sortkey> is the name of a field on which to sort the results. This
967 should be the name of a field in the C<issues>, C<biblio>,
968 C<biblioitems>, or C<items> table in the Koha database.
969
970 C<$limit> is the maximum number of results to return.
971
972 C<&GetAllIssues> returns a two-element array. C<$issues> is a
973 reference-to-array, where each element is a reference-to-hash; the
974 keys are the fields from the C<issues>, C<biblio>, C<biblioitems>, and
975 C<items> tables of the Koha database. C<$count> is the number of
976 elements in C<$issues>
977
978 =cut
979
980 #'
981 sub GetAllIssues {
982     my ( $borrowernumber, $order, $limit ) = @_;
983
984     #FIXME: sanity-check order and limit
985     my $dbh   = C4::Context->dbh;
986     my $count = 0;
987     my $query =
988   "Select *,items.timestamp AS itemstimestamp from 
989   issues 
990   LEFT JOIN items on items.itemnumber=issues.itemnumber
991   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
992   LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
993   where borrowernumber=? 
994   order by $order";
995     if ( $limit != 0 ) {
996         $query .= " limit $limit";
997     }
998
999     #print $query;
1000     my $sth = $dbh->prepare($query);
1001     $sth->execute($borrowernumber);
1002     my @result;
1003     my $i = 0;
1004     while ( my $data = $sth->fetchrow_hashref ) {
1005         $result[$i] = $data;
1006         $i++;
1007         $count++;
1008     }
1009
1010     # get all issued items for borrowernumber from oldissues table
1011     # large chunk of older issues data put into table oldissues
1012     # to speed up db calls for issuing items
1013     if ( C4::Context->preference("ReadingHistory") ) {
1014         my $query2 = "SELECT * FROM oldissues
1015                       LEFT JOIN items ON items.itemnumber=oldissues.itemnumber
1016                       LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber
1017                       LEFT JOIN biblioitems ON items.biblioitemnumber=biblioitems.biblioitemnumber
1018                       WHERE borrowernumber=? 
1019                       ORDER BY $order";
1020         if ( $limit != 0 ) {
1021             $limit = $limit - $count;
1022             $query2 .= " limit $limit";
1023         }
1024
1025         my $sth2 = $dbh->prepare($query2);
1026         $sth2->execute($borrowernumber);
1027
1028         while ( my $data2 = $sth2->fetchrow_hashref ) {
1029             $result[$i] = $data2;
1030             $i++;
1031         }
1032         $sth2->finish;
1033     }
1034     $sth->finish;
1035
1036     return ( $i, \@result );
1037 }
1038
1039
1040 =head2 GetMemberAccountRecords
1041
1042   ($total, $acctlines, $count) = &GetMemberAccountRecords($borrowernumber);
1043
1044 Looks up accounting data for the patron with the given borrowernumber.
1045
1046 C<&GetMemberAccountRecords> returns a three-element array. C<$acctlines> is a
1047 reference-to-array, where each element is a reference-to-hash; the
1048 keys are the fields of the C<accountlines> table in the Koha database.
1049 C<$count> is the number of elements in C<$acctlines>. C<$total> is the
1050 total amount outstanding for all of the account lines.
1051
1052 =cut
1053
1054 #'
1055 sub GetMemberAccountRecords {
1056     my ($borrowernumber,$date) = @_;
1057     my $dbh = C4::Context->dbh;
1058     my @acctlines;
1059     my $numlines = 0;
1060     my $strsth      = qq(
1061                         SELECT * 
1062                         FROM accountlines 
1063                         WHERE borrowernumber=?);
1064     my @bind = ($borrowernumber);
1065     if ($date && $date ne ''){
1066             $strsth.=" AND date < ? ";
1067             push(@bind,$date);
1068     }
1069     $strsth.=" ORDER BY date desc,timestamp DESC";
1070     my $sth= $dbh->prepare( $strsth );
1071     $sth->execute( @bind );
1072     my $total = 0;
1073     while ( my $data = $sth->fetchrow_hashref ) {
1074         $acctlines[$numlines] = $data;
1075         $numlines++;
1076         $total += $data->{'amountoutstanding'};
1077     }
1078     $sth->finish;
1079     return ( $total, \@acctlines,$numlines);
1080 }
1081
1082 =head2 GetBorNotifyAcctRecord
1083
1084   ($count, $acctlines, $total) = &GetBorNotifyAcctRecord($params,$notifyid);
1085
1086 Looks up accounting data for the patron with the given borrowernumber per file number.
1087
1088 (FIXME - I'm not at all sure what this is about.)
1089
1090 C<&GetBorNotifyAcctRecord> returns a three-element array. C<$acctlines> is a
1091 reference-to-array, where each element is a reference-to-hash; the
1092 keys are the fields of the C<accountlines> table in the Koha database.
1093 C<$count> is the number of elements in C<$acctlines>. C<$total> is the
1094 total amount outstanding for all of the account lines.
1095
1096 =cut
1097
1098 sub GetBorNotifyAcctRecord {
1099     my ( $borrowernumber, $notifyid ) = @_;
1100     my $dbh = C4::Context->dbh;
1101     my @acctlines;
1102     my $numlines = 0;
1103     my $sth = $dbh->prepare(
1104             "SELECT * 
1105                 FROM accountlines 
1106                 WHERE borrowernumber=? 
1107                     AND notify_id=? 
1108                     AND (accounttype='FU' OR accounttype='N' OR accounttype='M'OR accounttype='A'OR accounttype='F'OR accounttype='L' OR accounttype='IP' OR accounttype='CH' OR accounttype='RE' OR accounttype='RL')
1109                     AND amountoutstanding != '0' 
1110                 ORDER BY notify_id,accounttype
1111                 ");
1112     $sth->execute( $borrowernumber, $notifyid );
1113     my $total = 0;
1114     while ( my $data = $sth->fetchrow_hashref ) {
1115         $acctlines[$numlines] = $data;
1116         $numlines++;
1117         $total += $data->{'amountoutstanding'};
1118     }
1119     $sth->finish;
1120     return ( $total, \@acctlines, $numlines );
1121 }
1122
1123 =head2 checkuniquemember (OUEST-PROVENCE)
1124
1125   $result = &checkuniquemember($collectivity,$surname,$categorycode,$firstname,$dateofbirth);
1126
1127 Checks that a member exists or not in the database.
1128
1129 C<&result> is 1 (=exist) or 0 (=does not exist)
1130 C<&collectivity> is 1 (= we add a collectivity) or 0 (= we add a physical member)
1131 C<&surname> is the surname
1132 C<&categorycode> is from categorycode table
1133 C<&firstname> is the firstname (only if collectivity=0)
1134 C<&dateofbirth> is the date of birth (only if collectivity=0)
1135
1136 =cut
1137
1138 sub checkuniquemember {
1139     my ( $collectivity, $surname, $firstname, $dateofbirth ) = @_;
1140     my $dbh = C4::Context->dbh;
1141     my $request;
1142     if ($collectivity) {
1143
1144 #                               $request="select count(*) from borrowers where surname=? and categorycode=?";
1145         $request =
1146           "select borrowernumber,categorycode from borrowers where surname=? ";
1147     }
1148     else {
1149
1150 #                               $request="select count(*) from borrowers where surname=? and categorycode=? and firstname=? and dateofbirth=?";
1151         $request =
1152 "select borrowernumber,categorycode from borrowers where surname=?  and firstname=? and dateofbirth=?";
1153     }
1154     my $sth = $dbh->prepare($request);
1155     if ($collectivity) {
1156         $sth->execute( uc($surname) );
1157     }
1158     else {
1159         $sth->execute( uc($surname), ucfirst($firstname), $dateofbirth );
1160     }
1161     my @data = $sth->fetchrow;
1162     if ( $data[0] ) {
1163         $sth->finish;
1164         return $data[0], $data[1];
1165
1166         #
1167     }
1168     else {
1169         $sth->finish;
1170         return 0;
1171     }
1172 }
1173
1174 sub checkcardnumber {
1175         my ($cardnumber,$borrowernumber) = @_;
1176         my $dbh = C4::Context->dbh;
1177         my $query = "SELECT * FROM borrowers WHERE cardnumber=?";
1178         $query .= " AND borrowernumber <> ?" if ($borrowernumber);
1179   my $sth = $dbh->prepare($query);
1180   if ($borrowernumber) {
1181    $sth->execute($cardnumber,$borrowernumber);
1182   } else { 
1183          $sth->execute($cardnumber);
1184   } 
1185         if (my $data= $sth->fetchrow_hashref()){
1186                 return 1;
1187         }
1188         else {
1189                 return 0;
1190         }
1191         $sth->finish();
1192 }  
1193
1194
1195 =head2 getzipnamecity (OUEST-PROVENCE)
1196
1197 take all info from table city for the fields city and  zip
1198 check for the name and the zip code of the city selected
1199
1200 =cut
1201
1202 sub getzipnamecity {
1203     my ($cityid) = @_;
1204     my $dbh      = C4::Context->dbh;
1205     my $sth      =
1206       $dbh->prepare(
1207         "select city_name,city_zipcode from cities where cityid=? ");
1208     $sth->execute($cityid);
1209     my @data = $sth->fetchrow;
1210     return $data[0], $data[1];
1211 }
1212
1213
1214 =head2 getdcity (OUEST-PROVENCE)
1215
1216 recover cityid  with city_name condition
1217
1218 =cut
1219
1220 sub getidcity {
1221     my ($city_name) = @_;
1222     my $dbh = C4::Context->dbh;
1223     my $sth = $dbh->prepare("select cityid from cities where city_name=? ");
1224     $sth->execute($city_name);
1225     my $data = $sth->fetchrow;
1226     return $data;
1227 }
1228
1229
1230 =head2 GetExpiryDate 
1231
1232   $expirydate = GetExpiryDate($categorycode, $dateenrolled);
1233 process expiry date given a date and a categorycode
1234
1235 =cut
1236 sub GetExpiryDate {
1237     my ( $categorycode, $dateenrolled ) = @_;
1238     my $dbh = C4::Context->dbh;
1239     my $sth =
1240       $dbh->prepare(
1241         "select enrolmentperiod from categories where categorycode=?");
1242     $sth->execute($categorycode);
1243     my ($enrolmentperiod) = $sth->fetchrow;
1244     $enrolmentperiod = 12 unless ($enrolmentperiod);
1245     my @date=split /-/,format_date_in_iso($dateenrolled);
1246     @date=Add_Delta_YM($date[0],$date[1],$date[2],0,$enrolmentperiod);
1247     return sprintf("%04d-%02d-%02d",$date[0],$date[1],$date[2]);
1248 }
1249
1250 =head2 checkuserpassword (OUEST-PROVENCE)
1251
1252 check for the password and login are not used
1253 return the number of record 
1254 0=> NOT USED 1=> USED
1255
1256 =cut
1257
1258 sub checkuserpassword {
1259     my ( $borrowernumber, $userid, $password ) = @_;
1260     $password = md5_base64($password);
1261     my $dbh = C4::Context->dbh;
1262     my $sth =
1263       $dbh->prepare(
1264 "Select count(*) from borrowers where borrowernumber !=? and userid =? and password=? "
1265       );
1266     $sth->execute( $borrowernumber, $userid, $password );
1267     my $number_rows = $sth->fetchrow;
1268     return $number_rows;
1269
1270 }
1271
1272 =head2 GetborCatFromCatType
1273
1274   ($codes_arrayref, $labels_hashref) = &GetborCatFromCatType();
1275
1276 Looks up the different types of borrowers in the database. Returns two
1277 elements: a reference-to-array, which lists the borrower category
1278 codes, and a reference-to-hash, which maps the borrower category codes
1279 to category descriptions.
1280
1281 =cut
1282
1283 #'
1284 sub GetborCatFromCatType {
1285     my ( $category_type, $action ) = @_;
1286     my $dbh     = C4::Context->dbh;
1287     my $request = qq|   SELECT categorycode,description 
1288                         FROM categories 
1289                         $action
1290                         ORDER BY categorycode|;
1291     my $sth = $dbh->prepare($request);
1292     if ($action) {
1293         $sth->execute($category_type);
1294     }
1295     else {
1296         $sth->execute();
1297     }
1298
1299     my %labels;
1300     my @codes;
1301
1302     while ( my $data = $sth->fetchrow_hashref ) {
1303         push @codes, $data->{'categorycode'};
1304         $labels{ $data->{'categorycode'} } = $data->{'description'};
1305     }
1306     $sth->finish;
1307     return ( \@codes, \%labels );
1308 }
1309
1310 =head2 GetBorrowercategory
1311
1312   $hashref = &GetBorrowercategory($categorycode);
1313
1314 Given the borrower's category code, the function returns the corresponding
1315 data hashref for a comprehensive information display.
1316
1317 =cut
1318
1319 sub GetBorrowercategory {
1320     my ($catcode) = @_;
1321     my $dbh       = C4::Context->dbh;
1322     my $sth       =
1323       $dbh->prepare(
1324 "SELECT description,dateofbirthrequired,upperagelimit,category_type 
1325  FROM categories 
1326  WHERE categorycode = ?"
1327       );
1328     $sth->execute($catcode);
1329     my $data =
1330       $sth->fetchrow_hashref;
1331     $sth->finish();
1332     return $data;
1333 }    # sub getborrowercategory
1334
1335 =head2 ethnicitycategories
1336
1337   ($codes_arrayref, $labels_hashref) = &ethnicitycategories();
1338
1339 Looks up the different ethnic types in the database. Returns two
1340 elements: a reference-to-array, which lists the ethnicity codes, and a
1341 reference-to-hash, which maps the ethnicity codes to ethnicity
1342 descriptions.
1343
1344 =cut
1345
1346 #'
1347
1348 sub ethnicitycategories {
1349     my $dbh = C4::Context->dbh;
1350     my $sth = $dbh->prepare("Select code,name from ethnicity order by name");
1351     $sth->execute;
1352     my %labels;
1353     my @codes;
1354     while ( my $data = $sth->fetchrow_hashref ) {
1355         push @codes, $data->{'code'};
1356         $labels{ $data->{'code'} } = $data->{'name'};
1357     }
1358     $sth->finish;
1359     return ( \@codes, \%labels );
1360 }
1361
1362 =head2 fixEthnicity
1363
1364   $ethn_name = &fixEthnicity($ethn_code);
1365
1366 Takes an ethnicity code (e.g., "european" or "pi") and returns the
1367 corresponding descriptive name from the C<ethnicity> table in the
1368 Koha database ("European" or "Pacific Islander").
1369
1370 =cut
1371
1372 #'
1373
1374 sub fixEthnicity {
1375     my $ethnicity = shift;
1376     return unless $ethnicity;
1377     my $dbh       = C4::Context->dbh;
1378     my $sth       = $dbh->prepare("Select name from ethnicity where code = ?");
1379     $sth->execute($ethnicity);
1380     my $data = $sth->fetchrow_hashref;
1381     $sth->finish;
1382     return $data->{'name'};
1383 }    # sub fixEthnicity
1384
1385 =head2 GetAge
1386
1387   $dateofbirth,$date = &GetAge($date);
1388
1389 this function return the borrowers age with the value of dateofbirth
1390
1391 =cut
1392
1393 #'
1394 sub GetAge{
1395     my ( $date, $date_ref ) = @_;
1396
1397     if ( not defined $date_ref ) {
1398         $date_ref = sprintf( '%04d-%02d-%02d', Today() );
1399     }
1400
1401     my ( $year1, $month1, $day1 ) = split /-/, $date;
1402     my ( $year2, $month2, $day2 ) = split /-/, $date_ref;
1403
1404     my $age = $year2 - $year1;
1405     if ( $month1 . $day1 > $month2 . $day2 ) {
1406         $age--;
1407     }
1408
1409     return $age;
1410 }    # sub get_age
1411
1412 =head2 get_institutions
1413   $insitutions = get_institutions();
1414
1415 Just returns a list of all the borrowers of type I, borrownumber and name
1416
1417 =cut
1418
1419 #'
1420 sub get_institutions {
1421     my $dbh = C4::Context->dbh();
1422     my $sth =
1423       $dbh->prepare(
1424 "SELECT borrowernumber,surname FROM borrowers WHERE categorycode=? ORDER BY surname"
1425       );
1426     $sth->execute('I');
1427     my %orgs;
1428     while ( my $data = $sth->fetchrow_hashref() ) {
1429         $orgs{ $data->{'borrowernumber'} } = $data;
1430     }
1431     $sth->finish();
1432     return ( \%orgs );
1433
1434 }    # sub get_institutions
1435
1436 =head2 add_member_orgs
1437
1438   add_member_orgs($borrowernumber,$borrowernumbers);
1439
1440 Takes a borrowernumber and a list of other borrowernumbers and inserts them into the borrowers_to_borrowers table
1441
1442 =cut
1443
1444 #'
1445 sub add_member_orgs {
1446     my ( $borrowernumber, $otherborrowers ) = @_;
1447     my $dbh   = C4::Context->dbh();
1448     my $query =
1449       "INSERT INTO borrowers_to_borrowers (borrower1,borrower2) VALUES (?,?)";
1450     my $sth = $dbh->prepare($query);
1451     foreach my $otherborrowernumber (@$otherborrowers) {
1452         $sth->execute( $borrowernumber, $otherborrowernumber );
1453     }
1454     $sth->finish();
1455
1456 }    # sub add_member_orgs
1457
1458 =head2 GetCities (OUEST-PROVENCE)
1459
1460   ($id_cityarrayref, $city_hashref) = &GetCities();
1461
1462 Looks up the different city and zip in the database. Returns two
1463 elements: a reference-to-array, which lists the zip city
1464 codes, and a reference-to-hash, which maps the name of the city.
1465 WHERE =>OUEST PROVENCE OR EXTERIEUR
1466
1467 =cut
1468
1469 sub GetCities {
1470
1471     #my ($type_city) = @_;
1472     my $dbh   = C4::Context->dbh;
1473     my $query = qq|SELECT cityid,city_zipcode,city_name 
1474                 FROM cities 
1475                 ORDER BY city_name|;
1476     my $sth = $dbh->prepare($query);
1477
1478     #$sth->execute($type_city);
1479     $sth->execute();
1480     my %city;
1481     my @id;
1482     #    insert empty value to create a empty choice in cgi popup
1483     push @id, " ";
1484     $city{""} = "";
1485     while ( my $data = $sth->fetchrow_hashref ) {
1486         push @id, $data->{'city_zipcode'}."|".$data->{'city_name'};
1487         $city{ $data->{'city_zipcode'}."|".$data->{'city_name'} } = $data->{'city_name'};
1488     }
1489
1490 #test to know if the table contain some records if no the function return nothing
1491     my $id = @id;
1492     $sth->finish;
1493     if ( $id eq 0 ) {
1494         return ();
1495     }
1496     else {
1497         unshift( @id, "" );
1498         return ( \@id, \%city );
1499     }
1500 }
1501
1502 =head2 GetSortDetails (OUEST-PROVENCE)
1503
1504   ($lib) = &GetSortDetails($category,$sortvalue);
1505
1506 Returns the authorized value  details
1507 C<&$lib>return value of authorized value details
1508 C<&$sortvalue>this is the value of authorized value 
1509 C<&$category>this is the value of authorized value category
1510
1511 =cut
1512
1513 sub GetSortDetails {
1514     my ( $category, $sortvalue ) = @_;
1515     my $dbh   = C4::Context->dbh;
1516     my $query = qq|SELECT lib 
1517                 FROM authorised_values 
1518                 WHERE category=?
1519                 AND authorised_value=? |;
1520     my $sth = $dbh->prepare($query);
1521     $sth->execute( $category, $sortvalue );
1522     my $lib = $sth->fetchrow;
1523     return ($lib);
1524 }
1525
1526 =head2 DeleteBorrower 
1527
1528   () = &DeleteBorrower($member);
1529
1530 delete all data fo borrowers and add record to deletedborrowers table
1531 C<&$member>this is the borrowernumber
1532
1533 =cut
1534
1535 sub MoveMemberToDeleted {
1536     my ($member) = @_;
1537     my $dbh = C4::Context->dbh;
1538     my $query;
1539     $query = qq|SELECT * 
1540                   FROM borrowers 
1541                   WHERE borrowernumber=?|;
1542     my $sth = $dbh->prepare($query);
1543     $sth->execute($member);
1544     my @data = $sth->fetchrow_array;
1545     $sth->finish;
1546     $sth =
1547       $dbh->prepare( "INSERT INTO deletedborrowers VALUES ("
1548           . ( "?," x ( scalar(@data) - 1 ) )
1549           . "?)" );
1550     $sth->execute(@data);
1551     $sth->finish;
1552 }
1553
1554 =head2 DelMember
1555
1556 DelMember($borrowernumber);
1557
1558 This function remove directly a borrower whitout writing it on deleteborrower.
1559 + Deletes reserves for the borrower
1560
1561 =cut
1562
1563 sub DelMember {
1564     my $dbh            = C4::Context->dbh;
1565     my $borrowernumber = shift;
1566         warn "in delmember with $borrowernumber";
1567     return unless $borrowernumber;    # borrowernumber is mandatory.
1568
1569     my $query = qq|DELETE 
1570                   FROM  reserves 
1571                   WHERE borrowernumber=?|;
1572     my $sth = $dbh->prepare($query);
1573     $sth->execute($borrowernumber);
1574     $sth->finish;
1575     $query = "
1576        DELETE
1577        FROM borrowers
1578        WHERE borrowernumber = ?
1579    ";
1580     $sth = $dbh->prepare($query);
1581     $sth->execute($borrowernumber);
1582     $sth->finish;
1583     &logaction(C4::Context->userenv->{'number'},"MEMBERS","DELETE",$borrowernumber,"") 
1584         if C4::Context->preference("BorrowersLog");
1585     return $sth->rows;
1586 }
1587
1588 =head2 ExtendMemberSubscriptionTo (OUEST-PROVENCE)
1589
1590 $date= ExtendMemberSubscriptionTo($borrowerid, $date);
1591 Extending the subscription to a given date or to the expiry date calculated on local date.
1592 returns date 
1593 =cut
1594
1595 sub ExtendMemberSubscriptionTo {
1596     my ( $borrowerid,$date) = @_;
1597     my $dbh = C4::Context->dbh;
1598     my $borrower = GetMember($borrowerid,'borrowernumber');
1599     unless ($date){
1600       $date=POSIX::strftime("%Y-%m-%d",localtime(time));
1601       $date = GetExpiryDate( $borrower->{'categorycode'}, $date );
1602     }
1603     my $sth = $dbh->do(<<EOF);
1604 UPDATE borrowers 
1605 SET  dateexpiry='$date' 
1606 WHERE borrowernumber='$borrowerid'
1607 EOF
1608     # add enrolmentfee if needed
1609     $sth = $dbh->prepare("SELECT enrolmentfee FROM categories WHERE categorycode=?");
1610     $sth->execute($borrower->{'categorycode'});
1611     my ($enrolmentfee) = $sth->fetchrow;
1612     if ($enrolmentfee) {
1613         # insert fee in patron debts
1614         manualinvoice($borrower->{'borrowernumber'}, '', '', 'A', $enrolmentfee);
1615     }
1616     return $date if ($sth);
1617     return 0;
1618 }
1619
1620 =head2 GetRoadTypes (OUEST-PROVENCE)
1621
1622   ($idroadtypearrayref, $roadttype_hashref) = &GetRoadTypes();
1623
1624 Looks up the different road type . Returns two
1625 elements: a reference-to-array, which lists the id_roadtype
1626 codes, and a reference-to-hash, which maps the road type of the road .
1627
1628
1629 =cut
1630
1631 sub GetRoadTypes {
1632     my $dbh   = C4::Context->dbh;
1633     my $query = qq|
1634 SELECT roadtypeid,road_type 
1635 FROM roadtype 
1636 ORDER BY road_type|;
1637     my $sth = $dbh->prepare($query);
1638     $sth->execute();
1639     my %roadtype;
1640     my @id;
1641
1642     #    insert empty value to create a empty choice in cgi popup
1643
1644     while ( my $data = $sth->fetchrow_hashref ) {
1645
1646         push @id, $data->{'roadtypeid'};
1647         $roadtype{ $data->{'roadtypeid'} } = $data->{'road_type'};
1648     }
1649
1650 #test to know if the table contain some records if no the function return nothing
1651     my $id = @id;
1652     $sth->finish;
1653     if ( $id eq 0 ) {
1654         return ();
1655     }
1656     else {
1657         unshift( @id, "" );
1658         return ( \@id, \%roadtype );
1659     }
1660 }
1661
1662
1663
1664 =head2 GetTitles (OUEST-PROVENCE)
1665
1666   ($borrowertitle)= &GetTitles();
1667
1668 Looks up the different title . Returns array  with all borrowers title
1669
1670 =cut
1671
1672 sub GetTitles {
1673     my @borrowerTitle = split /,|\|/,C4::Context->preference('BorrowersTitles');
1674     unshift( @borrowerTitle, "" );
1675     return ( \@borrowerTitle);
1676     }
1677
1678
1679
1680 =head2 GetRoadTypeDetails (OUEST-PROVENCE)
1681
1682   ($roadtype) = &GetRoadTypeDetails($roadtypeid);
1683
1684 Returns the description of roadtype
1685 C<&$roadtype>return description of road type
1686 C<&$roadtypeid>this is the value of roadtype s
1687
1688 =cut
1689
1690 sub GetRoadTypeDetails {
1691     my ($roadtypeid) = @_;
1692     my $dbh          = C4::Context->dbh;
1693     my $query        = qq|
1694 SELECT road_type 
1695 FROM roadtype 
1696 WHERE roadtypeid=?|;
1697     my $sth = $dbh->prepare($query);
1698     $sth->execute($roadtypeid);
1699     my $roadtype = $sth->fetchrow;
1700     return ($roadtype);
1701 }
1702
1703 =head2 GetBorrowersWhoHaveNotBorrowedSince
1704
1705 &GetBorrowersWhoHaveNotBorrowedSince($date)
1706
1707 this function get all borrowers who haven't borrowed since the date given on input arg.
1708
1709 =cut
1710
1711 sub GetBorrowersWhoHaveNotBorrowedSince {
1712     my $date = shift;
1713     return unless $date;    # date is mandatory.
1714     my $dbh   = C4::Context->dbh;
1715     my $query = "
1716         SELECT borrowers.borrowernumber,max(timestamp)
1717         FROM   borrowers
1718           LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
1719         WHERE issues.borrowernumber IS NOT NULL
1720         GROUP BY borrowers.borrowernumber
1721    ";
1722     my $sth = $dbh->prepare($query);
1723     $sth->execute;
1724     my @results;
1725
1726     while ( my $data = $sth->fetchrow_hashref ) {
1727         push @results, $data;
1728     }
1729     return \@results;
1730 }
1731
1732 =head2 GetBorrowersWhoHaveNeverBorrowed
1733
1734 $results = &GetBorrowersWhoHaveNeverBorrowed
1735
1736 this function get all borrowers who have never borrowed.
1737
1738 I<$result> is a ref to an array which all elements are a hasref.
1739
1740 =cut
1741
1742 sub GetBorrowersWhoHaveNeverBorrowed {
1743     my $dbh   = C4::Context->dbh;
1744     my $query = "
1745         SELECT borrowers.borrowernumber,max(timestamp)
1746         FROM   borrowers
1747           LEFT JOIN issues ON borrowers.borrowernumber = issues.borrowernumber
1748         WHERE issues.borrowernumber IS NULL
1749    ";
1750     my $sth = $dbh->prepare($query);
1751     $sth->execute;
1752     my @results;
1753     while ( my $data = $sth->fetchrow_hashref ) {
1754         push @results, $data;
1755     }
1756     return \@results;
1757 }
1758
1759 =head2 GetBorrowersWithIssuesHistoryOlderThan
1760
1761 $results = &GetBorrowersWithIssuesHistoryOlderThan($date)
1762
1763 this function get all borrowers who has an issue history older than I<$date> given on input arg.
1764
1765 I<$result> is a ref to an array which all elements are a hashref.
1766 This hashref is containt the number of time this borrowers has borrowed before I<$date> and the borrowernumber.
1767
1768 =cut
1769
1770 sub GetBorrowersWithIssuesHistoryOlderThan {
1771     my $dbh  = C4::Context->dbh;
1772     my $date = shift;
1773     return unless $date;    # date is mandatory.
1774     my $query = "
1775        SELECT count(borrowernumber) as n,borrowernumber
1776        FROM issues
1777        WHERE returndate < ?
1778          AND borrowernumber IS NOT NULL 
1779        GROUP BY borrowernumber
1780    ";
1781     my $sth = $dbh->prepare($query);
1782     $sth->execute($date);
1783     my @results;
1784
1785     while ( my $data = $sth->fetchrow_hashref ) {
1786         push @results, $data;
1787     }
1788     return \@results;
1789 }
1790
1791 END { }    # module clean-up code here (global destructor)
1792
1793 1;
1794
1795 __END__
1796
1797 =back
1798
1799 =head1 AUTHOR
1800
1801 Koha Team
1802
1803 =cut