Koha/tools/inventory.pl
Matthias Meusburger 0313856d9a Bug 7684: multiple fixes for inventory
* when a file was uploaded and the comparison with catalogue range
 requested, the comparison was wrong: the logic was wrong
* items that were not supposed to be scanned (ie: supposed to be on another shelf)
  didn't had the author and title, it was hard to retrieve them on the shelved
* some useful fields were missing, like homebranch, location, status
* the CSV export contained all the item information. It should contain the same
   informations as the screen

Behaviour now:
   * scan a list of barcode & select a range of location
   * if a barcode has been scanned and should not be (misplaced item),
       the information is displayed
   * if you choose "compare barcodes list to result option", the
     resulting list contains all items that have been scanned and those
     that were supposed to be. Any item not in both list appears with a
     specific message on the last column

Signed-off-by: Leila <koha.aixmarseille@gmail.com>
Signed-off-by: Koha Team Amu <koha.aixmarseille@gmail.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
2013-11-01 00:10:49 +00:00

352 lines
13 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 2 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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;
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::Dates qw/format_date format_date_in_iso/;
use C4::Koha;
use C4::Branch; # GetBranches
use C4::Circulation;
use C4::Reports::Guided; #_get_column_defs
use C4::Charset;
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 $offset = $input->param('offset');
my $markseen = $input->param('markseen');
$offset=0 unless $offset;
my $pagesize = $input->param('pagesize');
$pagesize=50 unless $pagesize;
my $branchcode = $input->param('branchcode') || '';
my $branch = $input->param('branch');
my $op = $input->param('op');
my $compareinv2barcd = $input->param('compareinv2barcd');
my $res; #contains the results loop
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
{ template_name => "tools/inventory.tmpl",
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 => C4::Dates->today(),
minlocation => $minlocation,
maxlocation => $maxlocation,
location => $location,
ignoreissued => $ignoreissued,
branchcode => $branchcode,
branch => $branch,
offset => $offset,
pagesize => $pagesize,
datelastseen => $datelastseen,
compareinv2barcd => $compareinv2barcd,
notforloanlist => $notforloanlist
);
my @notforloans;
if (defined $notforloanlist) {
@notforloans = split(/,/, $notforloanlist);
}
my @brcditems;
my $barcodelist;
my @errorloop;
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
my $dbh = C4::Context->dbh;
my $date = format_date_in_iso( $input->param('setdate') ) || C4::Dates->today('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;
while (my $barcode=<$uploadbarcodes>){
$barcode =~ s/\r?\n$//;
$barcodelist .= ($barcodelist) ? '|' . $barcode : $barcode;
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 @brcditems, $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};
}
}
}
$qonloan->finish;
$qwithdrawn->finish;
$template->param( date => format_date($date), Number => $count );
$template->param( errorloop => \@errorloop ) if (@errorloop);
}
$template->param(barcodelist => $barcodelist);
# now build the result list: inventoried items if requested, and mis-placed items -always-
my $inventorylist;
if ( $markseen or $op ) {
# retrieve all items in this range.
my $totalrecords;
($inventorylist, $totalrecords) = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype, $ignoreissued, '', $branchcode, $branch, 0, undef , $staton);
# Real copy
my @res_copy;
foreach (@$inventorylist) {
push @res_copy, $_;
}
$res = \@res_copy;
}
# set "missing" flags for all items with a datelastseen before the choosen datelastseen
foreach (@$res) { $_->{missingitem}=1 if C4::Dates->new($_->{datelastseen})->output('iso') lt C4::Dates->new($datelastseen)->output('iso'); }
# removing missing items from loop if "Compare barcodes list to results" has not been checked
@$res = grep {!$_->{missingitem} == 1 } @$res if (!$input->param('compareinv2barcd'));
# 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
foreach my $temp (@brcditems) {
# Saving notforloan code before it's replaced by it's authorised value for later comparison
$temp->{'notforloancode'} = $temp->{'notforloan'};
# Populating with authorised values
foreach (keys %$temp) {
# If the koha field is mapped to a marc field
my $fc = $temp->{'frameworkcode'} || '';
my ($f, $sf) = GetMarcFromKohaField("items.$_", $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 $temp->{$_} and defined $authvals->{$temp->{$_}}) {
$temp->{$_} = $authvals->{$temp->{$_}};
}
}
}
next if $temp->{onloan}; # skip checked out items
# If we have scanned items with a non-matching notforloan value
if (none { $temp->{'notforloancode'} eq $_ } @notforloans) {
$temp->{'changestatus'} = 1;
my $biblio = C4::Biblio::GetBiblioData($temp->{biblionumber});
$temp->{title} = $biblio->{title};
$temp->{author} = $biblio->{author};
$temp->{datelastseen} = format_date($temp->{datelastseen});
push @$res, $temp;
}
if (none { $temp->{barcode} eq $_->{barcode} && !$_->{'onloan'} } @$inventorylist) {
my $temp2 = { %$temp };
$temp2->{wrongplace}=1;
my $biblio = C4::Biblio::GetBiblioData($temp->{biblionumber});
$temp2->{title} = $biblio->{title};
$temp2->{author} = $biblio->{author};
$temp2->{datelastseen} = format_date($temp->{datelastseen});
push @$res, $temp2;
}
}
# Finally, modifying datelastseen for remaining items
my $moddatecount = 0;
foreach (@$res) {
unless ($_->{'missingitem'}) {
ModDateLastSeen($_->{'itemnumber'});
$moddatecount++;
}
}
# Removing items that don't have any problems from loop
@$res = grep { $_->{missingitem} || $_->{wrongplace} || $_->{changestatus} } @$res;
$template->param(
moddatecount => $moddatecount,
loop => $res,
nextoffset => ( $offset + $pagesize ),
prevoffset => ( $offset ? $offset - $pagesize : 0 ),
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 ();
print $input->header(
-type => 'text/csv',
-attachment => 'inventory.csv',
);
my $columns_def_hashref = C4::Reports::Guided::_get_column_defs();
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.stocknumber
/ ) {
push @translated_keys, $columns_def_hashref->{$key};
}
$csv->combine(@translated_keys);
print $csv->string, "\n";
my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged stocknumber /;
for my $re (@$res) {
my @line;
for my $key (@keys) {
push @line, $re->{$key};
}
if ($re->{wrongplace}) {
push @line, "wrong place";
} elsif ($re->{missingitem}) {
push @line, "missing item";
} elsif ($re->{changestatus}) {
push @line, "change item status";
}
$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;