ffzg/recall_notices.pl: added --interval and --dedup
[koha.git] / offline_circ / process_koc.pl
index 86a79cf..174f00d 100755 (executable)
@@ -4,24 +4,25 @@
 
 # This file is part of Koha.
 #
 
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
 #
-# You should have received a copy of the GNU General Public License along with
-# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
-# Suite 330, Boston, MA  02111-1307 USA
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 #
 
 #
 
-use strict;
-use warnings;
+use Modern::Perl;
+
+use CGI qw ( -utf8 );
+use Carp;
 
 
-use CGI;
 use C4::Output;
 use C4::Auth;
 use C4::Koha;
 use C4::Output;
 use C4::Auth;
 use C4::Koha;
@@ -29,10 +30,14 @@ use C4::Context;
 use C4::Biblio;
 use C4::Accounts;
 use C4::Circulation;
 use C4::Biblio;
 use C4::Accounts;
 use C4::Circulation;
+use C4::Items;
 use C4::Members;
 use C4::Stats;
 use C4::Members;
 use C4::Stats;
-use C4::UploadedFile;
 use C4::BackgroundJob;
 use C4::BackgroundJob;
+use Koha::UploadedFiles;
+use Koha::Account;
+use Koha::Checkouts;
+use Koha::Patrons;
 
 use Date::Calc qw( Add_Delta_Days Date_to_Days );
 
 
 use Date::Calc qw( Add_Delta_Days Date_to_Days );
 
@@ -43,13 +48,13 @@ my $FILE_VERSION = '1.0';
 
 our $query = CGI->new;
 
 
 our $query = CGI->new;
 
-my ($template, $loggedinuser, $cookie)
-  = get_template_and_user( { template_name => "offline_circ/process_koc.tmpl",
-                               query => $query,
-                               type => "intranet",
-                               authnotrequired => 0,
-                                flagsrequired   => { circulate => 1 },
-                               });
+my ($template, $loggedinuser, $cookie) = get_template_and_user({
+    template_name => "offline_circ/process_koc.tt",
+    query => $query,
+    type => "intranet",
+    authnotrequired => 0,
+     flagsrequired   => { circulate => "circulate_remaining_permissions" },
+});
 
 
 my $fileID=$query->param('uploadedfileid');
 
 
 my $fileID=$query->param('uploadedfileid');
@@ -68,16 +73,17 @@ if ($completedJobID) {
     $template->param(transactions_loaded => 1);
     $template->param(messages => $results->{results});
 } elsif ($fileID) {
     $template->param(transactions_loaded => 1);
     $template->param(messages => $results->{results});
 } elsif ($fileID) {
-    my $uploaded_file = C4::UploadedFile->fetch($sessionID, $fileID);
-    my $fh = $uploaded_file->fh();
-    my @input_lines = <$fh>;
-  
-    my $filename = $uploaded_file->name(); 
+    my $upload = Koha::UploadedFiles->find( $fileID );
+    my $fh = $upload? $upload->file_handle: undef;
+    my $filename = $upload? $upload->filename: undef;
+    my @input_lines = $fh? <$fh>: ();
+    $fh->close if $fh;
+
     my $job = undef;
 
     if ($runinbackground) {
         my $job_size = scalar(@input_lines);
     my $job = undef;
 
     if ($runinbackground) {
         my $job_size = scalar(@input_lines);
-        $job = C4::BackgroundJob->new($sessionID, $filename, $ENV{'SCRIPT_NAME'}, $job_size);
+        $job = C4::BackgroundJob->new($sessionID, $filename, '/cgi-bin/koha/offline_circ/process_koc.pl', $job_size);
         my $jobID = $job->id();
 
         # fork off
         my $jobID = $job->id();
 
         # fork off
@@ -92,7 +98,7 @@ if ($completedJobID) {
 
             my $reply = CGI->new("");
             print $reply->header(-type => 'text/html');
 
             my $reply = CGI->new("");
             print $reply->header(-type => 'text/html');
-            print "{ jobID: '$jobID' }";
+            print '{"jobID":"' . $jobID . '"}';
             exit 0;
         } elsif (defined $pid) {
             # child
             exit 0;
         } elsif (defined $pid) {
             # child
@@ -103,28 +109,31 @@ if ($completedJobID) {
         } else {
             # fork failed, so exit immediately
             # fork failed, so exit immediately
         } else {
             # fork failed, so exit immediately
             # fork failed, so exit immediately
-            warn "fork failed while attempting to run $ENV{'SCRIPT_NAME'} as a background job";
+            warn "fork failed while attempting to run offline_circ/process_koc.pl as a background job";
             exit 0;
         }
 
         # if we get here, we're a child that has detached
         # itself from Apache
 
             exit 0;
         }
 
         # if we get here, we're a child that has detached
         # itself from Apache
 
-    }     
+    }
 
     my $header_line = shift @input_lines;
     my $file_info   = parse_header_line($header_line);
     if ($file_info->{'Version'} ne $FILE_VERSION) {
 
     my $header_line = shift @input_lines;
     my $file_info   = parse_header_line($header_line);
     if ($file_info->{'Version'} ne $FILE_VERSION) {
-        push( @output, { message => "Warning: This file is version '$file_info->{'Version'}', but I only know how to import version '$FILE_VERSION'. I'll try my best." } );
+        push @output, {
+            message => 1,
+            ERROR_file_version => 1,
+            upload_version => $file_info->{'Version'},
+            current_version => $FILE_VERSION
+        };
     }
     }
-    
-    
+
     my $i = 0;
     foreach  my $line (@input_lines)  {
     my $i = 0;
     foreach  my $line (@input_lines)  {
-    
         $i++;
         my $command_line = parse_command_line($line);
         $i++;
         my $command_line = parse_command_line($line);
-        
+
         # map command names in the file to subroutine names
         my %dispatch_table = (
             issue     => \&kocIssueItem,
         # map command names in the file to subroutine names
         my %dispatch_table = (
             issue     => \&kocIssueItem,
@@ -154,12 +163,14 @@ if ($completedJobID) {
 
 output_html_with_http_headers $query, $cookie, $template->output;
 
 
 output_html_with_http_headers $query, $cookie, $template->output;
 
-=head3 parse_header_line
+=head1 FUNCTIONS
+
+=head2 parse_header_line
 
 parses the header line from a .koc file. This is the line that
 specifies things such as the file version, and the name and version of
 the offline circulation tool that generated the file. See
 
 parses the header line from a .koc file. This is the line that
 specifies things such as the file version, and the name and version of
 the offline circulation tool that generated the file. See
-L<http://wiki.koha.org/doku.php?id=koha_offline_circulation_file_format>
+L<http://wiki.koha-community.org/wiki/Koha_offline_circulation_file_format>
 for more information.
 
 pass in a string containing the header line (the first line from th
 for more information.
 
 pass in a string containing the header line (the first line from th
@@ -172,19 +183,21 @@ returns a hashref containing the information from the header.
 sub parse_header_line {
     my $header_line = shift;
     chomp($header_line);
 sub parse_header_line {
     my $header_line = shift;
     chomp($header_line);
+    $header_line =~ s/\r//g;
 
     my @fields = split( /\t/, $header_line );
     my %header_info = map { split( /=/, $_ ) } @fields;
     return \%header_info;
 }
 
 
     my @fields = split( /\t/, $header_line );
     my %header_info = map { split( /=/, $_ ) } @fields;
     return \%header_info;
 }
 
-=head3 parse_command_line
+=head2 parse_command_line
 
 =cut
 
 sub parse_command_line {
     my $command_line = shift;
     chomp($command_line);
 
 =cut
 
 sub parse_command_line {
     my $command_line = shift;
     chomp($command_line);
+    $command_line =~ s/\r//g;
 
     my ( $timestamp, $command, @args ) = split( /\t/, $command_line );
     my ( $date,      $time,    $id )   = split( /\s/, $timestamp );
 
     my ( $timestamp, $command, @args ) = split( /\t/, $command_line );
     my ( $date,      $time,    $id )   = split( /\s/, $timestamp );
@@ -204,7 +217,7 @@ sub parse_command_line {
 
 }
 
 
 }
 
-=head3 arguments_for_command
+=head2 arguments_for_command
 
 fetches the names of the columns (and function arguments) found in the
 .koc file for a particular command name. For instance, the C<issue>
 
 fetches the names of the columns (and function arguments) found in the
 .koc file for a particular command name. For instance, the C<issue>
@@ -231,87 +244,147 @@ sub arguments_for_command {
 }
 
 sub kocIssueItem {
 }
 
 sub kocIssueItem {
-  my $circ = shift;
-
-  my $branchcode = C4::Context->userenv->{branch};
-  my $borrower = GetMember( $circ->{ 'cardnumber' }, 'cardnumber' );
-  my $item = GetBiblioFromItemNumber( undef, $circ->{ 'barcode' } );
-  my $issue = GetItemIssue( $item->{'itemnumber'} );
-
-  my $issuingrule = GetIssuingRule( $borrower->{ 'categorycode' }, $item->{ 'itemtype' }, $branchcode );
-  my $issuelength = $issuingrule->{ 'issuelength' };
-  my ( $year, $month, $day ) = split( /-/, $circ->{'date'} );
-  ( $year, $month, $day ) = Add_Delta_Days( $year, $month, $day, $issuelength );
-  my $date_due = sprintf("%04d-%02d-%02d", $year, $month, $day);
-  
-  if ( $issue->{ 'date_due' } ) { ## Item is currently checked out to another person.
-#warn "Item Currently Issued.";
-    my $issue = GetOpenIssue( $item->{'itemnumber'} );
-
-    if ( $issue->{'borrowernumber'} eq $borrower->{'borrowernumber'} ) { ## Issued to this person already, renew it.
-#warn "Item issued to this member already, renewing.";
-    
-    my $date_due_object = C4::Dates->new($date_due ,'iso');
-    C4::Circulation::AddRenewal(
-        $issue->{'borrowernumber'},    # borrowernumber
-        $item->{'itemnumber'},         # itemnumber
-        undef,                         # branch
-        $date_due_object,              # datedue
-        $circ->{'date'},               # issuedate
-    ) unless ($DEBUG);
-
-      push( @output, { message => "Renewed $item->{ 'title' } ( $item->{ 'barcode' } ) to $borrower->{ 'firstname' } $borrower->{ 'surename' } ( $borrower->{'cardnumber'} ) : $circ->{ 'datetime' }\n" } );
+    my $circ = shift;
+
+    $circ->{ 'barcode' } = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && C4::Context->preference('itemBarcodeInputFilter'));
+    my $branchcode = C4::Context->userenv->{branch};
+    my $patron = Koha::Patrons->find( { cardnumber => $circ->{cardnumber} } );
+    my $borrower = $patron->unblessed;
+    my $item = Koha::Items->find({ barcode => $circ->{barcode} });
+    my $issue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
+    my $biblio = $item->biblio;
+
+    if ( $issue ) { ## Item is currently checked out to another person.
+        #warn "Item Currently Issued.";
+        my $issue = GetOpenIssue( $item->itemnumber ); # FIXME Hum? That does not make sense, if it's in the issue table, the issue is open (i.e. returndate is null)
+
+        if ( $issue->{'borrowernumber'} eq $borrower->{'borrowernumber'} ) { ## Issued to this person already, renew it.
+            #warn "Item issued to this member already, renewing.";
+
+            C4::Circulation::AddRenewal(
+                $issue->{'borrowernumber'},    # borrowernumber
+                $item->itemnumber,             # itemnumber
+                undef,                         # branch
+                undef,                         # datedue - let AddRenewal calculate it automatically
+                $circ->{'date'},               # issuedate
+            ) unless ($DEBUG);
+
+            push @output, {
+                renew => 1,
+                title => $biblio->title,
+                biblionumber => $biblio->biblionumber,
+                barcode => $item->barcode,
+                firstname => $borrower->{ 'firstname' },
+                surname => $borrower->{ 'surname' },
+                borrowernumber => $borrower->{'borrowernumber'},
+                cardnumber => $borrower->{'cardnumber'},
+                datetime => $circ->{ 'datetime' }
+            };
 
 
-    } else {
-#warn "Item issued to a different member.";
-#warn "Date of previous issue: $issue->{'issuedate'}";
-#warn "Date of this issue: $circ->{'date'}";
-      my ( $i_y, $i_m, $i_d ) = split( /-/, $issue->{'issuedate'} );
-      my ( $c_y, $c_m, $c_d ) = split( /-/, $circ->{'date'} );
-      
-      if ( Date_to_Days( $i_y, $i_m, $i_d ) < Date_to_Days( $c_y, $c_m, $c_d ) ) { ## Current issue to a different persion is older than this issue, return and issue.
-        my $date_due_object = C4::Dates->new($date_due ,'iso');
-        C4::Circulation::AddIssue( $borrower, $circ->{'barcode'}, $date_due_object ) unless ( DEBUG );
-        push( @output, { message => "Issued $item->{ 'title' } ( $item->{ 'barcode' } ) to $borrower->{ 'firstname' } $borrower->{ 'surename' } ( $borrower->{'cardnumber'} ) : $circ->{ 'datetime' }\n" } );
-
-      } else { ## Current issue is *newer* than this issue, write a 'returned' issue, as the item is most likely in the hands of someone else now.
-#warn "Current issue to another member is newer. Doing nothing";
-        ## This situation should only happen of the Offline Circ data is *really* old.
-        ## FIXME: write line to old_issues and statistics
-      }
-    
+        } else {
+            #warn "Item issued to a different member.";
+            #warn "Date of previous issue: $issue->{'issuedate'}";
+            #warn "Date of this issue: $circ->{'date'}";
+            my ( $i_y, $i_m, $i_d ) = split( /-/, $issue->{'issuedate'} );
+            my ( $c_y, $c_m, $c_d ) = split( /-/, $circ->{'date'} );
+
+            if ( Date_to_Days( $i_y, $i_m, $i_d ) < Date_to_Days( $c_y, $c_m, $c_d ) ) { ## Current issue to a different persion is older than this issue, return and issue.
+                C4::Circulation::AddIssue( $borrower, $circ->{'barcode'}, undef, undef, $circ->{'date'} ) unless ( DEBUG );
+                push @output, {
+                    issue => 1,
+                    title => $biblio->title,
+                    biblionumber => $biblio->biblionumber,
+                    barcode => $item->barcode,
+                    firstname => $borrower->{ 'firstname' },
+                    surname => $borrower->{ 'surname' },
+                    borrowernumber => $borrower->{'borrowernumber'},
+                    cardnumber => $borrower->{'cardnumber'},
+                    datetime => $circ->{ 'datetime' }
+                };
+
+            } else { ## Current issue is *newer* than this issue, write a 'returned' issue, as the item is most likely in the hands of someone else now.
+                #warn "Current issue to another member is newer. Doing nothing";
+                ## This situation should only happen of the Offline Circ data is *really* old.
+                ## FIXME: write line to old_issues and statistics
+            }
+        }
+    } else { ## Item is not checked out to anyone at the moment, go ahead and issue it
+        C4::Circulation::AddIssue( $borrower, $circ->{'barcode'}, undef, undef, $circ->{'date'} ) unless ( DEBUG );
+        push @output, {
+            issue => 1,
+            title => $biblio->title,
+            biblionumber => $biblio->biblionumber,
+            barcode => $item->barcode,
+            firstname => $borrower->{ 'firstname' },
+            surname => $borrower->{ 'surname' },
+            borrowernumber => $borrower->{'borrowernumber'},
+            cardnumber => $borrower->{'cardnumber'},
+            datetime =>$circ->{ 'datetime' }
+        };
     }
     }
-  } else { ## Item is not checked out to anyone at the moment, go ahead and issue it
-      my $date_due_object = C4::Dates->new($date_due ,'iso');
-      C4::Circulation::AddIssue( $borrower, $circ->{'barcode'}, $date_due_object ) unless ( DEBUG );
-    push( @output, { message => "Issued $item->{ 'title' } ( $item->{ 'barcode' } ) to $borrower->{ 'firstname' } $borrower->{ 'surename' } ( $borrower->{'cardnumber'} ) : $circ->{ 'datetime' }\n" } );
-  }  
 }
 
 sub kocReturnItem {
 }
 
 sub kocReturnItem {
-  my ( $circ ) = @_;
-  my $item = GetBiblioFromItemNumber( undef, $circ->{ 'barcode' } );
-  #warn( Data::Dumper->Dump( [ $circ, $item ], [ qw( circ item ) ] ) );
-  my $borrowernumber = _get_borrowernumber_from_barcode( $circ->{'barcode'} );
-  unless ( $borrowernumber ) {
-      push( @output, { message => "Warning: unable to determine borrower from item ($item->{'barcode'}). Cannot mark returned\n" } );
-  }
-  C4::Circulation::MarkIssueReturned( $borrowernumber,
-                                      $item->{'itemnumber'},
-                                      undef,
-                                      $circ->{'date'} );
-  
-  push( @output, { message => "Returned $item->{ 'title' } ( $item->{ 'barcode' } ) From borrower number $borrowernumber : $circ->{ 'datetime' }\n" } ); 
+    my ( $circ ) = @_;
+    $circ->{'barcode'} = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && C4::Context->preference('itemBarcodeInputFilter'));
+    my $item = Koha::Items->find({ barcode => $circ->{barcode} });
+    my $biblio = $item->biblio;
+    my $borrowernumber = _get_borrowernumber_from_barcode( $circ->{'barcode'} );
+    if ( $borrowernumber ) {
+        my $patron = Koha::Patrons->find( $borrowernumber );
+        C4::Circulation::MarkIssueReturned(
+            $borrowernumber,
+            $item->itemnumber,
+            $circ->{'date'},
+            $patron->privacy
+        );
+
+        ModItem({ onloan => undef }, $biblio->biblionumber, $item->itemnumber);
+        ModDateLastSeen( $item->itemnumber );
+
+        push @output,
+          {
+            return         => 1,
+            title          => $biblio->title,
+            biblionumber   => $biblio->biblionumber,
+            barcode        => $item->barcode,
+            borrowernumber => $patron->borrowernumber,
+            firstname      => $patron->firstname,
+            surname        => $patron->surname,
+            cardnumber     => $patron->cardnumber,
+            datetime       => $circ->{'datetime'}
+          };
+    } else {
+        push @output, {
+            ERROR_no_borrower_from_item => 1,
+            badbarcode => $circ->{'barcode'}
+        };
+    }
 }
 
 sub kocMakePayment {
 }
 
 sub kocMakePayment {
-  my ( $circ ) = @_;
-  my $borrower = GetMember( $circ->{ 'cardnumber' }, 'cardnumber' );
-  recordpayment( $borrower->{'borrowernumber'}, $circ->{'amount'} );
-  push( @output, { message => "accepted payment ($circ->{'amount'}) from cardnumber ($circ->{'cardnumber'}), borrower ($borrower->{'borrowernumber'})" } );
+    my ($circ) = @_;
+
+    my $cardnumber = $circ->{cardnumber};
+    my $amount = $circ->{amount};
+
+    my $patron = Koha::Patrons->find( { cardnumber => $cardnumber } );
+
+    Koha::Account->new( { patron_id => $patron->id } )
+      ->pay( { amount => $amount } );
+
+    push @output,
+      {
+        payment    => 1,
+        amount     => $circ->{'amount'},
+        firstname  => $patron->firstname,
+        surname    => $patron->surname,
+        cardnumber => $patron->cardnumber,
+        borrower   => $patron->id,
+      };
 }
 
 }
 
-=head3 _get_borrowernumber_from_barcode
+=head2 _get_borrowernumber_from_barcode
 
 pass in a barcode
 get back the borrowernumber of the patron who has it checked out.
 
 pass in a barcode
 get back the borrowernumber of the patron who has it checked out.
@@ -324,11 +397,10 @@ sub _get_borrowernumber_from_barcode {
 
     return unless $barcode;
 
 
     return unless $barcode;
 
-    my $item = GetBiblioFromItemNumber( undef, $barcode );
-    return unless $item->{'itemnumber'};
-    
-    my $issue = C4::Circulation::GetItemIssue( $item->{'itemnumber'} );
-    return unless $issue->{'borrowernumber'};
-    return $issue->{'borrowernumber'};
-    
+    my $item = Koha::Items->find({ barcode => $barcode });
+    return unless $item;
+
+    my $issue = Koha::Checkouts->find( { itemnumber => $item->itemnumber } );
+    return unless $issue;
+    return $issue->borrowernumber;
 }
 }