X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=tools%2Finventory.pl;h=3da000afc0fe8d7771229ecf0fa2ff3818d1ac5f;hb=83b364ec56ae94a077542691f790d6153d90c4d1;hp=1ad8116fb078a2a792d3ba7b6b00b06f25f8f1b1;hpb=d019f86f671771a36006f8847712317e0337c02f;p=koha.git diff --git a/tools/inventory.pl b/tools/inventory.pl index 1ad8116fb0..3da000afc0 100755 --- a/tools/inventory.pl +++ b/tools/inventory.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl # Copyright 2000-2009 Biblibre S.A -# John Soros +# John Soros # # This file is part of Koha. # @@ -18,8 +18,7 @@ # 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 ); @@ -35,19 +34,19 @@ use C4::Koha; 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 List::MoreUtils qw( none ); - my $minlocation=$input->param('minlocation') || ''; my $maxlocation=$input->param('maxlocation'); $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 $datelastseen = $input->param('datelastseen'); # last inventory date my $branchcode = $input->param('branchcode') || ''; my $branch = $input->param('branch'); my $op = $input->param('op'); @@ -67,12 +66,12 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( 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 $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => $fwkcode, kohafield => 'items.location' }); +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 "; @@ -85,21 +84,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; - my $mss = Koha::MarcSubfieldStructures->search({ frameworkcode => '', kohafield => $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}}){ @@ -109,16 +114,6 @@ 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; $template->param( authorised_values => \@authorised_value_list, today => dt_from_string, @@ -130,16 +125,14 @@ $template->param( branch => $branch, datelastseen => $datelastseen, compareinv2barcd => $compareinv2barcd, - notforloanlist => $notforloanlist + uploadedbarcodesflag => $uploadbarcodes ? 1 : 0, ); -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( scalar $input->param('setdate') ); @@ -150,8 +143,6 @@ 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; @@ -163,10 +154,7 @@ if ( $uploadbarcodes && length($uploadbarcodes) > 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; + push @uploadedbarcodes, grep { /\S/ } split( /[\n\r,;|-]/, $barcode ); } for my $barcode (@uploadedbarcodes) { next unless $barcode; @@ -198,165 +186,136 @@ 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++; + # 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){ - push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1} + if( $doreturn ) { + $item->{onloan} = undef; + $item->{datelastseen} = dt_from_string; } else { - push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1} + 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; - - # We use datelastseen only when comparing the results to the barcode file. - my $paramdatelastseen = ($compareinv2barcd) ? $datelastseen : ''; - ($inventorylist, $totalrecords) = GetItemsForInventory( { +# 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, location => $location, - itemtype => $itemtype, ignoreissued => $ignoreissued, - datelastseen => $paramdatelastseen, + datelastseen => $datelastseen, branchcode => $branchcode, branch => $branch, offset => 0, - size => undef, statushash => $staton, - interface => 'staff', - } ); - - # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch) - ($wrongplacelist, $totalrecords) = GetItemsForInventory( { + }); +} +# Build rightplacelist used to check if a scanned item is in the right place. +if( @scanned_items ) { + ( $rightplacelist ) = GetItemsForInventory({ minlocation => $minlocation, maxlocation => $maxlocation, location => $location, - itemtype => undef, ignoreissued => undef, datelastseen => undef, branchcode => $branchcode, branch => $branch, offset => 0, - size => undef, statushash => undef, - interface => 'staff', - } ); - + }); + # Convert the structure to a hash on barcode + $rightplacelist = { + map { $_->{barcode} ? ( $_->{barcode}, $_ ) : (); } @$rightplacelist + }; } -# 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 chosen 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( $item->{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 }; - } - } -} - - - -# 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 chosen for comparison -my $moddatecount = 0; +# 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 ) { - - # 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 $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}}; - } + $item->{notforloancode} = $item->{notforloan}; # save for later use + my $fc = $item->{'frameworkcode'} || ''; + + # 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}; } } - 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 }; + if( none { $item->{'notforloancode'} eq $_ } @notforloans ) { + $item->{problems}->{changestatus} = 1; + additemtoresults( $item, $results ); } - # Modify date last seen for scanned items - ModDateLastSeen($item->{'itemnumber'}); - $moddatecount++; + # 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 ); + } } +# 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 @@ -390,23 +349,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"; } @@ -424,3 +388,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; +}