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>.
23 #need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth)
26 my $uploadbarcodes = $input->param('uploadbarcodes');
35 use C4
::Reports
::Guided
; #_get_column_defs
40 use Koha
::AuthorisedValues
;
41 use Koha
::BiblioFrameworks
;
42 use List
::MoreUtils
qw( none );
44 my $minlocation=$input->param('minlocation') || '';
45 my $maxlocation=$input->param('maxlocation');
46 $maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
47 my $location=$input->param('location') || '';
48 my $ignoreissued=$input->param('ignoreissued');
49 my $datelastseen = $input->param('datelastseen'); # last inventory date
50 my $branchcode = $input->param('branchcode') || '';
51 my $branch = $input->param('branch');
52 my $op = $input->param('op');
53 my $compareinv2barcd = $input->param('compareinv2barcd');
54 my $dont_checkin = $input->param('dont_checkin');
56 my ( $template, $borrowernumber, $cookie ) = get_template_and_user
(
57 { template_name
=> "tools/inventory.tt",
61 flagsrequired
=> { tools
=> 'inventory' },
66 my @authorised_value_list;
67 my $authorisedvalue_categories = '';
69 my $frameworks = Koha
::BiblioFrameworks
->search({}, { order_by
=> ['frameworktext'] })->unblessed;
70 unshift @
$frameworks, { frameworkcode
=> '' };
72 for my $fwk ( @
$frameworks ){
73 my $fwkcode = $fwk->{frameworkcode
};
74 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> $fwkcode, kohafield
=> 'items.location', authorised_value
=> [ -and => {'!=' => undef }, {'!=' => ''}] });
75 my $authcode = $mss->count ?
$mss->next->authorised_value : undef;
76 if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
77 $authorisedvalue_categories.="$authcode ";
78 my $data=GetAuthorisedValues
($authcode);
79 foreach my $value (@
$data){
80 $value->{selected
}=1 if ($value->{authorised_value
} eq ($location));
82 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
=> [ -and => {'!=' => undef }, {'!=' => ''}] });
92 $hash->{authcode
} = $mss->count ?
$mss->next->authorised_value : undef;
93 if ($hash->{authcode
}){
94 my $arr = GetAuthorisedValues
($hash->{authcode
});
95 if ( $statfield eq 'items.notforloan') {
96 # Add notforloan == 0 to the list of possible notforloan statuses
97 # The lib value is replaced in the template
98 push @
$arr, { authorised_value
=> 0, id
=> 'stat0' , lib
=> 'ignore' } if ! grep { $_->{authorised_value
} eq '0' } @
$arr;
99 @notforloans = map { $_->{'authorised_value'} } @
$arr;
101 $hash->{values} = $arr;
102 push @
$statuses, $hash;
106 $template->param( statuses
=> $statuses );
107 my $staton = {}; #authorized values that are ticked
108 for my $authvfield (@
$statuses) {
109 $staton->{$authvfield->{fieldname
}} = [];
110 for my $authval (@
{$authvfield->{values}}){
111 if ( defined $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) && $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) eq 'on' ){
112 push @
{$staton->{$authvfield->{fieldname
}}}, $authval->{authorised_value
};
118 authorised_values
=> \
@authorised_value_list,
119 today
=> dt_from_string
,
120 minlocation
=> $minlocation,
121 maxlocation
=> $maxlocation,
122 location
=> $location,
123 ignoreissued
=> $ignoreissued,
124 branchcode
=> $branchcode,
126 datelastseen
=> $datelastseen,
127 compareinv2barcd
=> $compareinv2barcd,
128 uploadedbarcodesflag
=> $uploadbarcodes ?
1 : 0,
131 # Walk through uploaded barcodes, report errors, mark as seen, check in
135 my $moddatecount = 0;
136 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
137 my $dbh = C4
::Context
->dbh;
138 my $date = dt_from_string
( scalar $input->param('setdate') );
139 $date = output_pref
( { dt
=> $date, dateformat
=> 'iso' } );
141 my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
142 my $qonloan = $dbh->prepare($strsth);
143 $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
144 my $qwithdrawn = $dbh->prepare($strsth);
147 my @uploadedbarcodes;
149 my $sth = $dbh->column_info(undef,undef,"items","barcode");
150 my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME');
151 my $barcode_size = $barcode_def->{barcode
}->{COLUMN_SIZE
};
155 binmode($uploadbarcodes, ":encoding(UTF-8)");
156 while (my $barcode=<$uploadbarcodes>) {
157 push @uploadedbarcodes, grep { /\S/ } split( /[\n\r,;|-]/, $barcode );
159 for my $barcode (@uploadedbarcodes) {
160 next unless $barcode;
162 if (length($barcode)>$barcode_size) {
165 my $check_barcode = $barcode;
166 $check_barcode =~ s/\p{Print}//g;
167 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
170 next if length($barcode)>$barcode_size;
171 next if ( length($check_barcode)>0 );
172 push @barcodes,$barcode;
174 $template->param( LinesRead
=> $lines_read );
176 push @errorloop, {'barcode'=>'No valid barcodes!'};
177 $op=''; # force the initial inventory screen again.
180 $template->param( err_length
=> $err_length,
181 err_data
=> $err_data );
183 foreach my $barcode (@barcodes) {
184 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
185 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
187 my $item = GetItem
( '', $barcode );
188 if ( defined $item && $item->{'itemnumber'} ) {
189 # Modify date last seen for scanned items, remove lost status
190 ModItem
( { itemlost
=> 0, datelastseen
=> $date }, undef, $item->{'itemnumber'} );
192 # update item hash accordingly
193 $item->{itemlost
} = 0;
194 $item->{datelastseen
} = $date;
195 unless ( $dont_checkin ) {
196 $qonloan->execute($barcode);
198 my $data = $qonloan->fetchrow_hashref;
199 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
201 $item->{onloan
} = undef;
202 $item->{datelastseen
} = dt_from_string
;
204 push @errorloop, { barcode
=> $barcode, ERR_ONLOAN_NOT_RET
=> 1 };
208 push @scanned_items, $item;
210 push @errorloop, { barcode
=> $barcode, ERR_BARCODE
=> 1 };
214 $template->param( date
=> $date );
215 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
218 # Build inventorylist: used as result list when you do not pass barcodes
219 # This list is also used when you want to compare with barcodes
220 my ( $inventorylist, $rightplacelist );
221 if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
222 ( $inventorylist ) = GetItemsForInventory
({
223 minlocation
=> $minlocation,
224 maxlocation
=> $maxlocation,
225 location
=> $location,
226 ignoreissued
=> $ignoreissued,
227 datelastseen
=> $datelastseen,
228 branchcode
=> $branchcode,
231 statushash
=> $staton,
234 # Build rightplacelist used to check if a scanned item is in the right place.
235 if( @scanned_items ) {
236 ( $rightplacelist ) = GetItemsForInventory
({
237 minlocation
=> $minlocation,
238 maxlocation
=> $maxlocation,
239 location
=> $location,
240 ignoreissued
=> undef,
241 datelastseen
=> undef,
242 branchcode
=> $branchcode,
247 # Convert the structure to a hash on barcode
249 map { $_->{barcode
} ?
( $_->{barcode
}, $_ ) : (); } @
$rightplacelist
253 # Report scanned items that are on the wrong place, or have a wrong notforloan
254 # status, or are still checked out.
255 foreach my $item ( @scanned_items ) {
256 $item->{notforloancode
} = $item->{notforloan
}; # save for later use
257 my $fc = $item->{'frameworkcode'} || '';
259 # Populating with authorised values description
260 foreach my $field (qw
/ location notforloan itemlost damaged withdrawn /) {
261 my $av = Koha
::AuthorisedValues
->get_description_by_koha_field(
262 { frameworkcode
=> $fc, kohafield
=> "items.$field", authorised_value
=> $item->{$field} } );
263 if ( $av and defined $item->{$field} and defined $av->{lib
} ) {
264 $item->{$field} = $av->{lib
};
268 # If we have scanned items with a non-matching notforloan value
269 if( none
{ $item->{'notforloancode'} eq $_ } @notforloans ) {
270 $item->{problems
}->{changestatus
} = 1;
271 additemtoresults
( $item, $results );
274 # Report an item that is checked out (unusual!) or wrongly placed
275 if( $item->{onloan
} ) {
276 $item->{problems
}->{checkedout
} = 1;
277 additemtoresults
( $item, $results );
278 next; # do not modify item
279 } elsif( !exists $rightplacelist->{ $item->{barcode
} } ) {
280 $item->{problems
}->{wrongplace
} = 1;
281 additemtoresults
( $item, $results );
285 # Compare barcodes with inventory list, report no_barcode and not_scanned.
286 # not_scanned can be interpreted as missing
287 if ( $compareinv2barcd ) {
288 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
289 for my $item ( @
$inventorylist ) {
290 my $barcode = $item->{barcode
};
292 $item->{problems
}->{no_barcode
} = 1;
293 } elsif ( grep /^$barcode$/, @scanned_barcodes ) {
296 $item->{problems
}->{not_scanned
} = 1;
298 additemtoresults
( $item, $results );
302 # Construct final results, add biblio information
303 my $loop = $uploadbarcodes
304 ?
[ map { $results->{$_} } keys %$results ]
305 : $inventorylist // [];
306 for my $item ( @
$loop ) {
307 my $biblio = Koha
::Biblios
->find( $item->{biblionumber
} );
308 $item->{title
} = $biblio->title;
309 $item->{author
} = $biblio->author;
313 moddatecount
=> $moddatecount,
319 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
320 eval {use Text
::CSV
};
321 my $csv = Text
::CSV
->new or
322 die Text
::CSV
->error_diag ();
323 binmode STDOUT
, ":encoding(UTF-8)";
324 print $input->header(
326 -attachment
=> 'inventory.csv',
329 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
330 foreach my $key ( keys %$columns_def_hashref ) {
332 $key =~ s/[^\.]*\.//;
333 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
334 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
338 for my $key (qw
/ biblioitems
.title biblio
.author
339 items
.barcode items
.itemnumber
340 items
.homebranch items
.location
341 items
.itemcallnumber items
.notforloan
342 items
.itemlost items
.damaged
343 items
.withdrawn items
.stocknumber
345 push @translated_keys, $columns_def_hashref->{$key};
347 push @translated_keys, 'problem' if $uploadbarcodes;
349 $csv->combine(@translated_keys);
350 print $csv->string, "\n";
352 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan itemlost damaged withdrawn stocknumber /;
353 for my $item ( @
$loop ) {
355 for my $key (@keys) {
356 push @line, $item->{$key};
359 foreach my $key ( keys %{$item->{problems
}} ) {
360 if( $key eq 'wrongplace' ) {
361 $errstr .= "wrong place,";
362 } elsif( $key eq 'changestatus' ) {
363 $errstr .= "unknown notforloan status,";
364 } elsif( $key eq 'not_scanned' ) {
365 $errstr .= "missing,";
366 } elsif( $key eq 'no_barcode' ) {
367 $errstr .= "no barcode,";
368 } elsif( $key eq 'checkedout' ) {
369 $errstr .= "checked out,";
374 $csv->combine(@line);
375 print $csv->string, "\n";
377 # Adding not found barcodes
378 foreach my $error (@errorloop) {
380 if ($error->{'ERR_BARCODE'}) {
381 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
382 push @line, "barcode not found";
383 $csv->combine(@line);
384 print $csv->string, "\n";
390 output_html_with_http_headers
$input, $cookie, $template->output;
392 sub additemtoresults
{
393 my ( $item, $results ) = @_;
394 my $itemno = $item->{itemnumber
};
395 # since the script appends to $item, we can just overwrite the hash entry
396 $results->{$itemno} = $item;