New set of routines for HEAD.
[koha.git] / C4 / Members.pm
1 # -*- tab-width: 8 -*-
2
3 package C4::Members;
4
5 # Copyright 2000-2003 Katipo Communications
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 with
19 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
20 # Suite 330, Boston, MA  02111-1307 USA
21
22 # $Id$
23
24 use strict;
25 require Exporter;
26 use C4::Context;
27 use Date::Manip;
28 use C4::Date;
29 use Digest::MD5 qw(md5_base64);
30 use Date::Calc qw/Today/;
31
32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
33
34 $VERSION = do { my @v = '$Revision$' =~ /\d+/g; shift(@v) . "." . join( "_", map { sprintf "%03d", $_ } @v ); };
35
36 =head1 NAME
37
38 C4::Members - Perl Module containing convenience functions for member handling
39
40 =head1 SYNOPSIS
41
42 use C4::Members;
43
44 =head1 DESCRIPTION
45
46 This module contains routines for adding, modifying and deleting members/patrons/borrowers 
47
48 =head1 FUNCTIONS
49
50 =over 2
51
52 =cut
53
54 #'
55
56 @ISA    = qw(Exporter);
57
58 @EXPORT = qw(
59 &allissues
60 &borrdata 
61 &borrdata2 
62 &BornameSearch 
63 &borrissues
64 &borrowercategories 
65
66 &changepassword  
67 &checkuniquemember 
68 &calcexpirydate 
69 &checkuserpassword
70 &ethnicitycategories get_institutions add_member_orgs
71 &fixEthnicity
72 &fixup_cardnumber 
73 &findguarantees 
74 &findguarantor 
75
76
77
78 &getmember 
79 &getzipnamecity 
80 &getidcity 
81 &getguarantordata 
82 &getcategorytype
83 &getboracctrecord
84 &getborrowercategory
85 &get_age 
86 &GetBorrowersFromSurname 
87 &GetBranchCodeFromBorrowers
88 &GetFlagsAndBranchFromBorrower
89 &GuarantornameSearch 
90
91 &NewBorrowerNumber   
92 &modmember 
93 &newmember 
94 );
95
96
97 =item BornameSearch
98
99   ($count, $borrowers) = &BornameSearch($env, $searchstring, $type);
100
101 Looks up patrons (borrowers) by name.
102
103 C<$env> is ignored.
104
105 BUGFIX 499: C<$type> is now used to determine type of search.
106 if $type is "simple", search is performed on the first letter of the
107 surname only.
108
109 C<$searchstring> is a space-separated list of search terms. Each term
110 must match the beginning a borrower's surname, first name, or other
111 name.
112
113 C<&BornameSearch> returns a two-element list. C<$borrowers> is a
114 reference-to-array; each element is a reference-to-hash, whose keys
115 are the fields of the C<borrowers> table in the Koha database.
116 C<$count> is the number of elements in C<$borrowers>.
117
118 =cut
119
120 #'
121 #used by member enquiries from the intranet
122 #called by member.pl
123 sub BornameSearch {
124     my ( $env, $searchstring, $orderby, $type ) = @_;
125     my $dbh   = C4::Context->dbh;
126     my $query = "";
127     my $count;
128     my @data;
129     my @bind = ();
130
131     if ( $type eq "simple" )    # simple search for one letter only
132     {
133         $query =
134           "Select * from borrowers where surname like ? order by $orderby";
135         @bind = ("$searchstring%");
136     }
137     else    # advanced search looking in surname, firstname and othernames
138     {
139         @data  = split( ' ', $searchstring );
140         $count = @data;
141         $query = "Select * from borrowers
142                 where ((surname like ? or surname like ?
143                 or firstname  like ? or firstname like ?
144                 or othernames like ? or othernames like ?)
145                 ";
146         @bind = (
147             "$data[0]%", "% $data[0]%", "$data[0]%", "% $data[0]%",
148             "$data[0]%", "% $data[0]%"
149         );
150         for ( my $i = 1 ; $i < $count ; $i++ ) {
151             $query = $query . " and (" . " surname like ? or surname like ?
152                         or firstname  like ? or firstname like ?
153                         or othernames like ? or othernames like ?)";
154             push( @bind,
155                 "$data[$i]%",   "% $data[$i]%", "$data[$i]%",
156                 "% $data[$i]%", "$data[$i]%",   "% $data[$i]%" );
157
158             # FIXME - .= <<EOT;
159         }
160         $query = $query . ") or cardnumber like ?
161                 order by $orderby";
162         push( @bind, $searchstring );
163
164         # FIXME - .= <<EOT;
165     }
166
167     my $sth = $dbh->prepare($query);
168
169     #   warn "Q $orderby : $query";
170     $sth->execute(@bind);
171     my @results;
172     my $cnt = $sth->rows;
173     while ( my $data = $sth->fetchrow_hashref ) {
174         push( @results, $data );
175     }
176
177     #  $sth->execute;
178     $sth->finish;
179     return ( $cnt, \@results );
180 }
181
182 =item getmember
183
184   $borrower = &getmember($cardnumber, $borrowernumber);
185
186 Looks up information about a patron (borrower) by either card number
187 or borrower number. If $borrowernumber is specified, C<&borrdata>
188 searches by borrower number; otherwise, it searches by card number.
189
190 C<&getmember> returns a reference-to-hash whose keys are the fields of
191 the C<borrowers> table in the Koha database.
192
193 =cut
194
195 =head3 GetFlagsAndBranchFromBorrower
196
197 =over 4
198
199 ($flags, $homebranch) = GetFlagsAndBranchFromBorrower($loggedinuser);
200
201 this function read on the database to get flags and homebranch for a user
202 given on input arg.
203
204 return : 
205 it returns the $flags & the homebranch in scalar context.
206
207 =back
208
209 =cut
210
211 sub GetFlagsAndBranchFromBorrower {
212     my $loggedinuser = @_;
213     my $dbh = C4::Context->dbh;
214     my $query = "
215        SELECT flags, branchcode
216        FROM   borrowers
217        WHERE  borrowernumber = ? 
218     ";
219     my $sth = $dbh->prepare($query);
220     $sth->execute($loggedinuser);
221
222     return $sth->fetchrow;
223 }
224
225
226 #'
227 sub getmember {
228     my ( $cardnumber, $bornum ) = @_;
229     $cardnumber = uc $cardnumber;
230     my $dbh = C4::Context->dbh;
231     my $sth;
232     if ( $bornum eq '' ) {
233         $sth = $dbh->prepare("Select * from borrowers where cardnumber=?");
234         $sth->execute($cardnumber);
235     }
236     else {
237         $sth = $dbh->prepare("Select * from borrowers where borrowernumber=?");
238         $sth->execute($bornum);
239     }
240     my $data = $sth->fetchrow_hashref;
241     $sth->finish;
242     if ($data) {
243         return ($data);
244     }
245     else {    # try with firstname
246         if ($cardnumber) {
247             my $sth =
248               $dbh->prepare("select * from borrowers where firstname=?");
249             $sth->execute($cardnumber);
250             my $data = $sth->fetchrow_hashref;
251             $sth->finish;
252             return ($data);
253         }
254     }
255     return undef;
256 }
257
258 =item borrdata
259
260   $borrower = &borrdata($cardnumber, $borrowernumber);
261
262 Looks up information about a patron (borrower) by either card number
263 or borrower number. If $borrowernumber is specified, C<&borrdata>
264 searches by borrower number; otherwise, it searches by card number.
265
266 C<&borrdata> returns a reference-to-hash whose keys are the fields of
267 the C<borrowers> table in the Koha database.
268
269 =cut
270
271 #'
272 sub borrdata {
273     my ( $cardnumber, $bornum ) = @_;
274     $cardnumber = uc $cardnumber;
275     my $dbh = C4::Context->dbh;
276     my $sth;
277     if ( $bornum eq '' ) {
278         $sth =
279           $dbh->prepare(
280 "Select borrowers.*,categories.category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where cardnumber=?"
281           );
282         $sth->execute($cardnumber);
283     }
284     else {
285         $sth =
286           $dbh->prepare(
287 "Select borrowers.*,categories.category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode where borrowernumber=?"
288           );
289         $sth->execute($bornum);
290     }
291     my $data = $sth->fetchrow_hashref;
292 #     warn "DATA" . $data->{category_type};
293     $sth->finish;
294     if ($data) {
295         return ($data);
296     }
297     else {    # try with firstname
298         if ($cardnumber) {
299             my $sth =
300               $dbh->prepare(
301 "Select borrowers.*,categories.category_type from borrowers left join categories on borrowers.categorycode=categories.categorycode  where firstname=?"
302               );
303             $sth->execute($cardnumber);
304             my $data = $sth->fetchrow_hashref;
305             $sth->finish;
306             return ($data);
307         }
308     }
309     return undef;
310 }
311
312 =item borrdata2
313
314   ($borrowed, $due, $fine) = &borrdata2($env, $borrowernumber);
315
316 Returns aggregate data about items borrowed by the patron with the
317 given borrowernumber.
318
319 C<$env> is ignored.
320
321 C<&borrdata2> returns a three-element array. C<$borrowed> is the
322 number of books the patron currently has borrowed. C<$due> is the
323 number of overdue items the patron currently has borrowed. C<$fine> is
324 the total fine currently due by the borrower.
325
326 =cut
327
328 #'
329 sub borrdata2 {
330     my ( $env, $bornum ) = @_;
331     my $dbh   = C4::Context->dbh;
332     my $query = "Select count(*) from issues where borrowernumber='$bornum' and
333     returndate is NULL";
334
335     # print $query;
336     my $sth = $dbh->prepare($query);
337     $sth->execute;
338     my $data = $sth->fetchrow_hashref;
339     $sth->finish;
340     $sth = $dbh->prepare(
341         "Select count(*) from issues where
342     borrowernumber='$bornum' and date_due < now() and returndate is NULL"
343     );
344     $sth->execute;
345     my $data2 = $sth->fetchrow_hashref;
346     $sth->finish;
347     $sth = $dbh->prepare(
348         "Select sum(amountoutstanding) from accountlines where
349     borrowernumber='$bornum'"
350     );
351     $sth->execute;
352     my $data3 = $sth->fetchrow_hashref;
353     $sth->finish;
354
355     return ( $data2->{'count(*)'}, $data->{'count(*)'},
356         $data3->{'sum(amountoutstanding)'} );
357 }
358
359 sub modmember {
360         my (%data) = @_;
361         my $dbh = C4::Context->dbh;
362         $data{'dateofbirth'}=format_date_in_iso($data{'dateofbirth'});
363         $data{'dateexpiry'}=format_date_in_iso($data{'dateexpiry'});
364         $data{'dateenrolled'}=format_date_in_iso($data{'dateenrolled'});
365 #       warn "num user".$data{'borrowernumber'};
366         my $query;
367         my $sth;
368         $data{'userid'}='' if ($data{'password'}eq '');
369         # test to know if u must update or not the borrower password
370         if ($data{'password'} eq '****'){
371                 
372                 $query="UPDATE borrowers SET 
373                 cardnumber  = ?,surname = ?,firstname = ?,title = ?,othernames = ?,initials = ?,
374                 streetnumber = ?,streettype = ?,address = ?,address2 = ?,city = ?,zipcode = ?,
375                 email = ?,phone = ?,mobile = ?,fax = ?,emailpro = ?,phonepro = ?,B_streetnumber = ?,
376                 B_streettype = ?,B_address = ?,B_city = ?,B_zipcode = ?,B_email = ?,B_phone = ?,dateofbirth = ?,branchcode = ?,
377                 categorycode = ?,dateenrolled = ?,dateexpiry = ?,gonenoaddress = ?,lost = ?,debarred = ?,contactname = ?,
378                 contactfirstname = ?,contacttitle = ?,guarantorid = ?,borrowernotes = ?,relationship =  ?,ethnicity = ?,
379                 ethnotes = ?,sex = ?,flags = ?,userid = ?,opacnote = ?,contactnote = ?,sort1 = ?,sort2 = ? 
380                 WHERE borrowernumber=$data{'borrowernumber'}";
381         $sth=$dbh->prepare($query);
382         $sth->execute(
383                 $data{'cardnumber'},$data{'surname'},
384                 $data{'firstname'},$data{'title'},
385                 $data{'othernames'},$data{'initials'},
386                 $data{'streetnumber'},$data{'streettype'},
387                 $data{'address'},$data{'address2'},
388                 $data{'city'},$data{'zipcode'},
389                 $data{'email'},$data{'phone'},
390                 $data{'mobile'},$data{'fax'},
391                 $data{'emailpro'},$data{'phonepro'},
392                 $data{'B_streetnumber'},$data{'B_streettype'},
393                 $data{'B_address'},$data{'B_city'},
394                 $data{'B_zipcode'},$data{'B_email'},$data{'B_phone'},
395                 $data{'dateofbirth'},$data{'branchcode'},
396                 $data{'categorycode'},$data{'dateenrolled'},
397                 $data{'dateexpiry'},$data{'gonenoaddress'},
398                 $data{'lost'},$data{'debarred'},
399                 $data{'contactname'},$data{'contactfirstname'},
400                 $data{'contacttitle'},$data{'guarantorid'},
401                 $data{'borrowernotes'},$data{'relationship'},
402                 $data{'ethnicity'},$data{'ethnotes'},
403                 $data{'sex'},
404                 $data{'flags'},$data{'userid'},
405                 $data{'opacnote'},$data{'contactnote'},
406                 $data{'sort1'},$data{'sort2'});
407         }
408         else{
409                 
410                 ($data{'password'}=md5_base64($data{'password'})) if ($data{'password'} ne '');
411                 $query="UPDATE borrowers SET 
412                 cardnumber  = ?,surname = ?,firstname = ?,title = ?,othernames = ?,initials = ?,
413                 streetnumber = ?,streettype = ?,address = ?,address2 = ?,city = ?,zipcode = ?,
414                 email = ?,phone = ?,mobile = ?,fax = ?,emailpro = ?,phonepro = ?,B_streetnumber = ?,
415                 B_streettype = ?,B_address = ?,B_city = ?,B_zipcode = ?,B_email = ?,B_phone = ?,dateofbirth = ?,branchcode = ?,
416                 categorycode = ?,dateenrolled = ?,dateexpiry = ?,gonenoaddress = ?,lost = ?,debarred = ?,contactname = ?,
417                 contactfirstname = ?,contacttitle = ?,guarantorid = ?,borrowernotes = ?,relationship =  ?,ethnicity = ?,
418                 ethnotes = ?,sex = ?,password = ?,flags = ?,userid = ?,opacnote = ?,contactnote = ?,sort1 = ?,sort2 = ? 
419                 WHERE borrowernumber=$data{'borrowernumber'}";
420         $sth=$dbh->prepare($query);
421         $sth->execute(
422                 $data{'cardnumber'},$data{'surname'},
423                 $data{'firstname'},$data{'title'},
424                 $data{'othernames'},$data{'initials'},
425                 $data{'streetnumber'},$data{'streettype'},
426                 $data{'address'},$data{'address2'},
427                 $data{'city'},$data{'zipcode'},
428                 $data{'email'},$data{'phone'},
429                 $data{'mobile'},$data{'fax'},
430                 $data{'emailpro'},$data{'phonepro'},
431                 $data{'B_streetnumber'},$data{'B_streettype'},
432                 $data{'B_address'},$data{'B_city'},
433                 $data{'B_zipcode'},$data{'B_email'},$data{'B_phone'},
434                 $data{'dateofbirth'},$data{'branchcode'},
435                 $data{'categorycode'},$data{'dateenrolled'},
436                 $data{'dateexpiry'},$data{'gonenoaddress'},
437                 $data{'lost'},$data{'debarred'},
438                 $data{'contactname'},$data{'contactfirstname'},
439                 $data{'contacttitle'},$data{'guarantorid'},
440                 $data{'borrowernotes'},$data{'relationship'},
441                 $data{'ethnicity'},$data{'ethnotes'},
442                 $data{'sex'},$data{'password'},
443                 $data{'flags'},$data{'userid'},
444                 $data{'opacnote'},$data{'contactnote'},
445                 $data{'sort1'},$data{'sort2'}
446                 );
447         }
448         $sth->finish;
449         # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
450         # so when we update information for an adult we should check for guarantees and update the relevant part
451         # of their records, ie addresses and phone numbers
452         my ($category_type,undef)=getcategorytype($data{'category_type'});
453         if ($category_type eq 'A' ){
454         
455         # is adult check guarantees;
456                 updateguarantees(%data);
457         
458         }
459
460
461 }
462
463 sub newmember {
464     my (%data) = @_;
465     my $dbh = C4::Context->dbh;
466     $data{'userid'} = '' unless $data{'password'};
467     $data{'password'} = md5_base64( $data{'password'} ) if $data{'password'};
468     $data{'dateofbirth'} = format_date_in_iso( $data{'dateofbirth'} );
469     $data{'dateenrolled'} = format_date_in_iso( $data{'dateenrolled'} );
470     $data{'dateexpiry'} = format_date_in_iso( $data{'dateexpiry'} );
471         my $query =
472         "insert into borrowers set cardnumber="
473       . $dbh->quote( $data{'cardnumber'} )
474       . ",surname="
475       . $dbh->quote( $data{'surname'} )
476       . ",firstname="
477       . $dbh->quote( $data{'firstname'} )
478       . ",title="
479       . $dbh->quote( $data{'title'} )
480       . ",othernames="
481       . $dbh->quote( $data{'othernames'} )
482       . ",initials="
483       . $dbh->quote( $data{'initials'} )
484       . ",streetnumber="
485       . $dbh->quote( $data{'streetnumber'} )
486       . ",streettype="
487       . $dbh->quote( $data{'streettype'} )
488       . ",address="
489       . $dbh->quote( $data{'address'} )
490       . ",address2="
491       . $dbh->quote( $data{'address2'} )
492       . ",zipcode="
493       . $dbh->quote( $data{'zipcode'} )
494       . ",city="
495       . $dbh->quote( $data{'city'} )
496       . ",phone="
497       . $dbh->quote( $data{'phone'} )
498       . ",email="
499       . $dbh->quote( $data{'email'} )
500       . ",mobile="
501       . $dbh->quote( $data{'mobile'} )
502       . ",phonepro="
503       . $dbh->quote( $data{'phonepro'} )
504       . ",opacnote="
505       . $dbh->quote( $data{'opacnote'} )
506       . ",guarantorid="
507       . $dbh->quote( $data{'guarantorid'} )
508       . ",dateofbirth="
509       . $dbh->quote( $data{'dateofbirth'} )
510       . ",branchcode="
511       . $dbh->quote( $data{'branchcode'} )
512       . ",categorycode="
513       . $dbh->quote( $data{'categorycode'} )
514       . ",dateenrolled="
515       . $dbh->quote( $data{'dateenrolled'} )
516       . ",contactname="
517       . $dbh->quote( $data{'contactname'} )
518       . ",borrowernotes="
519       . $dbh->quote( $data{'borrowernotes'} )
520       . ",dateexpiry="
521       . $dbh->quote( $data{'dateexpiry'} )
522       . ",contactnote="
523       . $dbh->quote( $data{'contactnote'} )
524       . ",B_address="
525       . $dbh->quote( $data{'B_address'} )
526       . ",B_zipcode="
527       . $dbh->quote( $data{'B_zipcode'} )
528       . ",B_city="
529       . $dbh->quote( $data{'B_city'} )
530       . ",B_phone="
531       . $dbh->quote( $data{'B_phone'} )
532       . ",B_email="
533       . $dbh->quote( $data{'B_email'}, )
534       . ",password="
535       . $dbh->quote( $data{'password'} )
536       . ",userid="
537       . $dbh->quote( $data{'userid'} )
538       . ",sort1="
539       . $dbh->quote( $data{'sort1'} )
540       . ",sort2="
541       . $dbh->quote( $data{'sort2'} )
542       . ",contacttitle="
543       . $dbh->quote( $data{'contacttitle'} )
544       . ",emailpro="
545       . $dbh->quote( $data{'emailpro'} )
546       . ",contactfirstname="
547       . $dbh->quote( $data{'contactfirstname'} ) 
548       . ",sex="
549       . $dbh->quote( $data{'sex'} ) 
550       . ",fax="
551       . $dbh->quote( $data{'fax'} )
552       . ",flags="
553       . $dbh->quote( $data{'flags'} )
554       . ",relationship="
555       . $dbh->quote( $data{'relationship'} )
556       . ",B_streetnumber="
557       . $dbh->quote( $data{'B_streetnumber'}) 
558       . ",B_streettype="
559       . $dbh->quote( $data{'B_streettype'})
560       . ",gonenoaddress="
561       . $dbh->quote( $data{'gonenoaddress'})    
562       . ",lost="
563       . $dbh->quote( $data{'lost'})                     
564       . ",debarred="
565       . $dbh->quote( $data{'debarred'})
566       . ",ethnicity="
567       . $dbh->quote( $data{'ethnicity'})
568       . ",ethnotes="
569       . $dbh->quote( $data{'ethnotes'});
570
571     my $sth = $dbh->prepare($query);
572     $sth->execute;
573     $sth->finish;
574     $data{'borrowernumber'} = $dbh->{'mysql_insertid'};
575     return $data{'borrowernumber'};
576 }
577
578 sub changepassword {
579     my ( $uid, $member, $digest ) = @_;
580     my $dbh = C4::Context->dbh;
581
582 #Make sure the userid chosen is unique and not theirs if non-empty. If it is not,
583 #Then we need to tell the user and have them create a new one.
584     my $sth =
585       $dbh->prepare(
586         "select * from borrowers where userid=? and borrowernumber != ?");
587     $sth->execute( $uid, $member );
588     if ( ( $uid ne '' ) && ( $sth->fetchrow ) ) {
589         return 0;
590     }
591     else {
592
593         #Everything is good so we can update the information.
594         $sth =
595           $dbh->prepare(
596             "update borrowers set userid=?, password=? where borrowernumber=?");
597         $sth->execute( $uid, $digest, $member );
598         return 1;
599     }
600 }
601
602 sub getmemberfromuserid {
603     my ($userid) = @_;
604     my $dbh      = C4::Context->dbh;
605     my $sth      = $dbh->prepare("select * from borrowers where userid=?");
606     $sth->execute($userid);
607     return $sth->fetchrow_hashref;
608 }
609
610 sub updateguarantees {
611     my (%data) = @_;
612     my $dbh = C4::Context->dbh;
613     my ( $count, $guarantees ) = findguarantees( $data{'borrowernumber'} );
614     for ( my $i = 0 ; $i < $count ; $i++ ) {
615
616         # FIXME
617         # It looks like the $i is only being returned to handle walking through
618         # the array, which is probably better done as a foreach loop.
619         #
620         my $guaquery =
621 "update borrowers set streetaddress='$data{'address'}',faxnumber='$data{'faxnumber'}',
622                 streetcity='$data{'streetcity'}',phoneday='$data{'phoneday'}',city='$data{'city'}',area='$data{'area'}',phone='$data{'phone'}'
623                 ,streetaddress='$data{'address'}'
624                 where borrowernumber='$guarantees->[$i]->{'borrowernumber'}'";
625         my $sth3 = $dbh->prepare($guaquery);
626         $sth3->execute;
627         $sth3->finish;
628     }
629 }
630 ################################################################################
631
632 =item fixup_cardnumber
633
634 Warning: The caller is responsible for locking the members table in write
635 mode, to avoid database corruption.
636
637 =cut
638
639 use vars qw( @weightings );
640 my @weightings = ( 8, 4, 6, 3, 5, 2, 1 );
641
642 sub fixup_cardnumber ($) {
643     my ($cardnumber) = @_;
644     my $autonumber_members = C4::Context->boolean_preference('autoMemberNum');
645     $autonumber_members = 0 unless defined $autonumber_members;
646
647     # Find out whether member numbers should be generated
648     # automatically. Should be either "1" or something else.
649     # Defaults to "0", which is interpreted as "no".
650
651     #     if ($cardnumber !~ /\S/ && $autonumber_members) {
652     if ($autonumber_members) {
653         my $dbh = C4::Context->dbh;
654         if ( C4::Context->preference('checkdigit') eq 'katipo' ) {
655
656             # if checkdigit is selected, calculate katipo-style cardnumber.
657             # otherwise, just use the max()
658             # purpose: generate checksum'd member numbers.
659             # We'll assume we just got the max value of digits 2-8 of member #'s
660             # from the database and our job is to increment that by one,
661             # determine the 1st and 9th digits and return the full string.
662             my $sth =
663               $dbh->prepare(
664                 "select max(substring(borrowers.cardnumber,2,7)) from borrowers"
665               );
666             $sth->execute;
667
668             my $data = $sth->fetchrow_hashref;
669             $cardnumber = $data->{'max(substring(borrowers.cardnumber,2,7))'};
670             $sth->finish;
671             if ( !$cardnumber ) {    # If DB has no values,
672                 $cardnumber = 1000000;    # start at 1000000
673             }
674             else {
675                 $cardnumber += 1;
676             }
677
678             my $sum = 0;
679             for ( my $i = 0 ; $i < 8 ; $i += 1 ) {
680
681                 # read weightings, left to right, 1 char at a time
682                 my $temp1 = $weightings[$i];
683
684                 # sequence left to right, 1 char at a time
685                 my $temp2 = substr( $cardnumber, $i, 1 );
686
687                 # mult each char 1-7 by its corresponding weighting
688                 $sum += $temp1 * $temp2;
689             }
690
691             my $rem = ( $sum % 11 );
692             $rem = 'X' if $rem == 10;
693
694             $cardnumber = "V$cardnumber$rem";
695         }
696         else {
697
698      # MODIFIED BY JF: mysql4.1 allows casting as an integer, which is probably
699      # better. I'll leave the original in in case it needs to be changed for you
700             my $sth =
701               $dbh->prepare(
702                 "select max(cast(cardnumber as signed)) from borrowers");
703
704       #my $sth=$dbh->prepare("select max(borrowers.cardnumber) from borrowers");
705
706             $sth->execute;
707
708             my ($result) = $sth->fetchrow;
709             $sth->finish;
710             $cardnumber = $result + 1;
711         }
712     }
713     return $cardnumber;
714 }
715
716 sub findguarantees {
717     my ($bornum) = @_;
718     my $dbh      = C4::Context->dbh;
719     my $sth      = $dbh->prepare(
720         "select cardnumber,borrowernumber from borrowers where
721   guarantorid=?"
722     );
723     $sth->execute($bornum);
724     my @dat;
725     my $i = 0;
726     while ( my $data = $sth->fetchrow_hashref ) {
727         $dat[$i] = $data;
728         $i++;
729     }
730     $sth->finish;
731     return ( $i, \@dat );
732 }
733
734 =item findguarantor
735
736   $guarantor = &findguarantor($borrower_no);
737   $guarantor_cardno = $guarantor->{"cardnumber"};
738   $guarantor_surname = $guarantor->{"surname"};
739   ...
740
741 C<&findguarantor> takes a borrower number (presumably that of a child
742 patron), finds the guarantor for C<$borrower_no> (the child's parent),
743 and returns the record for the guarantor.
744
745 C<&findguarantor> returns a reference-to-hash. Its keys are the fields
746 from the C<borrowers> database table;
747
748 =cut
749
750 #'
751 sub findguarantor {
752     my ($bornum) = @_;
753     my $dbh = C4::Context->dbh;
754     my $sth = $dbh->prepare("Select * from borrowers where borrowernumber=?");
755     $sth->execute($bornum);
756     my $data = $sth->fetchrow_hashref;
757     $sth->finish;
758     return ($data);
759 }
760
761 =item GuarantornameSearch
762
763   ($count, $borrowers) = &GuarantornameSearch($env, $searchstring, $type);
764
765 Looks up guarantor  by name.
766
767 C<$env> is ignored.
768
769 BUGFIX 499: C<$type> is now used to determine type of search.
770 if $type is "simple", search is performed on the first letter of the
771 surname only.
772
773 C<$searchstring> is a space-separated list of search terms. Each term
774 must match the beginning a borrower's surname, first name, or other
775 name.
776
777 C<&GuarantornameSearch> returns a two-element list. C<$borrowers> is a
778 reference-to-array; each element is a reference-to-hash, whose keys
779 are the fields of the C<borrowers> table in the Koha database.
780 C<$count> is the number of elements in C<$borrowers>.
781
782 return all info from guarantor =>only category_type A
783
784 =cut
785
786 #'
787 #used by member enquiries from the intranet
788 #called by guarantor_search.pl
789 sub GuarantornameSearch {
790     my ( $env, $searchstring, $orderby, $type ) = @_;
791     my $dbh   = C4::Context->dbh;
792     my $query = "";
793     my $count;
794     my @data;
795     my @bind = ();
796
797     if ( $type eq "simple" )    # simple search for one letter only
798     {
799         $query =
800 "Select * from borrowers,categories  where borrowers.categorycode=categories.categorycode and category_type='A'  and  surname like ? order by $orderby";
801         @bind = ("$searchstring%");
802     }
803     else    # advanced search looking in surname, firstname and othernames
804     {
805         @data  = split( ' ', $searchstring );
806         $count = @data;
807         $query = "Select * from borrowers,categories
808                 where ((surname like ? or surname like ?
809                 or firstname  like ? or firstname like ?
810                 or othernames like ? or othernames like ?) and borrowers.categorycode=categories.categorycode and category_type='A' 
811                 ";
812         @bind = (
813             "$data[0]%", "% $data[0]%", "$data[0]%", "% $data[0]%",
814             "$data[0]%", "% $data[0]%"
815         );
816         for ( my $i = 1 ; $i < $count ; $i++ ) {
817             $query = $query . " and (" . " surname like ? or surname like ?
818                         or firstname  like ? or firstname like ?
819                         or othernames like ? or othernames like ?)";
820             push( @bind,
821                 "$data[$i]%",   "% $data[$i]%", "$data[$i]%",
822                 "% $data[$i]%", "$data[$i]%",   "% $data[$i]%" );
823
824             # FIXME - .= <<EOT;
825         }
826         $query = $query . ") or cardnumber like ?
827                 order by $orderby";
828         push( @bind, $searchstring );
829
830         # FIXME - .= <<EOT;
831     }
832
833     my $sth = $dbh->prepare($query);
834     $sth->execute(@bind);
835     my @results;
836     my $cnt = $sth->rows;
837     while ( my $data = $sth->fetchrow_hashref ) {
838         push( @results, $data );
839     }
840
841     #  $sth->execute;
842     $sth->finish;
843     return ( $cnt, \@results );
844 }
845
846 =item NewBorrowerNumber
847
848   $num = &NewBorrowerNumber();
849
850 Allocates a new, unused borrower number, and returns it.
851
852 =cut
853
854 #'
855 # FIXME - This is identical to C4::Circulation::Borrower::NewBorrowerNumber.
856 # Pick one and stick with it. Preferably use the other one. This function
857 # doesn't belong in C4::Search.
858 sub NewBorrowerNumber {
859     my $dbh = C4::Context->dbh;
860     my $sth = $dbh->prepare("Select max(borrowernumber) from borrowers");
861     $sth->execute;
862     my $data = $sth->fetchrow_hashref;
863     $sth->finish;
864     $data->{'max(borrowernumber)'}++;
865     return ( $data->{'max(borrowernumber)'} );
866 }
867
868 =head2 borrissues
869
870   ($count, $issues) = &borrissues($borrowernumber);
871
872 Looks up what the patron with the given borrowernumber has borrowed.
873
874 C<&borrissues> returns a two-element array. C<$issues> is a
875 reference-to-array, where each element is a reference-to-hash; the
876 keys are the fields from the C<issues>, C<biblio>, and C<items> tables
877 in the Koha database. C<$count> is the number of elements in
878 C<$issues>.
879
880 =cut
881
882 #'
883 sub borrissues {
884     my ($bornum) = @_;
885     my $dbh      = C4::Context->dbh;
886     my $sth      = $dbh->prepare(
887         "Select * from issues,biblio,items where borrowernumber=?
888    and items.itemnumber=issues.itemnumber
889         and items.biblionumber=biblio.biblionumber
890         and issues.returndate is NULL order by date_due"
891     );
892     $sth->execute($bornum);
893     my @result;
894     while ( my $data = $sth->fetchrow_hashref ) {
895         push @result, $data;
896     }
897     $sth->finish;
898     return ( scalar(@result), \@result );
899 }
900
901 =head2 allissues
902
903   ($count, $issues) = &allissues($borrowernumber, $sortkey, $limit);
904
905 Looks up what the patron with the given borrowernumber has borrowed,
906 and sorts the results.
907
908 C<$sortkey> is the name of a field on which to sort the results. This
909 should be the name of a field in the C<issues>, C<biblio>,
910 C<biblioitems>, or C<items> table in the Koha database.
911
912 C<$limit> is the maximum number of results to return.
913
914 C<&allissues> returns a two-element array. C<$issues> is a
915 reference-to-array, where each element is a reference-to-hash; the
916 keys are the fields from the C<issues>, C<biblio>, C<biblioitems>, and
917 C<items> tables of the Koha database. C<$count> is the number of
918 elements in C<$issues>
919
920 =cut
921
922 #'
923 sub allissues {
924     my ( $bornum, $order, $limit ) = @_;
925
926     #FIXME: sanity-check order and limit
927     my $dbh   = C4::Context->dbh;
928     my $count=0;
929     my $query = "Select * from issues,biblio,items,biblioitems
930   where borrowernumber=? and
931   items.biblioitemnumber=biblioitems.biblioitemnumber and
932   items.itemnumber=issues.itemnumber and
933   items.biblionumber=biblio.biblionumber order by $order";
934     if ( $limit != 0 ) {
935         $query .= " limit $limit";
936     }
937
938     #print $query;
939     my $sth = $dbh->prepare($query);
940     $sth->execute($bornum);
941     my @result;
942     my $i = 0;
943     while ( my $data = $sth->fetchrow_hashref ) {
944         $result[$i] = $data;
945         $i++;
946         $count++;
947     }
948
949     # get all issued items for bornum from oldissues table
950     # large chunk of older issues data put into table oldissues
951     # to speed up db calls for issuing items
952     if(C4::Context->preference("ReadingHistory")){
953           my $query2="SELECT * FROM oldissues,biblio,items,biblioitems
954                       WHERE borrowernumber=? 
955                       AND items.biblioitemnumber=biblioitems.biblioitemnumber
956                       AND items.itemnumber=oldissues.itemnumber
957                       AND items.biblionumber=biblio.biblionumber
958                       ORDER BY $order";
959           if ($limit !=0){
960                 $limit=$limit-$count;
961                 $query2.=" limit $limit";
962           }
963
964           my $sth2=$dbh->prepare($query2);
965           $sth2->execute($bornum);
966
967           while (my $data2=$sth2->fetchrow_hashref){
968                 $result[$i]=$data2;
969                 $i++;
970           }
971           $sth2->finish;
972     }
973     $sth->finish;
974
975     return ( $i, \@result );
976 }
977
978 =head2 getboracctrecord
979
980   ($count, $acctlines, $total) = &getboracctrecord($env, $borrowernumber);
981
982 Looks up accounting data for the patron with the given borrowernumber.
983
984 C<$env> is ignored.
985
986 (FIXME - I'm not at all sure what this is about.)
987
988 C<&getboracctrecord> returns a three-element array. C<$acctlines> is a
989 reference-to-array, where each element is a reference-to-hash; the
990 keys are the fields of the C<accountlines> table in the Koha database.
991 C<$count> is the number of elements in C<$acctlines>. C<$total> is the
992 total amount outstanding for all of the account lines.
993
994 =cut
995
996 #'
997 sub getboracctrecord {
998     my ( $env, $params ) = @_;
999     my $dbh = C4::Context->dbh;
1000     my @acctlines;
1001     my $numlines = 0;
1002     my $sth      = $dbh->prepare(
1003         "Select * from accountlines where
1004 borrowernumber=? order by date desc,timestamp desc"
1005     );
1006
1007     #   print $query;
1008     $sth->execute( $params->{'borrowernumber'} );
1009     my $total = 0;
1010     while ( my $data = $sth->fetchrow_hashref ) {
1011
1012         #FIXME before reinstating: insecure?
1013         #      if ($data->{'itemnumber'} ne ''){
1014         #        $query="Select * from items,biblio where items.itemnumber=
1015         #       '$data->{'itemnumber'}' and biblio.biblionumber=items.biblionumber";
1016         #       my $sth2=$dbh->prepare($query);
1017         #       $sth2->execute;
1018         #       my $data2=$sth2->fetchrow_hashref;
1019         #       $sth2->finish;
1020         #       $data=$data2;
1021         #     }
1022         $acctlines[$numlines] = $data;
1023         $numlines++;
1024         $total += $data->{'amountoutstanding'};
1025     }
1026     $sth->finish;
1027     return ( $numlines, \@acctlines, $total );
1028 }
1029
1030 =head2 checkuniquemember (OUEST-PROVENCE)
1031
1032   $result = &checkuniquemember($collectivity,$surname,$categorycode,$firstname,$dateofbirth);
1033
1034 Checks that a member exists or not in the database.
1035
1036 C<&result> is 1 (=exist) or 0 (=does not exist)
1037 C<&collectivity> is 1 (= we add a collectivity) or 0 (= we add a physical member)
1038 C<&surname> is the surname
1039 C<&categorycode> is from categorycode table
1040 C<&firstname> is the firstname (only if collectivity=0)
1041 C<&dateofbirth> is the date of birth (only if collectivity=0)
1042
1043 =cut
1044
1045 sub checkuniquemember {
1046     my ( $collectivity, $surname, $firstname, $dateofbirth ) = @_;
1047     my $dbh = C4::Context->dbh;
1048     my $request;
1049     if ($collectivity) {
1050
1051 #                               $request="select count(*) from borrowers where surname=? and categorycode=?";
1052         $request =
1053           "select borrowernumber,categorycode from borrowers where surname=? ";
1054     }
1055     else {
1056
1057 #                               $request="select count(*) from borrowers where surname=? and categorycode=? and firstname=? and dateofbirth=?";
1058         $request =
1059 "select borrowernumber,categorycode from borrowers where surname=?  and firstname=? and dateofbirth=?";
1060     }
1061     my $sth = $dbh->prepare($request);
1062     if ($collectivity) {
1063         $sth->execute( uc($surname) );
1064     }
1065     else {
1066         $sth->execute( uc($surname), ucfirst($firstname), $dateofbirth );
1067     }
1068     my @data = $sth->fetchrow;
1069     if ( $data[0] ) {
1070         $sth->finish;
1071         return $data[0], $data[1];
1072
1073         #
1074     }
1075     else {
1076         $sth->finish;
1077         return 0;
1078     }
1079 }
1080
1081 =head2 getzipnamecity (OUEST-PROVENCE)
1082
1083 take all info from table city for the fields city and  zip
1084 check for the name and the zip code of the city selected
1085
1086 =cut
1087
1088 sub getzipnamecity {
1089     my ($cityid) = @_;
1090     my $dbh      = C4::Context->dbh;
1091     my $sth      =
1092       $dbh->prepare(
1093         "select city_name,city_zipcode from cities where cityid=? ");
1094     $sth->execute($cityid);
1095     my @data = $sth->fetchrow;
1096     return $data[0], $data[1];
1097 }
1098
1099 =head2 updatechildguarantor (OUEST-PROVENCE)
1100
1101 check for title,firstname,surname,adress,zip code and city  from guarantor to 
1102 guarantorchild
1103
1104 =cut
1105
1106 #'
1107
1108 sub getguarantordata {
1109     my ($borrowerid) = @_;
1110     my $dbh          = C4::Context->dbh;
1111     my $sth          =
1112       $dbh->prepare(
1113 "Select title,firstname,surname,streetnumber,address,streettype,address2,zipcode,city,phone,phonepro,mobile,email,emailpro,fax  from borrowers where borrowernumber =? "
1114       );
1115     $sth->execute($borrowerid);
1116     my $guarantor_data = $sth->fetchrow_hashref;
1117     $sth->finish;
1118     return $guarantor_data;
1119 }
1120
1121 =head2 getdcity (OUEST-PROVENCE)
1122 recover cityid  with city_name condition
1123 =cut
1124
1125 sub getidcity {
1126     my ($city_name) = @_;
1127     my $dbh = C4::Context->dbh;
1128     my $sth = $dbh->prepare("select cityid from cities where city_name=? ");
1129     $sth->execute($city_name);
1130     my $data = $sth->fetchrow;
1131     return $data;
1132 }
1133
1134 =head2 getcategorytype (OUEST-PROVENCE)
1135
1136 check for the category_type with categorycode
1137 and return the category_type 
1138
1139 =cut
1140
1141 sub getcategorytype {
1142     my ($categorycode) = @_;
1143     my $dbh            = C4::Context->dbh;
1144     my $sth            =
1145       $dbh->prepare(
1146 "Select category_type,description from categories where categorycode=?  "
1147       );
1148     $sth->execute($categorycode);
1149     my ( $category_type, $description ) = $sth->fetchrow;
1150     return $category_type, $description;
1151 }
1152
1153 sub calcexpirydate {
1154     my ( $categorycode, $dateenrolled ) = @_;
1155     my $dbh = C4::Context->dbh;
1156     my $sth =
1157       $dbh->prepare(
1158         "select enrolmentperiod from categories where categorycode=?");
1159     $sth->execute($categorycode);
1160     my ($enrolmentperiod) = $sth->fetchrow;
1161     $enrolmentperiod = 12 unless ($enrolmentperiod);
1162     return format_date_in_iso(
1163         &DateCalc( $dateenrolled, "$enrolmentperiod months" ) );
1164 }
1165
1166 =head2 checkuserpassword (OUEST-PROVENCE)
1167
1168 check for the password and login are not used
1169 return the number of record 
1170 0=> NOT USED 1=> USED
1171
1172 =cut
1173
1174 sub checkuserpassword {
1175     my ( $borrowernumber, $userid, $password ) = @_;
1176     $password = md5_base64($password);
1177     my $dbh = C4::Context->dbh;
1178     my $sth =
1179       $dbh->prepare(
1180 "Select count(*) from borrowers where borrowernumber !=? and userid =? and password=? "
1181       );
1182     $sth->execute( $borrowernumber, $userid, $password );
1183     my $number_rows = $sth->fetchrow;
1184     return $number_rows;
1185
1186 }
1187
1188 =head2 borrowercategories
1189
1190   ($codes_arrayref, $labels_hashref) = &borrowercategories();
1191
1192 Looks up the different types of borrowers in the database. Returns two
1193 elements: a reference-to-array, which lists the borrower category
1194 codes, and a reference-to-hash, which maps the borrower category codes
1195 to category descriptions.
1196
1197 =cut
1198
1199 #'
1200 sub borrowercategories {
1201     my ( $category_type, $action ) = @_;
1202     my $dbh = C4::Context->dbh;
1203     my $request;
1204     $request =
1205 "Select categorycode,description from categories where category_type=? order by categorycode";
1206     my $sth = $dbh->prepare($request);
1207     $sth->execute($category_type);
1208     my %labels;
1209     my @codes;
1210
1211     while ( my $data = $sth->fetchrow_hashref ) {
1212         push @codes, $data->{'categorycode'};
1213         $labels{ $data->{'categorycode'} } = $data->{'description'};
1214     }
1215     $sth->finish;
1216     return ( \@codes, \%labels );
1217 }
1218
1219 =head2 getborrowercategory
1220
1221   $description,$dateofbirthrequired,$upperagelimit,$category_type = &getborrowercategory($categorycode);
1222
1223 Given the borrower's category code, the function returns the corresponding
1224 description , dateofbirthrequired , upperagelimit and category type for a comprehensive information display.
1225
1226 =cut
1227
1228 sub getborrowercategory {
1229     my ($catcode) = @_;
1230     my $dbh       = C4::Context->dbh;
1231     my $sth       =
1232       $dbh->prepare(
1233         "SELECT description,dateofbirthrequired,upperagelimit,category_type FROM categories WHERE categorycode = ?");
1234     $sth->execute($catcode);
1235     my ($description,$dateofbirthrequired,$upperagelimit,$category_type) = $sth->fetchrow();
1236     $sth->finish();
1237     return ($description,$dateofbirthrequired,$upperagelimit,$category_type);
1238 }    # sub getborrowercategory
1239
1240
1241
1242 =head2 ethnicitycategories
1243
1244   ($codes_arrayref, $labels_hashref) = &ethnicitycategories();
1245
1246 Looks up the different ethnic types in the database. Returns two
1247 elements: a reference-to-array, which lists the ethnicity codes, and a
1248 reference-to-hash, which maps the ethnicity codes to ethnicity
1249 descriptions.
1250
1251 =cut
1252
1253 #'
1254
1255 sub ethnicitycategories {
1256     my $dbh = C4::Context->dbh;
1257     my $sth = $dbh->prepare("Select code,name from ethnicity order by name");
1258     $sth->execute;
1259     my %labels;
1260     my @codes;
1261     while ( my $data = $sth->fetchrow_hashref ) {
1262         push @codes, $data->{'code'};
1263         $labels{ $data->{'code'} } = $data->{'name'};
1264     }
1265     $sth->finish;
1266     return ( \@codes, \%labels );
1267 }
1268
1269 =head2 fixEthnicity
1270
1271   $ethn_name = &fixEthnicity($ethn_code);
1272
1273 Takes an ethnicity code (e.g., "european" or "pi") and returns the
1274 corresponding descriptive name from the C<ethnicity> table in the
1275 Koha database ("European" or "Pacific Islander").
1276
1277 =cut
1278
1279 #'
1280
1281 sub fixEthnicity($) {
1282
1283     my $ethnicity = shift;
1284     my $dbh       = C4::Context->dbh;
1285     my $sth       = $dbh->prepare("Select name from ethnicity where code = ?");
1286     $sth->execute($ethnicity);
1287     my $data = $sth->fetchrow_hashref;
1288     $sth->finish;
1289     return $data->{'name'};
1290 }    # sub fixEthnicity
1291
1292
1293
1294 =head2 get_age
1295
1296   $dateofbirth,$date = &get_age($date);
1297
1298 this function return the borrowers age with the value of dateofbirth
1299
1300 =cut
1301 #'
1302 sub get_age {
1303     my ($date, $date_ref) = @_;
1304
1305     if (not defined $date_ref) {
1306         $date_ref = sprintf('%04d-%02d-%02d', Today());
1307     }
1308
1309     my ($year1, $month1, $day1) = split /-/, $date;
1310     my ($year2, $month2, $day2) = split /-/, $date_ref;
1311
1312     my $age = $year2 - $year1;
1313     if ($month1.$day1 > $month2.$day2) {
1314         $age--;
1315     }
1316
1317     return $age;
1318 }# sub get_age
1319
1320
1321
1322 =head2 get_institutions
1323   $insitutions = get_institutions();
1324
1325 Just returns a list of all the borrowers of type I, borrownumber and name
1326 =cut
1327
1328 #'
1329 sub get_institutions {
1330     my $dbh = C4::Context->dbh();
1331     my $sth =
1332       $dbh->prepare(
1333 "SELECT borrowernumber,surname FROM borrowers WHERE categorycode=? ORDER BY surname"
1334       );
1335     $sth->execute('I');
1336     my %orgs;
1337     while ( my $data = $sth->fetchrow_hashref() ) {
1338         $orgs{ $data->{'borrowernumber'} } = $data;
1339     }
1340     $sth->finish();
1341     return ( \%orgs );
1342
1343 }    # sub get_institutions
1344
1345 =head2 add_member_orgs
1346
1347   add_member_orgs($borrowernumber,$borrowernumbers);
1348
1349 Takes a borrowernumber and a list of other borrowernumbers and inserts them into the borrowers_to_borrowers table
1350
1351 =cut
1352
1353 #'
1354 sub add_member_orgs {
1355     my ( $borrowernumber, $otherborrowers ) = @_;
1356     my $dbh   = C4::Context->dbh();
1357     my $query =
1358       "INSERT INTO borrowers_to_borrowers (borrower1,borrower2) VALUES (?,?)";
1359     my $sth = $dbh->prepare($query);
1360     foreach my $bornum (@$otherborrowers) {
1361         $sth->execute( $borrowernumber, $bornum );
1362     }
1363     $sth->finish();
1364
1365 }    # sub add_member_orgs
1366
1367 =head2 GetBorrowersFromSurname
1368
1369 =over 4
1370
1371 \@resutlts = GetBorrowersFromSurname($surname)
1372 this function get the list of borrower names like $surname.
1373 return :
1374 the table of results in @results
1375
1376 =back
1377
1378 =cut
1379 sub GetBorrowersFromSurname  {
1380     my ($searchstring)=@_;
1381     my $dbh = C4::Context->dbh;
1382     $searchstring=~ s/\'/\\\'/g;
1383     my @data=split(' ',$searchstring);
1384     my $count=@data;
1385     my $query = qq|
1386         SELECT   surname,firstname
1387         FROM     borrowers
1388         WHERE    (surname like ?)
1389         ORDER BY surname
1390     |;
1391     my $sth=$dbh->prepare($query);
1392     $sth->execute("$data[0]%");
1393     my @results;
1394     my $count = 0;
1395     while (my $data=$sth->fetchrow_hashref){
1396          push(@results,$data);
1397          $count++;
1398     }
1399      $sth->finish;
1400      return ($count,\@results);
1401 }
1402
1403 END { }       # module clean-up code here (global destructor)
1404
1405 1;