Bug 32445: Fix display of 'not for loan' items in staff
[srvgit] / tools / inventory.pl
index 1ee40a1..ace4e7c 100755 (executable)
@@ -24,40 +24,48 @@ use Modern::Perl;
 use CGI qw ( -utf8 );
 my $input = CGI->new;
 my $uploadbarcodes = $input->param('uploadbarcodes');
+my $barcodelist = $input->param('barcodelist');
 
-use C4::Auth;
+use C4::Auth qw( get_template_and_user );
 use C4::Context;
-use C4::Output;
-use C4::Biblio;
-use C4::Items;
-use C4::Koha;
-use C4::Circulation;
-use C4::Reports::Guided;    #_get_column_defs
-use C4::Charset;
-use Koha::DateUtils;
+use C4::Output qw( output_html_with_http_headers );
+use C4::Items qw( GetItemsForInventory );
+use C4::Koha qw( GetAuthorisedValues );
+use C4::Circulation qw( barcodedecode AddReturn );
+use C4::Reports::Guided qw( );
+use C4::Charset qw( NormalizeString );
+
+use Koha::Biblios;
+use Koha::DateUtils qw( dt_from_string );
+use Koha::Database::Columns;
 use Koha::AuthorisedValues;
 use Koha::BiblioFrameworks;
+use Koha::ClassSources;
+use Koha::Items;
+
 use List::MoreUtils qw( none );
 
 my $minlocation=$input->param('minlocation') || '';
 my $maxlocation=$input->param('maxlocation');
+my $class_source=$input->param('class_source');
 $maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
 my $location=$input->param('location') || '';
 my $ignoreissued=$input->param('ignoreissued');
+my $ignore_waiting_holds = $input->param('ignore_waiting_holds');
 my $datelastseen = $input->param('datelastseen'); # last inventory date
 my $branchcode = $input->param('branchcode') || '';
 my $branch     = $input->param('branch');
 my $op         = $input->param('op');
 my $compareinv2barcd = $input->param('compareinv2barcd');
 my $dont_checkin = $input->param('dont_checkin');
+my $out_of_order = $input->param('out_of_order');
+my $ccode = $input->param('ccode');
 
 my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
     {   template_name   => "tools/inventory.tt",
         query           => $input,
         type            => "intranet",
-        authnotrequired => 0,
         flagsrequired   => { tools => 'inventory' },
-        debug           => 1,
     }
 );
 
@@ -67,9 +75,12 @@ my $authorisedvalue_categories = '';
 my $frameworks = Koha::BiblioFrameworks->search({}, { order_by => ['frameworktext'] })->unblessed;
 unshift @$frameworks, { frameworkcode => '' };
 
+my @collections = ();
+my @collection_codes = ();
+
 for my $fwk ( @$frameworks ){
   my $fwkcode = $fwk->{frameworkcode};
-  my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => $fwkcode, kohafield => 'items.location', authorised_value => { not => undef } });
+  my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => $fwkcode, kohafield => 'items.location', authorised_value => [ -and => {'!=' => undef }, {'!=' => ''}] });
   my $authcode = $mss->count ? $mss->next->authorised_value : undef;
     if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
       $authorisedvalue_categories.="$authcode ";
@@ -86,14 +97,14 @@ my @notforloans;
 for my $statfield (qw/items.notforloan items.itemlost items.withdrawn items.damaged/){
     my $hash = {};
     $hash->{fieldname} = $statfield;
-    my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => $statfield, authorised_value => { not => undef } });
+    my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => $statfield, authorised_value => [ -and => {'!=' => undef }, {'!=' => ''}] });
     $hash->{authcode} = $mss->count ? $mss->next->authorised_value : undef;
     if ($hash->{authcode}){
         my $arr = GetAuthorisedValues($hash->{authcode});
         if ( $statfield eq 'items.notforloan') {
             # Add notforloan == 0 to the list of possible notforloan statuses
             # The lib value is replaced in the template
-            push @$arr, { authorised_value => 0, id => 'stat0' , lib => 'ignore' } if ! grep { $_->{authorised_value} eq '0' } @$arr;
+            push @$arr, { authorised_value => 0, id => 'stat0' , lib => '__IGNORE__' } if ! grep { $_->{authorised_value} eq '0' } @$arr;
             @notforloans = map { $_->{'authorised_value'} } @$arr;
         }
         $hash->{values} = $arr;
@@ -112,6 +123,21 @@ for my $authvfield (@$statuses) {
     }
 }
 
+# if there's a list of not for loans types selected use it rather than
+# the full set.
+@notforloans = @{$staton->{'items.notforloan'}} if defined $staton->{'items.notforloan'} and scalar @{$staton->{'items.notforloan'}} > 0;
+
+my @class_sources = Koha::ClassSources->search({ used => 1 })->as_list;
+my $pref_class = C4::Context->preference("DefaultClassificationSource");
+
+my @itemtypes = Koha::ItemTypes->search->as_list;
+my @selected_itemtypes;
+foreach my $itemtype ( @itemtypes ) {
+    if ( defined $input->param('itemtype-' . $itemtype->itemtype) ) {
+        push @selected_itemtypes, "'" . $itemtype->itemtype . "'";
+    }
+}
+
 $template->param(
     authorised_values        => \@authorised_value_list,
     today                    => dt_from_string,
@@ -123,7 +149,12 @@ $template->param(
     branch                   => $branch,
     datelastseen             => $datelastseen,
     compareinv2barcd         => $compareinv2barcd,
-    uploadedbarcodesflag     => $uploadbarcodes ? 1 : 0,
+    uploadedbarcodesflag     => ($uploadbarcodes || $barcodelist) ? 1 : 0,
+    ignore_waiting_holds     => $ignore_waiting_holds,
+    class_sources            => \@class_sources,
+    pref_class               => $pref_class,
+    itemtypes                => \@itemtypes,
+    ccode                    => $ccode,
 );
 
 # Walk through uploaded barcodes, report errors, mark as seen, check in
@@ -131,10 +162,10 @@ my $results = {};
 my @scanned_items;
 my @errorloop;
 my $moddatecount = 0;
-if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
+if ( ($uploadbarcodes && length($uploadbarcodes) > 0) || ($barcodelist && length($barcodelist) > 0) ) {
     my $dbh = C4::Context->dbh;
-    my $date = dt_from_string( scalar $input->param('setdate') );
-    $date = output_pref ( { dt => $date, dateformat => 'iso' } );
+    my $date = $input->param('setdate');
+    my $date_dt = dt_from_string($date);
 
     my $strsth  = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
     my $qonloan = $dbh->prepare($strsth);
@@ -150,15 +181,21 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
     my $err_length=0;
     my $err_data=0;
     my $lines_read=0;
-    binmode($uploadbarcodes, ":encoding(UTF-8)");
-    while (my $barcode=<$uploadbarcodes>) {
-        $barcode =~ s/\r/\n/g;
-        $barcode =~ s/\n\n/\n/g;
-        my @data = split(/\n/,$barcode);
-        push @uploadedbarcodes, @data;
+    if ($uploadbarcodes && length($uploadbarcodes) > 0) {
+        binmode($uploadbarcodes, ":encoding(UTF-8)");
+        while (my $barcode=<$uploadbarcodes>) {
+            my $split_chars = C4::Context->preference('BarcodeSeparators');
+            push @uploadedbarcodes, grep { /\S/ } split( /[$split_chars]/, $barcode );
+        }
+    } else {
+        push @uploadedbarcodes, split(/\s\n/, scalar $input->param('barcodelist') );
+        $uploadbarcodes = $barcodelist;
     }
     for my $barcode (@uploadedbarcodes) {
         next unless $barcode;
+
+        $barcode = barcodedecode($barcode);
+
         ++$lines_read;
         if (length($barcode)>$barcode_size) {
             $err_length += 1;
@@ -185,28 +222,26 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
         if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
             push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
         } else {
-            my $item = GetItem( '', $barcode );
-            if ( defined $item && $item->{'itemnumber'} ) {
+            my $item = Koha::Items->find({barcode => $barcode});
+            if ( $item ) {
                 # Modify date last seen for scanned items, remove lost status
-                ModItem( { itemlost => 0, datelastseen => $date }, undef, $item->{'itemnumber'} );
+                $item->set({ itemlost => 0, datelastseen => $date_dt })->store;
+                my $item_unblessed = $item->unblessed;
                 $moddatecount++;
-                # update item hash accordingly
-                $item->{itemlost} = 0;
-                $item->{datelastseen} = $date;
                 unless ( $dont_checkin ) {
                     $qonloan->execute($barcode);
                     if ($qonloan->rows){
                         my $data = $qonloan->fetchrow_hashref;
                         my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch});
                         if( $doreturn ) {
-                            $item->{onloan} = undef;
-                            $item->{datelastseen} = dt_from_string;
+                            $item_unblessed->{onloan} = undef;
+                            $item_unblessed->{datelastseen} = dt_from_string;
                         } else {
                             push @errorloop, { barcode => $barcode, ERR_ONLOAN_NOT_RET => 1 };
                         }
                     }
                 }
-                push @scanned_items, $item;
+                push @scanned_items, $item_unblessed;
             } else {
                 push @errorloop, { barcode => $barcode, ERR_BARCODE => 1 };
             }
@@ -223,6 +258,7 @@ if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
     ( $inventorylist ) = GetItemsForInventory({
       minlocation  => $minlocation,
       maxlocation  => $maxlocation,
+      class_source => $class_source,
       location     => $location,
       ignoreissued => $ignoreissued,
       datelastseen => $datelastseen,
@@ -230,13 +266,18 @@ if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
       branch       => $branch,
       offset       => 0,
       statushash   => $staton,
+      ccode        => $ccode,
+      ignore_waiting_holds => $ignore_waiting_holds,
+      itemtypes    => \@selected_itemtypes,
     });
 }
 # Build rightplacelist used to check if a scanned item is in the right place.
 if( @scanned_items ) {
+    # For the items that may be marked as "wrong place", we only check the location (callnumbers, location, ccode and branch)
     ( $rightplacelist ) = GetItemsForInventory({
       minlocation  => $minlocation,
       maxlocation  => $maxlocation,
+      class_source => $class_source,
       location     => $location,
       ignoreissued => undef,
       datelastseen => undef,
@@ -244,31 +285,32 @@ if( @scanned_items ) {
       branch       => $branch,
       offset       => 0,
       statushash   => undef,
+      ignore_waiting_holds => $ignore_waiting_holds,
+      itemtypes    => \@selected_itemtypes,
+      ccode        => $ccode,
     });
     # Convert the structure to a hash on barcode
     $rightplacelist = {
         map { $_->{barcode} ? ( $_->{barcode}, $_ ) : (); } @$rightplacelist
     };
+
 }
 
 # Report scanned items that are on the wrong place, or have a wrong notforloan
 # status, or are still checked out.
-foreach my $item ( @scanned_items ) {
+for ( my $i = 0; $i < @scanned_items; $i++ ) {
+
+    my $item = $scanned_items[$i];
+
     $item->{notforloancode} = $item->{notforloan}; # save for later use
+    my $fc = $item->{'frameworkcode'} || '';
 
-    # Populating with authorised values
-    foreach my $field ( keys %$item ) {
-        # If the koha field is mapped to a marc field
-        my $fc = $item->{'frameworkcode'} || '';
-        my ($f, $sf) = GetMarcFromKohaField("items.$field", $fc);
-        if ($f and $sf) {
-            # We replace the code with it's description
-            my $av = Koha::AuthorisedValues->search_by_marc_field({ frameworkcode => $fc, tagfield => $f, tagsubfield => $sf, });
-            $av = $av->count ? $av->unblessed : [];
-            my $authvals = { map { ( $_->{authorised_value} => $_->{lib} ) } @$av };
-            if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
-              $item->{$field} = $authvals->{$item->{$field}};
-            }
+    # Populating with authorised values description
+    foreach my $field (qw/ location notforloan itemlost damaged withdrawn /) {
+        my $av = Koha::AuthorisedValues->get_description_by_koha_field(
+            { frameworkcode => $fc, kohafield => "items.$field", authorised_value => $item->{$field} } );
+        if ( $av and defined $item->{$field} and defined $av->{lib} ) {
+            $item->{$field} = $av->{lib};
         }
     }
 
@@ -278,6 +320,24 @@ foreach my $item ( @scanned_items ) {
         additemtoresults( $item, $results );
     }
 
+    # Check for items shelved out of order
+    if ($out_of_order) {
+        unless ( $i == 0 ) {
+            my $previous_item = $scanned_items[ $i - 1 ];
+            if ( $previous_item && $item->{cn_sort} lt $previous_item->{cn_sort} ) {
+                $item->{problems}->{out_of_order} = 1;
+                additemtoresults( $item, $results );
+            }
+        }
+        unless ( $i == scalar(@scanned_items) ) {
+            my $next_item = $scanned_items[ $i + 1 ];
+            if ( $next_item && $item->{cn_sort} gt $next_item->{cn_sort} ) {
+                $item->{problems}->{out_of_order} = 1;
+                additemtoresults( $item, $results );
+            }
+        }
+    }
+
     # Report an item that is checked out (unusual!) or wrongly placed
     if( $item->{onloan} ) {
         $item->{problems}->{checkedout} = 1;
@@ -297,7 +357,7 @@ if ( $compareinv2barcd ) {
         my $barcode = $item->{barcode};
         if( !$barcode ) {
             $item->{problems}->{no_barcode} = 1;
-        } elsif ( grep /^$barcode$/, @scanned_barcodes ) {
+        } elsif ( grep { $_ eq $barcode } @scanned_barcodes ) {
             next;
         } else {
             $item->{problems}->{not_scanned} = 1;
@@ -311,9 +371,9 @@ my $loop = $uploadbarcodes
     ? [ map { $results->{$_} } keys %$results ]
     : $inventorylist // [];
 for my $item ( @$loop ) {
-    my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber});
-    $item->{title} = $biblio->{title};
-    $item->{author} = $biblio->{author};
+    my $biblio = Koha::Biblios->find( $item->{biblionumber} );
+    $item->{title} = $biblio->title;
+    $item->{author} = $biblio->author;
 }
 
 $template->param(
@@ -324,7 +384,7 @@ $template->param(
 
 # Export to csv
 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
-    eval {use Text::CSV};
+    eval {use Text::CSV ();};
     my $csv = Text::CSV->new or
             die Text::CSV->error_diag ();
     binmode STDOUT, ":encoding(UTF-8)";
@@ -333,30 +393,24 @@ if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
         -attachment => 'inventory.csv',
     );
 
-    my $columns_def_hashref = C4::Reports::Guided::_get_column_defs($input);
-    foreach my $key ( keys %$columns_def_hashref ) {
-        my $initkey = $key;
-        $key =~ s/[^\.]*\.//;
-        $columns_def_hashref->{$initkey}=NormalizeString($columns_def_hashref->{$initkey} // '');
-        $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
-    }
-
+    my $columns = Koha::Database::Columns->columns;
     my @translated_keys;
     for my $key (qw / biblioitems.title    biblio.author
                       items.barcode        items.itemnumber
-                      items.homebranch     items.location
+                      items.homebranch     items.location   items.ccode
                       items.itemcallnumber items.notforloan
                       items.itemlost       items.damaged
                       items.withdrawn      items.stocknumber
                       / ) {
-       push @translated_keys, $columns_def_hashref->{$key};
+        my ( $table, $column ) = split '\.', $key;
+        push @translated_keys, NormalizeString($columns->{$table}->{$column} // '');
     }
     push @translated_keys, 'problem' if $uploadbarcodes;
 
     $csv->combine(@translated_keys);
     print $csv->string, "\n";
 
-    my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
+    my @keys = qw/ title author barcode itemnumber homebranch location ccode itemcallnumber notforloan itemlost damaged withdrawn stocknumber /;
     for my $item ( @$loop ) {
         my @line;
         for my $key (@keys) {