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
=> { not => 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
=> { not => 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 $barcode =~ s/\r/\n/g;
158 $barcode =~ s/\n\n/\n/g;
159 my @data = split(/\n/,$barcode);
160 push @uploadedbarcodes, @data;
162 for my $barcode (@uploadedbarcodes) {
163 next unless $barcode;
165 if (length($barcode)>$barcode_size) {
168 my $check_barcode = $barcode;
169 $check_barcode =~ s/\p{Print}//g;
170 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
173 next if length($barcode)>$barcode_size;
174 next if ( length($check_barcode)>0 );
175 push @barcodes,$barcode;
177 $template->param( LinesRead
=> $lines_read );
179 push @errorloop, {'barcode'=>'No valid barcodes!'};
180 $op=''; # force the initial inventory screen again.
183 $template->param( err_length
=> $err_length,
184 err_data
=> $err_data );
186 foreach my $barcode (@barcodes) {
187 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
188 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
190 my $item = GetItem
( '', $barcode );
191 if ( defined $item && $item->{'itemnumber'} ) {
192 # Modify date last seen for scanned items, remove lost status
193 ModItem
( { itemlost
=> 0, datelastseen
=> $date }, undef, $item->{'itemnumber'} );
195 # update item hash accordingly
196 $item->{itemlost
} = 0;
197 $item->{datelastseen
} = $date;
198 unless ( $dont_checkin ) {
199 $qonloan->execute($barcode);
201 my $data = $qonloan->fetchrow_hashref;
202 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
204 $item->{onloan
} = undef;
205 $item->{datelastseen
} = dt_from_string
;
207 push @errorloop, { barcode
=> $barcode, ERR_ONLOAN_NOT_RET
=> 1 };
211 push @scanned_items, $item;
213 push @errorloop, { barcode
=> $barcode, ERR_BARCODE
=> 1 };
217 $template->param( date
=> $date );
218 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
221 # Build inventorylist: used as result list when you do not pass barcodes
222 # This list is also used when you want to compare with barcodes
223 my ( $inventorylist, $rightplacelist );
224 if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
225 ( $inventorylist ) = GetItemsForInventory
({
226 minlocation
=> $minlocation,
227 maxlocation
=> $maxlocation,
228 location
=> $location,
229 ignoreissued
=> $ignoreissued,
230 datelastseen
=> $datelastseen,
231 branchcode
=> $branchcode,
234 statushash
=> $staton,
237 # Build rightplacelist used to check if a scanned item is in the right place.
238 if( @scanned_items ) {
239 ( $rightplacelist ) = GetItemsForInventory
({
240 minlocation
=> $minlocation,
241 maxlocation
=> $maxlocation,
242 location
=> $location,
243 ignoreissued
=> undef,
244 datelastseen
=> undef,
245 branchcode
=> $branchcode,
250 # Convert the structure to a hash on barcode
252 map { $_->{barcode
} ?
( $_->{barcode
}, $_ ) : (); } @
$rightplacelist
256 # Report scanned items that are on the wrong place, or have a wrong notforloan
257 # status, or are still checked out.
258 foreach my $item ( @scanned_items ) {
259 $item->{notforloancode
} = $item->{notforloan
}; # save for later use
260 my $fc = $item->{'frameworkcode'} || '';
262 # Populating with authorised values description
263 foreach my $field (qw
/ location notforloan itemlost damaged withdrawn /) {
264 my $av = Koha
::AuthorisedValues
->get_description_by_koha_field(
265 { frameworkcode
=> $fc, kohafield
=> "items.$field", authorised_value
=> $item->{$field} } );
266 if ( $av and defined $item->{$field} and defined $av->{lib
} ) {
267 $item->{$field} = $av->{lib
};
271 # If we have scanned items with a non-matching notforloan value
272 if( none
{ $item->{'notforloancode'} eq $_ } @notforloans ) {
273 $item->{problems
}->{changestatus
} = 1;
274 additemtoresults
( $item, $results );
277 # Report an item that is checked out (unusual!) or wrongly placed
278 if( $item->{onloan
} ) {
279 $item->{problems
}->{checkedout
} = 1;
280 additemtoresults
( $item, $results );
281 next; # do not modify item
282 } elsif( !exists $rightplacelist->{ $item->{barcode
} } ) {
283 $item->{problems
}->{wrongplace
} = 1;
284 additemtoresults
( $item, $results );
288 # Compare barcodes with inventory list, report no_barcode and not_scanned.
289 # not_scanned can be interpreted as missing
290 if ( $compareinv2barcd ) {
291 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
292 for my $item ( @
$inventorylist ) {
293 my $barcode = $item->{barcode
};
295 $item->{problems
}->{no_barcode
} = 1;
296 } elsif ( grep /^$barcode$/, @scanned_barcodes ) {
299 $item->{problems
}->{not_scanned
} = 1;
301 additemtoresults
( $item, $results );
305 # Construct final results, add biblio information
306 my $loop = $uploadbarcodes
307 ?
[ map { $results->{$_} } keys %$results ]
308 : $inventorylist // [];
309 for my $item ( @
$loop ) {
310 my $biblio = Koha
::Biblios
->find( $item->{biblionumber
} );
311 $item->{title
} = $biblio->title;
312 $item->{author
} = $biblio->author;
316 moddatecount
=> $moddatecount,
322 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
323 eval {use Text
::CSV
};
324 my $csv = Text
::CSV
->new or
325 die Text
::CSV
->error_diag ();
326 binmode STDOUT
, ":encoding(UTF-8)";
327 print $input->header(
329 -attachment
=> 'inventory.csv',
332 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
333 foreach my $key ( keys %$columns_def_hashref ) {
335 $key =~ s/[^\.]*\.//;
336 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
337 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
341 for my $key (qw
/ biblioitems
.title biblio
.author
342 items
.barcode items
.itemnumber
343 items
.homebranch items
.location
344 items
.itemcallnumber items
.notforloan
345 items
.itemlost items
.damaged
346 items
.withdrawn items
.stocknumber
348 push @translated_keys, $columns_def_hashref->{$key};
350 push @translated_keys, 'problem' if $uploadbarcodes;
352 $csv->combine(@translated_keys);
353 print $csv->string, "\n";
355 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
356 for my $item ( @
$loop ) {
358 for my $key (@keys) {
359 push @line, $item->{$key};
362 foreach my $key ( keys %{$item->{problems
}} ) {
363 if( $key eq 'wrongplace' ) {
364 $errstr .= "wrong place,";
365 } elsif( $key eq 'changestatus' ) {
366 $errstr .= "unknown notforloan status,";
367 } elsif( $key eq 'not_scanned' ) {
368 $errstr .= "missing,";
369 } elsif( $key eq 'no_barcode' ) {
370 $errstr .= "no barcode,";
371 } elsif( $key eq 'checkedout' ) {
372 $errstr .= "checked out,";
377 $csv->combine(@line);
378 print $csv->string, "\n";
380 # Adding not found barcodes
381 foreach my $error (@errorloop) {
383 if ($error->{'ERR_BARCODE'}) {
384 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
385 push @line, "barcode not found";
386 $csv->combine(@line);
387 print $csv->string, "\n";
393 output_html_with_http_headers
$input, $cookie, $template->output;
395 sub additemtoresults
{
396 my ( $item, $results ) = @_;
397 my $itemno = $item->{itemnumber
};
398 # since the script appends to $item, we can just overwrite the hash entry
399 $results->{$itemno} = $item;