3 # Copyright 2000-2009 Biblibre S.A
4 # John Soros <john.soros@biblibre.com>
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24 #need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth)
27 my $uploadbarcodes = $input->param('uploadbarcodes');
36 use C4
::Reports
::Guided
; #_get_column_defs
39 use Koha
::AuthorisedValues
;
40 use List
::MoreUtils
qw( none );
43 my $minlocation=$input->param('minlocation') || '';
44 my $maxlocation=$input->param('maxlocation');
45 $maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
46 my $location=$input->param('location') || '';
47 my $itemtype=$input->param('itemtype'); # FIXME note, template does not currently supply this
48 my $ignoreissued=$input->param('ignoreissued');
49 my $datelastseen = $input->param('datelastseen');
50 my $markseen = $input->param('markseen');
51 my $branchcode = $input->param('branchcode') || '';
52 my $branch = $input->param('branch');
53 my $op = $input->param('op');
54 my $compareinv2barcd = $input->param('compareinv2barcd');
55 my $dont_checkin = $input->param('dont_checkin');
57 my ( $template, $borrowernumber, $cookie ) = get_template_and_user
(
58 { template_name
=> "tools/inventory.tt",
62 flagsrequired
=> { tools
=> 'inventory' },
67 my @authorised_value_list;
68 my $authorisedvalue_categories = '';
70 my $frameworks = getframeworks
();
71 $frameworks->{''} = {frameworkcode
=> ''}; # Add the default framework
73 for my $fwk (keys %$frameworks){
74 my $fwkcode = $frameworks->{$fwk}->{'frameworkcode'};
75 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> $fwkcode, kohafield
=> 'items.location', authorised_value
=> { not => undef } });
76 my $authcode = $mss->count ?
$mss->next->authorised_value : undef;
77 if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
78 $authorisedvalue_categories.="$authcode ";
79 my $data=GetAuthorisedValues
($authcode);
80 foreach my $value (@
$data){
81 $value->{selected
}=1 if ($value->{authorised_value
} eq ($location));
83 push @authorised_value_list,@
$data;
88 for my $statfield (qw
/items.notforloan items.itemlost items.withdrawn items.damaged/){
90 $hash->{fieldname
} = $statfield;
91 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> '', kohafield
=> $statfield, authorised_value
=> { not => undef } });
92 $hash->{authcode
} = $mss->count ?
$mss->next->authorised_value : undef;
93 if ($hash->{authcode
}){
94 my $arr = GetAuthorisedValues
($hash->{authcode
});
95 $hash->{values} = $arr;
96 push @
$statuses, $hash;
101 $template->param( statuses
=> $statuses );
102 my $staton = {}; #authorized values that are ticked
103 for my $authvfield (@
$statuses) {
104 $staton->{$authvfield->{fieldname
}} = [];
105 for my $authval (@
{$authvfield->{values}}){
106 if ( defined $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) && $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) eq 'on' ){
107 push @
{$staton->{$authvfield->{fieldname
}}}, $authval->{authorised_value
};
114 for my $authvfield (@
$statuses) {
115 if ( scalar @
{$staton->{$authvfield->{fieldname
}}} > 0 ){
116 my $joinedvals = join ',', @
{$staton->{$authvfield->{fieldname
}}};
117 $statussth .= "$authvfield->{fieldname} in ($joinedvals) and ";
118 $notforloanlist = $joinedvals if ($authvfield->{fieldname
} eq "items.notforloan");
121 $statussth =~ s
, and $,,g
;
123 authorised_values
=> \
@authorised_value_list,
124 today
=> dt_from_string
,
125 minlocation
=> $minlocation,
126 maxlocation
=> $maxlocation,
127 location
=> $location,
128 ignoreissued
=> $ignoreissued,
129 branchcode
=> $branchcode,
131 datelastseen
=> $datelastseen,
132 compareinv2barcd
=> $compareinv2barcd,
133 notforloanlist
=> $notforloanlist
137 if (defined $notforloanlist) {
138 @notforloans = split(/,/, $notforloanlist);
143 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
144 my $dbh = C4
::Context
->dbh;
145 my $date = dt_from_string
( scalar $input->param('setdate') );
146 $date = output_pref
( { dt
=> $date, dateformat
=> 'iso' } );
148 my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
149 my $qonloan = $dbh->prepare($strsth);
150 $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
151 my $qwithdrawn = $dbh->prepare($strsth);
156 my @uploadedbarcodes;
158 my $sth = $dbh->column_info(undef,undef,"items","barcode");
159 my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME');
160 my $barcode_size = $barcode_def->{barcode
}->{COLUMN_SIZE
};
164 binmode($uploadbarcodes, ":encoding(UTF-8)");
165 while (my $barcode=<$uploadbarcodes>) {
166 $barcode =~ s/\r/\n/g;
167 $barcode =~ s/\n\n/\n/g;
168 my @data = split(/\n/,$barcode);
169 push @uploadedbarcodes, @data;
171 for my $barcode (@uploadedbarcodes) {
172 next unless $barcode;
174 if (length($barcode)>$barcode_size) {
177 my $check_barcode = $barcode;
178 $check_barcode =~ s/\p{Print}//g;
179 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
182 next if length($barcode)>$barcode_size;
183 next if ( length($check_barcode)>0 );
184 push @barcodes,$barcode;
186 $template->param( LinesRead
=> $lines_read );
188 push @errorloop, {'barcode'=>'No valid barcodes!'};
189 $op=''; # force the initial inventory screen again.
192 $template->param( err_length
=> $err_length,
193 err_data
=> $err_data );
195 foreach my $barcode (@barcodes) {
196 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
197 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
199 my $item = GetItem
( '', $barcode );
200 if ( defined $item && $item->{'itemnumber'} ) {
201 ModItem
( { datelastseen
=> $date }, undef, $item->{'itemnumber'} );
202 push @scanned_items, $item;
204 unless ( $dont_checkin ) {
205 $qonloan->execute($barcode);
207 my $data = $qonloan->fetchrow_hashref;
208 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
210 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
212 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
217 push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
223 $template->param( date
=> $date, Number
=> $count );
224 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
227 # now build the result list: inventoried items if requested, and mis-placed items -always-
230 my @items_with_problems;
231 if ( $markseen or $op ) {
232 # retrieve all items in this range.
235 # We use datelastseen only when comparing the results to the barcode file.
236 my $paramdatelastseen = ($compareinv2barcd) ?
$datelastseen : '';
237 ($inventorylist, $totalrecords) = GetItemsForInventory
( {
238 minlocation
=> $minlocation,
239 maxlocation
=> $maxlocation,
240 location
=> $location,
241 itemtype
=> $itemtype,
242 ignoreissued
=> $ignoreissued,
243 datelastseen
=> $paramdatelastseen,
244 branchcode
=> $branchcode,
248 statushash
=> $staton,
251 # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
252 ($wrongplacelist, $totalrecords) = GetItemsForInventory
( {
253 minlocation
=> $minlocation,
254 maxlocation
=> $maxlocation,
255 location
=> $location,
257 ignoreissued
=> undef,
258 datelastseen
=> undef,
259 branchcode
=> $branchcode,
268 # If "compare barcodes list to results" has been checked, we want to alert for missing items
269 if ( $compareinv2barcd ) {
270 # set "missing" flags for all items with a datelastseen (dls) before the chosen datelastseen (cdls)
271 my $dls = output_pref
( { dt
=> dt_from_string
( $datelastseen ),
272 dateformat
=> 'iso' } );
273 foreach my $item ( @
$inventorylist ) {
274 my $cdls = output_pref
( { dt
=> dt_from_string
( $item->{datelastseen
} ),
275 dateformat
=> 'iso' } );
276 if ( $cdls lt $dls ) {
277 $item->{problem
} = 'missingitem';
278 # We have to push a copy of the item, not the reference
279 push @items_with_problems, { %$item };
286 # insert "wrongplace" to all scanned items that are not supposed to be in this range
287 # note this list is always displayed, whatever the librarian has chosen for comparison
288 my $moddatecount = 0;
289 foreach my $item ( @scanned_items ) {
291 # Saving notforloan code before it's replaced by it's authorised value for later comparison
292 $item->{notforloancode
} = $item->{notforloan
};
294 # Populating with authorised values
295 foreach my $field ( keys %$item ) {
296 # If the koha field is mapped to a marc field
297 my $fc = $item->{'frameworkcode'} || '';
298 my ($f, $sf) = GetMarcFromKohaField
("items.$field", $fc);
300 # We replace the code with it's description
301 my $av = Koha
::AuthorisedValues
->search_by_marc_field({ frameworkcode
=> $fc, tagfield
=> $f, tagsubfield
=> $sf, });
302 $av = $av->count ?
$av->unblessed : [];
303 my $authvals = { map { ( $_->{authorised_value
} => $_->{lib
} ) } @
$av };
304 if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
305 $item->{$field} = $authvals->{$item->{$field}};
310 next if $item->{onloan
}; # skip checked out items
312 # If we have scanned items with a non-matching notforloan value
313 if (none
{ $item->{'notforloancode'} eq $_ } @notforloans) {
314 $item->{problem
} = 'changestatus';
315 push @items_with_problems, { %$item };
317 if (none
{ $item->{barcode
} eq $_->{barcode
} && !$_->{'onloan'} } @
$wrongplacelist) {
318 $item->{problem
} = 'wrongplace';
319 push @items_with_problems, { %$item };
322 # Modify date last seen for scanned items
323 ModDateLastSeen
($item->{'itemnumber'});
327 if ( $compareinv2barcd ) {
328 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
329 for my $should_be_scanned ( @
$inventorylist ) {
330 my $barcode = $should_be_scanned->{barcode
};
331 unless ( grep /^$barcode$/, @scanned_barcodes ) {
332 $should_be_scanned->{problem
} = 'not_scanned';
333 push @items_with_problems, { %$should_be_scanned };
338 for my $item ( @items_with_problems ) {
339 my $biblio = C4
::Biblio
::GetBiblioData
($item->{biblionumber
});
340 $item->{title
} = $biblio->{title
};
341 $item->{author
} = $biblio->{author
};
344 # If a barcode file is given, we want to show problems, else all items
346 @results = $uploadbarcodes
347 ?
@items_with_problems
353 moddatecount
=> $moddatecount,
358 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
359 eval {use Text
::CSV
};
360 my $csv = Text
::CSV
->new or
361 die Text
::CSV
->error_diag ();
362 binmode STDOUT
, ":encoding(UTF-8)";
363 print $input->header(
365 -attachment
=> 'inventory.csv',
368 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
369 foreach my $key ( keys %$columns_def_hashref ) {
371 $key =~ s/[^\.]*\.//;
372 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
373 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
377 for my $key (qw
/ biblioitems
.title biblio
.author
378 items
.barcode items
.itemnumber
379 items
.homebranch items
.location
380 items
.itemcallnumber items
.notforloan
381 items
.itemlost items
.damaged
382 items
.withdrawn items
.stocknumber
384 push @translated_keys, $columns_def_hashref->{$key};
386 push @translated_keys, 'problem' if $uploadbarcodes;
388 $csv->combine(@translated_keys);
389 print $csv->string, "\n";
391 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
392 for my $item ( @results ) {
394 for my $key (@keys) {
395 push @line, $item->{$key};
397 if ( defined $item->{problem
} ) {
398 if ( $item->{problem
} eq 'wrongplace' ) {
399 push @line, "wrong place";
400 } elsif ( $item->{problem
} eq 'missingitem' ) {
401 push @line, "missing item";
402 } elsif ( $item->{problem
} eq 'changestatus' ) {
403 push @line, "change item status";
404 } elsif ($item->{problem
} eq 'not_scanned' ) {
405 push @line, "item not scanned";
408 $csv->combine(@line);
409 print $csv->string, "\n";
411 # Adding not found barcodes
412 foreach my $error (@errorloop) {
414 if ($error->{'ERR_BARCODE'}) {
415 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
416 push @line, "barcode not found";
417 $csv->combine(@line);
418 print $csv->string, "\n";
424 output_html_with_http_headers
$input, $cookie, $template->output;