X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=tools%2Finventory.pl;h=eff4fe2ffcce48c2bd162c3e02bf9e4fc267fe90;hb=a2ced89ab486d4cc46f66553ff8fe7dff1022b78;hp=b8cc31dae1e71ee55d7cd3d1e7cddb3fef5e1a60;hpb=bafceab9bd3fdc140b729b8adb67540a1a62effc;p=koha.git diff --git a/tools/inventory.pl b/tools/inventory.pl index b8cc31dae1..eff4fe2ffc 100755 --- a/tools/inventory.pl +++ b/tools/inventory.pl @@ -1,25 +1,24 @@ #!/usr/bin/perl # Copyright 2000-2009 Biblibre S.A -# John Soros +# John Soros # # 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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . -use strict; -use warnings; +use Modern::Perl; #need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth) use CGI qw ( -utf8 ); @@ -32,26 +31,31 @@ use C4::Output; use C4::Biblio; use C4::Items; use C4::Koha; -use C4::Branch; # GetBranches use C4::Circulation; use C4::Reports::Guided; #_get_column_defs use C4::Charset; + +use Koha::Biblios; use Koha::DateUtils; +use Koha::AuthorisedValues; +use Koha::BiblioFrameworks; +use Koha::ClassSources; 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 $itemtype=$input->param('itemtype'); # FIXME note, template does not currently supply this my $ignoreissued=$input->param('ignoreissued'); -my $datelastseen = $input->param('datelastseen'); -my $markseen = $input->param('markseen'); +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 ( $template, $borrowernumber, $cookie ) = get_template_and_user( { template_name => "tools/inventory.tt", @@ -63,25 +67,16 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( } ); - -my $branches = GetBranches(); -my @branch_loop; -for my $branch_hash (keys %$branches) { - push @branch_loop, {value => "$branch_hash", - branchname => $branches->{$branch_hash}->{'branchname'}, - selected => ($branch_hash eq $branchcode?1:0)}; -} - -@branch_loop = sort {$a->{branchname} cmp $b->{branchname}} @branch_loop; my @authorised_value_list; my $authorisedvalue_categories = ''; -my $frameworks = getframeworks(); -$frameworks->{''} = {frameworkcode => ''}; # Add the default framework +my $frameworks = Koha::BiblioFrameworks->search({}, { order_by => ['frameworktext'] })->unblessed; +unshift @$frameworks, { frameworkcode => '' }; -for my $fwk (keys %$frameworks){ - my $fwkcode = $frameworks->{$fwk}->{'frameworkcode'}; - my $authcode = GetAuthValCode('items.location', $fwkcode); +for my $fwk ( @$frameworks ){ + my $fwkcode = $fwk->{frameworkcode}; + 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 "; my $data=GetAuthorisedValues($authcode); @@ -93,20 +88,27 @@ for my $fwk (keys %$frameworks){ } my $statuses = []; +my @notforloans; for my $statfield (qw/items.notforloan items.itemlost items.withdrawn items.damaged/){ my $hash = {}; $hash->{fieldname} = $statfield; - $hash->{authcode} = GetAuthValCode($statfield); + 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; + @notforloans = map { $_->{'authorised_value'} } @$arr; + } $hash->{values} = $arr; push @$statuses, $hash; } } - $template->param( statuses => $statuses ); -my $staton = {}; #authorized values that are ticked +my $staton = {}; #authorized values that are ticked for my $authvfield (@$statuses) { $staton->{$authvfield->{fieldname}} = []; for my $authval (@{$authvfield->{values}}){ @@ -116,18 +118,11 @@ for my $authvfield (@$statuses) { } } -my $notforloanlist; -my $statussth = ''; -for my $authvfield (@$statuses) { - if ( scalar @{$staton->{$authvfield->{fieldname}}} > 0 ){ - my $joinedvals = join ',', @{$staton->{$authvfield->{fieldname}}}; - $statussth .= "$authvfield->{fieldname} in ($joinedvals) and "; - $notforloanlist = $joinedvals if ($authvfield->{fieldname} eq "items.notforloan"); - } -} -$statussth =~ s, and $,,g; +my @class_sources = Koha::ClassSources->search({ used => 1 }); +my $pref_class = C4::Context->preference("DefaultClassificationSource"); + + $template->param( - branchloop => \@branch_loop, authorised_values => \@authorised_value_list, today => dt_from_string, minlocation => $minlocation, @@ -138,19 +133,20 @@ $template->param( branch => $branch, datelastseen => $datelastseen, compareinv2barcd => $compareinv2barcd, - notforloanlist => $notforloanlist + uploadedbarcodesflag => $uploadbarcodes ? 1 : 0, + ignore_waiting_holds => $ignore_waiting_holds, + class_sources => \@class_sources, + pref_class => $pref_class ); -my @notforloans; -if (defined $notforloanlist) { - @notforloans = split(/,/, $notforloanlist); -} - +# Walk through uploaded barcodes, report errors, mark as seen, check in +my $results = {}; my @scanned_items; my @errorloop; +my $moddatecount = 0; if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { my $dbh = C4::Context->dbh; - my $date = dt_from_string( $input->param('setdate') ); + my $date = dt_from_string( scalar $input->param('setdate') ); $date = output_pref ( { dt => $date, dateformat => 'iso' } ); my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?"; @@ -158,9 +154,8 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { $strsth="select * from items where items.barcode =? and items.withdrawn = 1"; my $qwithdrawn = $dbh->prepare($strsth); - my $count = 0; - my @barcodes; + my @uploadedbarcodes; my $sth = $dbh->column_info(undef,undef,"items","barcode"); my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME'); @@ -169,8 +164,10 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { my $err_data=0; my $lines_read=0; binmode($uploadbarcodes, ":encoding(UTF-8)"); - while (my $barcode=<$uploadbarcodes>){ - $barcode =~ s/\r?\n$//; + while (my $barcode=<$uploadbarcodes>) { + push @uploadedbarcodes, grep { /\S/ } split( /[\n\r,;|-]/, $barcode ); + } + for my $barcode (@uploadedbarcodes) { next unless $barcode; ++$lines_read; if (length($barcode)>$barcode_size) { @@ -200,135 +197,161 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { } else { my $item = GetItem( '', $barcode ); if ( defined $item && $item->{'itemnumber'} ) { - ModItem( { datelastseen => $date }, undef, $item->{'itemnumber'} ); - push @scanned_items, $item; - $count++; - $qonloan->execute($barcode); - if ($qonloan->rows){ - my $data = $qonloan->fetchrow_hashref; - my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch}); - if ($doreturn){ - push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1} - } else { - push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1} + # Modify date last seen for scanned items, remove lost status + ModItem( { itemlost => 0, datelastseen => $date }, undef, $item->{'itemnumber'} ); + $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; + } else { + push @errorloop, { barcode => $barcode, ERR_ONLOAN_NOT_RET => 1 }; + } } } + push @scanned_items, $item; } else { - push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1}; + push @errorloop, { barcode => $barcode, ERR_BARCODE => 1 }; } } - } - - $template->param( date => $date, Number => $count ); + $template->param( date => $date ); $template->param( errorloop => \@errorloop ) if (@errorloop); } -# now build the result list: inventoried items if requested, and mis-placed items -always- -my $inventorylist; -my $wrongplacelist; -my @items_with_problems; -if ( $markseen or $op ) { - # retrieve all items in this range. - my $totalrecords; +# Build inventorylist: used as result list when you do not pass barcodes +# This list is also used when you want to compare with barcodes +my ( $inventorylist, $rightplacelist ); +if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) { + ( $inventorylist ) = GetItemsForInventory({ + minlocation => $minlocation, + maxlocation => $maxlocation, + class_source => $class_source, + location => $location, + ignoreissued => $ignoreissued, + datelastseen => $datelastseen, + branchcode => $branchcode, + branch => $branch, + offset => 0, + statushash => $staton, + ignore_waiting_holds => $ignore_waiting_holds, + }); +} +# Build rightplacelist used to check if a scanned item is in the right place. +if( @scanned_items ) { + ( $rightplacelist ) = GetItemsForInventory({ + minlocation => $minlocation, + maxlocation => $maxlocation, + class_source => $class_source, + location => $location, + ignoreissued => undef, + datelastseen => undef, + branchcode => $branchcode, + branch => $branch, + offset => 0, + statushash => undef, + ignore_waiting_holds => $ignore_waiting_holds, + }); + # Convert the structure to a hash on barcode + $rightplacelist = { + map { $_->{barcode} ? ( $_->{barcode}, $_ ) : (); } @$rightplacelist + }; +} - # We use datelastseen only when comparing the results to the barcode file. - my $paramdatelastseen = ($compareinv2barcd) ? $datelastseen : ''; - ($inventorylist, $totalrecords) = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype, $ignoreissued, $paramdatelastseen, $branchcode, $branch, 0, undef, $staton); +# Report scanned items that are on the wrong place, or have a wrong notforloan +# status, or are still checked out. +for ( my $i = 0; $i < @scanned_items; $i++ ) { - # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch) - ($wrongplacelist, $totalrecords) = GetItemsForInventory($minlocation, $maxlocation, $location, undef, undef, undef, $branchcode, $branch, 0, undef, undef); + my $item = $scanned_items[$i]; -} + $item->{notforloancode} = $item->{notforloan}; # save for later use + my $fc = $item->{'frameworkcode'} || ''; -# If "compare barcodes list to results" has been checked, we want to alert for missing items -if ( $compareinv2barcd ) { - # set "missing" flags for all items with a datelastseen (dls) before the choosen datelastseen (cdls) - my $dls = output_pref( { dt => dt_from_string( $datelastseen ), - dateformat => 'iso' } ); - foreach my $item ( @$inventorylist ) { - my $cdls = output_pref( { dt => dt_from_string( $_->{datelastseen} ), - dateformat => 'iso' } ); - if ( $cdls lt $dls ) { - $item->{problem} = 'missingitem'; - # We have to push a copy of the item, not the reference - push @items_with_problems, { %$item }; + # 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}; } } -} - + # If we have scanned items with a non-matching notforloan value + if( none { $item->{'notforloancode'} eq $_ } @notforloans ) { + $item->{problems}->{changestatus} = 1; + additemtoresults( $item, $results ); + } -# insert "wrongplace" to all scanned items that are not supposed to be in this range -# note this list is always displayed, whatever the librarian has choosen for comparison -my $moddatecount = 0; -foreach my $item ( @scanned_items ) { - - # Saving notforloan code before it's replaced by it's authorised value for later comparison - $item->{notforloancode} = $item->{notforloan}; - - # 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 $authvals = C4::Koha::GetKohaAuthorisedValuesFromField($f, $sf, $fc); - if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) { - $item->{$field} = $authvals->{$item->{$field}}; + # 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 ); } } } - next if $item->{onloan}; # skip checked out items - - # If we have scanned items with a non-matching notforloan value - if (none { $item->{'notforloancode'} eq $_ } @notforloans) { - $item->{problem} = 'changestatus'; - push @items_with_problems, { %$item }; - } - if (none { $item->{barcode} eq $_->{barcode} && !$_->{'onloan'} } @$wrongplacelist) { - $item->{problem} = 'wrongplace'; - push @items_with_problems, { %$item }; + # Report an item that is checked out (unusual!) or wrongly placed + if( $item->{onloan} ) { + $item->{problems}->{checkedout} = 1; + additemtoresults( $item, $results ); + next; # do not modify item + } elsif( !exists $rightplacelist->{ $item->{barcode} } ) { + $item->{problems}->{wrongplace} = 1; + additemtoresults( $item, $results ); } - - # Modify date last seen for scanned items - ModDateLastSeen($_->{'itemnumber'}); - $moddatecount++; } +# Compare barcodes with inventory list, report no_barcode and not_scanned. +# not_scanned can be interpreted as missing if ( $compareinv2barcd ) { my @scanned_barcodes = map {$_->{barcode}} @scanned_items; - for my $should_be_scanned ( @$inventorylist ) { - my $barcode = $should_be_scanned->{barcode}; - unless ( grep /^$barcode$/, @scanned_barcodes ) { - $should_be_scanned->{problem} = 'not_scanned'; - push @items_with_problems, { %$should_be_scanned }; + for my $item ( @$inventorylist ) { + my $barcode = $item->{barcode}; + if( !$barcode ) { + $item->{problems}->{no_barcode} = 1; + } elsif ( grep /^$barcode$/, @scanned_barcodes ) { + next; + } else { + $item->{problems}->{not_scanned} = 1; } + additemtoresults( $item, $results ); } } -for my $item ( @items_with_problems ) { - my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber}); - $item->{title} = $biblio->{title}; - $item->{author} = $biblio->{author}; +# Construct final results, add biblio information +my $loop = $uploadbarcodes + ? [ map { $results->{$_} } keys %$results ] + : $inventorylist // []; +for my $item ( @$loop ) { + my $biblio = Koha::Biblios->find( $item->{biblionumber} ); + $item->{title} = $biblio->title; + $item->{author} = $biblio->author; } -# If a barcode file is given, we want to show problems, else all items -my @results; -@results = $uploadbarcodes - ? @items_with_problems - : $op - ? @$inventorylist - : (); - $template->param( moddatecount => $moddatecount, - loop => \@results, - op => $op + loop => $loop, + op => $op, ); +# Export to csv if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ eval {use Text::CSV}; my $csv = Text::CSV->new or @@ -362,23 +385,28 @@ if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ $csv->combine(@translated_keys); print $csv->string, "\n"; - my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /; - for my $item ( @results ) { + my @keys = qw/ title author barcode itemnumber homebranch location itemcallnumber notforloan itemlost damaged withdrawn stocknumber /; + for my $item ( @$loop ) { my @line; for my $key (@keys) { push @line, $item->{$key}; } - if ( defined $item->{problem} ) { - if ( $item->{problem} eq 'wrongplace' ) { - push @line, "wrong place"; - } elsif ( $item->{problem} eq 'missingitem' ) { - push @line, "missing item"; - } elsif ( $item->{problem} eq 'changestatus' ) { - push @line, "change item status"; - } elsif ($item->{problem} eq 'not_scanned' ) { - push @line, "item not scanned"; + my $errstr = ''; + foreach my $key ( keys %{$item->{problems}} ) { + if( $key eq 'wrongplace' ) { + $errstr .= "wrong place,"; + } elsif( $key eq 'changestatus' ) { + $errstr .= "unknown notforloan status,"; + } elsif( $key eq 'not_scanned' ) { + $errstr .= "missing,"; + } elsif( $key eq 'no_barcode' ) { + $errstr .= "no barcode,"; + } elsif( $key eq 'checkedout' ) { + $errstr .= "checked out,"; } } + $errstr =~ s/,$//; + push @line, $errstr; $csv->combine(@line); print $csv->string, "\n"; } @@ -396,3 +424,10 @@ if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ } output_html_with_http_headers $input, $cookie, $template->output; + +sub additemtoresults { + my ( $item, $results ) = @_; + my $itemno = $item->{itemnumber}; + # since the script appends to $item, we can just overwrite the hash entry + $results->{$itemno} = $item; +}