3ebc081962
After receiving an error while attempt a simple inventory run, Two lines were changed from: ...$_->... to ...$item->... since the loop variable is $item. And $_ is not set to the expected hash reference, when there is a loop variable. This also helps explain the "Why are there blank dates on my last seen field?" problem that has been mentioned by users. TEST PLAN --------- 1) Apply this patch after a reset to master. 2) Log in to staff client 3) Add one item via z39.50, setting barcode to a known value (BARCODE1) 4) Wait for the reindex 5) Home -> Tools -> Inventory/Stocktaking 6) Browse for a file with the barcode in it 7) Set the library dropdown to the library branch of the added item. 8) Check 'Compare barcodes list to results:' 9) Click 'Submit' -- This should not die under plack. This should not generate blank last seen dates. The last seen dates should be as expected. 10) run koha qa test tools 11) Confirm the two change point correspond to the two change points in the patch which shall not be pushed to master. The test result comply with expected outcome outlined in test plan. Signed-off-by: Indranil Das Gupta (L2C2 Technologies) <indradg@gmail.com> Signed-off-by: Jonathan Druart <jonathan.druart@koha-community.org> Signed-off-by: Tomas Cohen Arazi <tomascohen@unc.edu.ar>
430 lines
15 KiB
Perl
Executable file
430 lines
15 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
|
|
# Copyright 2000-2009 Biblibre S.A
|
|
# John Soros <john.soros@biblibre.com>
|
|
#
|
|
# 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 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.
|
|
#
|
|
# 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;
|
|
|
|
#need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth)
|
|
use CGI qw ( -utf8 );
|
|
my $input = CGI->new;
|
|
my $uploadbarcodes = $input->param('uploadbarcodes');
|
|
|
|
use C4::Auth;
|
|
use C4::Context;
|
|
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::DateUtils;
|
|
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 $branchcode = $input->param('branchcode') || '';
|
|
my $branch = $input->param('branch');
|
|
my $op = $input->param('op');
|
|
my $compareinv2barcd = $input->param('compareinv2barcd');
|
|
|
|
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
|
|
{ template_name => "tools/inventory.tt",
|
|
query => $input,
|
|
type => "intranet",
|
|
authnotrequired => 0,
|
|
flagsrequired => { tools => 'inventory' },
|
|
debug => 1,
|
|
}
|
|
);
|
|
|
|
|
|
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
|
|
|
|
for my $fwk (keys %$frameworks){
|
|
my $fwkcode = $frameworks->{$fwk}->{'frameworkcode'};
|
|
my $authcode = GetAuthValCode('items.location', $fwkcode);
|
|
if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
|
|
$authorisedvalue_categories.="$authcode ";
|
|
my $data=GetAuthorisedValues($authcode);
|
|
foreach my $value (@$data){
|
|
$value->{selected}=1 if ($value->{authorised_value} eq ($location));
|
|
}
|
|
push @authorised_value_list,@$data;
|
|
}
|
|
}
|
|
|
|
my $statuses = [];
|
|
for my $statfield (qw/items.notforloan items.itemlost items.withdrawn items.damaged/){
|
|
my $hash = {};
|
|
$hash->{fieldname} = $statfield;
|
|
$hash->{authcode} = GetAuthValCode($statfield);
|
|
if ($hash->{authcode}){
|
|
my $arr = GetAuthorisedValues($hash->{authcode});
|
|
$hash->{values} = $arr;
|
|
push @$statuses, $hash;
|
|
}
|
|
}
|
|
|
|
|
|
$template->param( statuses => $statuses );
|
|
my $staton = {}; #authorized values that are ticked
|
|
for my $authvfield (@$statuses) {
|
|
$staton->{$authvfield->{fieldname}} = [];
|
|
for my $authval (@{$authvfield->{values}}){
|
|
if ( defined $input->param('status-' . $authvfield->{fieldname} . '-' . $authval->{authorised_value}) && $input->param('status-' . $authvfield->{fieldname} . '-' . $authval->{authorised_value}) eq 'on' ){
|
|
push @{$staton->{$authvfield->{fieldname}}}, $authval->{authorised_value};
|
|
}
|
|
}
|
|
}
|
|
|
|
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(
|
|
branchloop => \@branch_loop,
|
|
authorised_values => \@authorised_value_list,
|
|
today => dt_from_string,
|
|
minlocation => $minlocation,
|
|
maxlocation => $maxlocation,
|
|
location => $location,
|
|
ignoreissued => $ignoreissued,
|
|
branchcode => $branchcode,
|
|
branch => $branch,
|
|
datelastseen => $datelastseen,
|
|
compareinv2barcd => $compareinv2barcd,
|
|
notforloanlist => $notforloanlist
|
|
);
|
|
|
|
my @notforloans;
|
|
if (defined $notforloanlist) {
|
|
@notforloans = split(/,/, $notforloanlist);
|
|
}
|
|
|
|
my @scanned_items;
|
|
my @errorloop;
|
|
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
|
|
my $dbh = C4::Context->dbh;
|
|
my $date = dt_from_string( $input->param('setdate') );
|
|
$date = output_pref ( { dt => $date, dateformat => 'iso' } );
|
|
|
|
my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
|
|
my $qonloan = $dbh->prepare($strsth);
|
|
$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');
|
|
my $barcode_size = $barcode_def->{barcode}->{COLUMN_SIZE};
|
|
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;
|
|
}
|
|
for my $barcode (@uploadedbarcodes) {
|
|
next unless $barcode;
|
|
++$lines_read;
|
|
if (length($barcode)>$barcode_size) {
|
|
$err_length += 1;
|
|
}
|
|
my $check_barcode = $barcode;
|
|
$check_barcode =~ s/\p{Print}//g;
|
|
if (length($check_barcode)>0) { # Only printable unicode characters allowed.
|
|
$err_data += 1;
|
|
}
|
|
next if length($barcode)>$barcode_size;
|
|
next if ( length($check_barcode)>0 );
|
|
push @barcodes,$barcode;
|
|
}
|
|
$template->param( LinesRead => $lines_read );
|
|
if (! @barcodes) {
|
|
push @errorloop, {'barcode'=>'No valid barcodes!'};
|
|
$op=''; # force the initial inventory screen again.
|
|
}
|
|
else {
|
|
$template->param( err_length => $err_length,
|
|
err_data => $err_data );
|
|
}
|
|
foreach my $barcode (@barcodes) {
|
|
if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
|
|
push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
|
|
} 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}
|
|
}
|
|
}
|
|
} else {
|
|
push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
$template->param( date => $date, Number => $count );
|
|
$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( {
|
|
minlocation => $minlocation,
|
|
maxlocation => $maxlocation,
|
|
location => $location,
|
|
itemtype => $itemtype,
|
|
ignoreissued => $ignoreissued,
|
|
datelastseen => $paramdatelastseen,
|
|
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( {
|
|
minlocation => $minlocation,
|
|
maxlocation => $maxlocation,
|
|
location => $location,
|
|
itemtype => undef,
|
|
ignoreissued => undef,
|
|
datelastseen => undef,
|
|
branchcode => $branchcode,
|
|
branch => $branch,
|
|
offset => 0,
|
|
size => undef,
|
|
statushash => undef,
|
|
interface => 'staff',
|
|
} );
|
|
|
|
}
|
|
|
|
# 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( $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 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}};
|
|
}
|
|
}
|
|
}
|
|
|
|
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 };
|
|
}
|
|
|
|
# Modify date last seen for scanned items
|
|
ModDateLastSeen($item->{'itemnumber'});
|
|
$moddatecount++;
|
|
}
|
|
|
|
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 ( @items_with_problems ) {
|
|
my $biblio = C4::Biblio::GetBiblioData($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
|
|
);
|
|
|
|
if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
|
|
eval {use Text::CSV};
|
|
my $csv = Text::CSV->new or
|
|
die Text::CSV->error_diag ();
|
|
binmode STDOUT, ":encoding(UTF-8)";
|
|
print $input->header(
|
|
-type => 'text/csv',
|
|
-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 @translated_keys;
|
|
for my $key (qw / biblioitems.title biblio.author
|
|
items.barcode items.itemnumber
|
|
items.homebranch items.location
|
|
items.itemcallnumber items.notforloan
|
|
items.itemlost items.damaged
|
|
items.withdrawn items.stocknumber
|
|
/ ) {
|
|
push @translated_keys, $columns_def_hashref->{$key};
|
|
}
|
|
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 /;
|
|
for my $item ( @results ) {
|
|
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";
|
|
}
|
|
}
|
|
$csv->combine(@line);
|
|
print $csv->string, "\n";
|
|
}
|
|
# Adding not found barcodes
|
|
foreach my $error (@errorloop) {
|
|
my @line;
|
|
if ($error->{'ERR_BARCODE'}) {
|
|
push @line, map { $_ eq 'barcode' ? $error->{'barcode'} : ''} @keys;
|
|
push @line, "barcode not found";
|
|
$csv->combine(@line);
|
|
print $csv->string, "\n";
|
|
}
|
|
}
|
|
exit;
|
|
}
|
|
|
|
output_html_with_http_headers $input, $cookie, $template->output;
|