3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16 # Suite 330, Boston, MA 02111-1307 USA
20 use C4::Circulation::Circ2;
21 use C4::Circulation::Fines;
25 use Mail::RFC822::Address;
30 #levyFines(); # Do not levy real fines in testing situation.
36 # - Need to calculate the fine on each book; no idea how to get this information from Koha
37 # - Need to diffentricate between the total_fines including replacement costs,
38 # and the total fines if the books are returned in the day 29 notices (see above).
39 # - clean up the %actions hash creation code.
42 # - preferedcont field in borrowers hash; does this do anything?
44 # - which 'address' to send sms to?
45 # - senders returning success or fail
50 # Look at the current overdues, and levy fines on the offenders.
55 # Work out what today is as an integer value.
56 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime(time);
57 $mon++; $year=$year+1900;
58 my $date=Date_DaysSince1BC($mon,$mday,$year);
62 # Retrieve an array of overdues.
63 my ($count, $overduesReference) = Getoverdues();
64 print "$count overdue items where found.\n\n";
65 my @overdues=@$overduesReference;
67 foreach my $overdue (@overdues) {
68 my @dates=split('-',$overdue->{'date_due'});
69 my $due_day=Date_DaysSince1BC($dates[1],$dates[2],$dates[0]);
72 # Check that the item is really overdue. The output of Getoverdues() will normally
73 # always be overdue items. However, if you are running this script with a value of $date other than the current time, this check is needed.
74 if ($due_day <= $date) {
75 my $difference=$date-$due_day;
76 print "Itemnumber ".$overdue->{'itemnumber'}." is issued to ".$overdue->{'borrowernumber'}." is overdue by $difference days.\n";
78 # Calculate the cost of this overdue.
79 # Fines vary according to borrower type, but cannot exceed the maximum fine.
80 print $overdue->{'borrowernumber'};
81 my $borrower=BorType($overdue->{'borrowernumber'});
83 my ($amount,$type,$printout)=CalcFine($overdue->{'itemnumber'}, $borrower->{'categorycode'}, $difference);
84 if ($amount > $maxfine){
89 my $due="$dates[2]/$dates[1]/$dates[0]";
90 UpdateFine($overdue->{'itemnumber'}, $overdue->{'borrowernumber'}, $amount, $type, $due);
91 print $overdue->{'borrowernumber'}." has been fined $amount for itemnumber ".$overdue->{'itemnumber'}." overdue for $difference days.\n";
96 # After 28 days, the item is marked lost and the replacement charge is added as a fine
97 if ($difference >= 28) {
98 my $borrower=BorType($overdue->{'borrowernumber'});
99 if ($borrower->{'cardnumber'} ne ''){
100 my $cost=ReplacementCost($overdue->{'itemnumber'});
104 my $accountno=C4::Circulation::Circ2::getnextacctno($env,$overdue->{'borrowernumber'},$dbh);
105 my $item=GetBiblioFromItemNumber($overdue->{'itemnumber'});
106 if ($item->{'itemlost'} ne '1' && $item->{'itemlost'} ne '2' ){
107 $item->{'title'}=~ s/\'/\\'/g;
108 my $query="Insert into accountlines (borrowernumber,itemnumber,accountno,date,amount, description,accounttype,amountoutstanding)
109 values ($overdue->{'borrowernumber'}, $overdue->{'itemnumber'},
110 '$accountno',now(),'$cost','Lost item $item->{'title'} $item->{'barcode'}','L','$cost')";
112 my $sth=$dbh->prepare($query);
116 $query="update items set itemlost=2 where itemnumber='$overdue->{'itemnumber'}'";
117 $sth=$dbh->prepare($query);
135 # Look up the overdues for today.
136 # Capture overdues which fall on our dates of interest.
141 ####################################################################################################
142 # Creating a big hash of available templates
144 %email->{'template'}='email-8.txt';
146 %sms->{'template'}='sms-8.txt';
149 %fax1->{'template'}='fax-8.html';
151 my %firstReminder->{'email'} = \%email;
152 %firstReminder->{'sms'} = \%sms;
153 %firstReminder->{'fax'} = \%fax1;
156 %email2->{'template'}='email-15.txt';
159 %fax2->{'template'}='fax-15.html';
162 %letter2->{'template'}='fax-15.html';
164 my %sms2->{'template'}='sms-15.txt';
165 my %secondReminder->{'email'} = \%email2;
166 %secondReminder->{'sms'} = \%sms2;
167 %secondReminder->{'fax'} = \%fax2;
168 %secondReminder->{'letter'} = \%letter2;
172 %email3->{'template'}='email-29.txt';
174 %fax3->{'template'}='fax-29.html';
176 %letter3->{'template'}='letter-29.html';
178 my %finalReminder->{'email'} = \%email3;
179 %finalReminder->{'fax'} = \%fax3;
180 %finalReminder->{'letter'} = \%letter3;
184 %actions->{'8'}=\%firstReminder;
185 %actions->{'15'}=\%secondReminder;
186 %actions->{'29'}=\%finalReminder;
188 ##################################################################################################################
191 # Work out what today is as an integer value.
192 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime(time);
193 $mon++; $year=$year+1900;
194 my $date=Date_DaysSince1BC($mon,$mday,$year);
197 # Retrieve an array of overdues.
198 my ($count, $overduesReference) = Getoverdues();
199 print "$count overdue items where found.\n\n";
200 my @overdues=@$overduesReference;
203 # We're going to build a hash of arrays, containing the items requiring action.
204 # ->borrowernumber, date, @overdues
206 foreach my $actionday (keys(%actions)) {
208 %actionItems->{$actionday} = \@items;
213 foreach my $overdue (@overdues) {
214 my @dates=split('-',$overdue->{'date_due'});
215 my $due_day=Date_DaysSince1BC($dates[1],$dates[2],$dates[0]);
217 my $difference=$date-$due_day;
218 # $overdue->{'fine'}=GetFine($overdue->{'itemnumber'});
219 # If does this item fall on a day of interest?
220 $overdue->{'difference'}=$difference;
221 foreach my $actiondate (keys(%actions)) {
222 if ($actiondate == $difference) {
223 my @items = @{%actionItems->{$actiondate}};
227 %actionItems->{$actiondate} = \@items;
235 # We now have a hash containing overdues which need actioning, we can step through each set.
236 # Work from earilest to latest. We only wish to send the most urgent message.
240 foreach my $actiondate (sort {$a <=> $b} (keys(%actions))) {
241 print "\n\nThe following items are $actiondate days overdue.\n";
242 my @items = @{%actionItems->{$actiondate}};
245 foreach my $overdue (@items) {
246 if ($overdue->{'difference'} eq $actiondate) {
247 # Detemine which borrower is responsible for this overdue;
248 # if the offender is a child, then the garentor is the person to notify
249 my $borrower=responsibleBorrower($overdue);
252 my ($method, $address) = preferedContactMethod($borrower);
255 # Do we have to send something, using this method on this day?
256 if (%actions->{$actiondate}->{$method}->{'template'}) {
257 # If this user has one overdue, then they may have offers as well.
258 # No point in sending a notice without mentioning all of the items.
260 foreach my $over (@overdues) {
261 my $responisble= responsibleBorrower($over);
262 if ($responisble->{'borrowernumber'} eq $borrower->{'borrowernumber'}) {
263 $over->{'borrowernumber'}=$responisble->{'borrowernumber'};
265 push (@alloverdues, \%o);
269 my $dbh=C4Connect(); # FIXME disconnect this
271 # Template the message
272 my $template = HTML::Template->new(filename => 'templates/'.%actions->{$actiondate}->{$method}->{'template'}, die_on_bad_params => 0);
276 foreach my $over (@alloverdues) {
278 my $env; #FIXME what is this varible for?
280 if ( my $item = GetBiblioFromItemNumber( $over->{'itemnumber'})){
281 print "getting fine ($over->{'itemnumber'} $overdue->{'borrowernumber'} $over->{'borrowernumber'}\n";
282 my $fine = GetFine($over->{'itemnumber'},$overdue->{'borrowernumber'});
287 my $rep = ReplacementCost2($over->{'itemnumber'},$overdue->{'borrowernumber'});
294 $borritem{"$over->{'itemnumber'} $over->{'borrowernumber'}"}=$fine;
296 $borritem{"$over->{'itemnumber'} $over->{'borrowernumber'}"}+=$fine;
298 print $borritem{"$over->{'itemnumber'} $over->{'borrowernumber'}"},"\n";
299 $total_fines += $borritem{"$over->{'itemnumber'} $over->{'borrowernumber'}"};
300 $item->{'title'}=substr($item->{'title'},0,25);
301 my $len=length($item->{'title'});
304 $item->{'title'}.=" " x $diff;
307 $row_data{'BARCODE'}=$item->{'barcode'};
308 $row_data{'TITLE'}=$item->{'title'};
309 $row_data{'DATE_DUE'}=$over->{'date_due'};
310 $row_data{'FINE'}=$borritem{"$over->{'itemnumber'} $over->{'borrowernumber'}"};
311 $row_data{'REP'}=$rep;
313 push(@bookdetails, \%row_data);
315 print "Missing item $over->{'itemnumber'}\n";
319 $template->param(BOOKDETAILS => \@bookdetails);
323 %params->{'borrowernumber'} = $overdue->{'borrowernumber'};
324 my ($count, $acctlines, $total) = &getboracctrecord($env, \%params);
325 $template->param(FINES_TOTAL => $total_fines);
326 $template->param(OWING => $total);
327 my $name= "$borrower->{'firstname'} $borrower->{'surname'}";
328 $template->param(NAME=> $name);
330 %messages->{$borrower->{'borrowernumber'}} = $template->output();
333 print "No $method needs to be sent at $overdue->{'difference'} days; not sending\n";
338 print "This borrower has an overdue item, but no means of contact\n";
341 } #end of 'if this overdue falls on an action date'
343 } #end of 'foreach overdue'
345 } # end of foreach actiondate
348 # How that all of the messsages to be sent have been composed, send them.
349 foreach my $borrowernumber (keys(%messages)) {
350 print "$borrowernumber\n";
352 my $borrower=BorType($borrowernumber);
353 my ($method, $address) = preferedContactMethod($borrower);
356 if ($method eq 'email') {
357 $result = sendEmail($address, 'lep@library.org.nz', 'Overdue Library Items', %messages->{$borrowernumber});
359 elsif ($method eq 'sms') {
360 $result = sendSMS($address, %messages->{$borrowernumber});
362 elsif ($method eq 'fax') {
363 $result = sendFax($address, %messages->{$borrowernumber});
365 elsif ($method eq 'letter') {
366 $result = printLetter($address, %messages->{$borrowernumber});
370 #print %messages->{$borrowernumber}; # debug
373 # Log the outcome of this attempt
374 logContact($borrowernumber, $method, $address, $result, %messages->{$borrowernumber});
391 sub responsibleBorrower {
392 # Given an overdue item, return the details of the borrower responible as a hash of database columns.
395 if ($overdue->{'borrowernumber'}) {
396 my $borrower=BorType($overdue->{'borrowernumber'});
399 # Overdue books assigned to children have notices sent to the guarantor.
400 if ($borrower->{'categorycode'} eq 'C') {
402 my $query="Select borrowernumber from borrowers
403 where borrowernumber=?";
405 my $sth=$dbh->prepare($query);
406 $sth->execute($borrower->{'guarantor'});
408 my $tdata=$sth->fetchrow_hashref();
412 my $guarantor=BorType($tdata->{'borrowernumber'});
413 $borrower = $guarantor;
429 sub preferedContactMethod {
430 # Given a reference to borrower details, in the format
431 # returned by BorType(), determine the prefered contact method, and address to use.
433 # print "finding borrower method $borrower->{'preferredcont'} $borrower->{'emailaddress'} $borrower->{'streetaddress'}\n";
435 # Possible contact methods, in order of preference are:
436 my @methods = ('email', 'sms', 'fax', 'letter');
442 # Does this borrower have a borrower.preferredcont set?
443 # If so, push it to the head of our array of methods to try.
444 # If it's a method unheard of by this system, then we'll drop though to the prefined methods above.
445 # Note use of unshift to push onto the front of the array.
446 if ($borrower->{'preferredcont'}) {
447 unshift(@methods, $borrower->{'preferredcont'});
451 # Cycle through the possible methods until one is accepted
452 while ((@methods) and (!$address)) {
453 $method=shift(@methods);
456 if ($method eq 'email') {
457 if (($borrower->{'emailaddress'}) and (Mail::RFC822::Address::valid($borrower->{'emailaddress'}))) {
458 $address = $borrower->{'emailaddress'};
461 elsif ($method eq 'fax') {
462 if ($borrower->{'faxnumber'}) {
463 $address = $borrower->{'faxnumber'};
466 elsif ($method eq 'sms') {
467 if ($borrower->{'textmessaging'}) {
468 $address = $borrower->{'textmessaging'};
471 elsif ($method eq 'letter') {
472 if ($borrower->{'streetaddress'}) {
473 $address = mailingAddress($borrower);
477 print "$method, $address\n";
478 return ($method, $address);
489 # Given the details of an attempt to contact a borrower,
490 # log them in the attempted_contacts table of the koha database.
491 my ($borrowernumber, $method, $address, $result, $message) = @_;
493 my $dbh=C4Connect(); # FIXME - disconnect me
494 my $querystring = " insert into attempted_contacts
495 (borrowernumber, method, address, result, message, date)
496 values (?, ?, ?, ?, ?, now())";
497 my $sth= $dbh->prepare($querystring);
498 $sth->execute($borrowernumber, $method, $address, $result, $message);
510 # Given a hash of borrower information, such as that returned by BorType,
511 # return a mailing address.
514 my $address = $borrower->{'firstname'}."\n".
515 $borrower->{'streetaddress'}."\n".
516 $borrower->{'streetcity'};
528 # Given an overdue item, return the current fines on it
543 # Given an email address, and a subject and message, attempt to send email.
549 # print "in email area";
551 # print "\nSending Email To: $to\n$message\n";
553 my %mail = ( To => $to,
554 # CC => 'rosalie@library.org.nz',
557 Message => $message);
560 if (not(sendmail %mail)) {
561 warn "sendEmail to $to failed.";
571 # Given a cell number and a message, attempt to send an SMS message.
572 # FIXME - needs information about how to do this at HLT
579 # Given a fax number, and a message, attempt to send a fax.
580 # FIXME - needs information about how to do this at HLT
581 # This is fairly easy.
582 # We will be past the body of the fax as HTML.
583 # We can pass this through html2ps to generate Postscript suitable
584 # for passing to the fax server.
591 # FIXME - needs information about how to do this at HLT