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 Koha
::BiblioFrameworks
;
41 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 $itemtype=$input->param('itemtype'); # FIXME note, template does not currently supply this
49 my $ignoreissued=$input->param('ignoreissued');
50 my $datelastseen = $input->param('datelastseen');
51 my $markseen = $input->param('markseen');
52 my $branchcode = $input->param('branchcode') || '';
53 my $branch = $input->param('branch');
54 my $op = $input->param('op');
55 my $compareinv2barcd = $input->param('compareinv2barcd');
56 my $dont_checkin = $input->param('dont_checkin');
58 my ( $template, $borrowernumber, $cookie ) = get_template_and_user
(
59 { template_name
=> "tools/inventory.tt",
63 flagsrequired
=> { tools
=> 'inventory' },
68 my @authorised_value_list;
69 my $authorisedvalue_categories = '';
71 my $frameworks = Koha
::BiblioFrameworks
->search({}, { order_by
=> ['frameworktext'] })->unblessed;
72 unshift @
$frameworks, { frameworkcode
=> '' };
74 for my $fwk ( @
$frameworks ){
75 my $fwkcode = $fwk->{frameworkcode
};
76 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> $fwkcode, kohafield
=> 'items.location', authorised_value
=> { not => undef } });
77 my $authcode = $mss->count ?
$mss->next->authorised_value : undef;
78 if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
79 $authorisedvalue_categories.="$authcode ";
80 my $data=GetAuthorisedValues
($authcode);
81 foreach my $value (@
$data){
82 $value->{selected
}=1 if ($value->{authorised_value
} eq ($location));
84 push @authorised_value_list,@
$data;
89 for my $statfield (qw
/items.notforloan items.itemlost items.withdrawn items.damaged/){
91 $hash->{fieldname
} = $statfield;
92 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> '', kohafield
=> $statfield, authorised_value
=> { not => undef } });
93 $hash->{authcode
} = $mss->count ?
$mss->next->authorised_value : undef;
94 if ($hash->{authcode
}){
95 my $arr = GetAuthorisedValues
($hash->{authcode
});
96 $hash->{values} = $arr;
97 push @
$statuses, $hash;
102 $template->param( statuses
=> $statuses );
103 my $staton = {}; #authorized values that are ticked
104 for my $authvfield (@
$statuses) {
105 $staton->{$authvfield->{fieldname
}} = [];
106 for my $authval (@
{$authvfield->{values}}){
107 if ( defined $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) && $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) eq 'on' ){
108 push @
{$staton->{$authvfield->{fieldname
}}}, $authval->{authorised_value
};
115 for my $authvfield (@
$statuses) {
116 if ( scalar @
{$staton->{$authvfield->{fieldname
}}} > 0 ){
117 my $joinedvals = join ',', @
{$staton->{$authvfield->{fieldname
}}};
118 $statussth .= "$authvfield->{fieldname} in ($joinedvals) and ";
119 $notforloanlist = $joinedvals if ($authvfield->{fieldname
} eq "items.notforloan");
122 $statussth =~ s
, and $,,g
;
124 authorised_values
=> \
@authorised_value_list,
125 today
=> dt_from_string
,
126 minlocation
=> $minlocation,
127 maxlocation
=> $maxlocation,
128 location
=> $location,
129 ignoreissued
=> $ignoreissued,
130 branchcode
=> $branchcode,
132 datelastseen
=> $datelastseen,
133 compareinv2barcd
=> $compareinv2barcd,
134 notforloanlist
=> $notforloanlist
138 if (defined $notforloanlist) {
139 @notforloans = split(/,/, $notforloanlist);
144 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
145 my $dbh = C4
::Context
->dbh;
146 my $date = dt_from_string
( scalar $input->param('setdate') );
147 $date = output_pref
( { dt
=> $date, dateformat
=> 'iso' } );
149 my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
150 my $qonloan = $dbh->prepare($strsth);
151 $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
152 my $qwithdrawn = $dbh->prepare($strsth);
157 my @uploadedbarcodes;
159 my $sth = $dbh->column_info(undef,undef,"items","barcode");
160 my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME');
161 my $barcode_size = $barcode_def->{barcode
}->{COLUMN_SIZE
};
165 binmode($uploadbarcodes, ":encoding(UTF-8)");
166 while (my $barcode=<$uploadbarcodes>) {
167 $barcode =~ s/\r/\n/g;
168 $barcode =~ s/\n\n/\n/g;
169 my @data = split(/\n/,$barcode);
170 push @uploadedbarcodes, @data;
172 for my $barcode (@uploadedbarcodes) {
173 next unless $barcode;
175 if (length($barcode)>$barcode_size) {
178 my $check_barcode = $barcode;
179 $check_barcode =~ s/\p{Print}//g;
180 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
183 next if length($barcode)>$barcode_size;
184 next if ( length($check_barcode)>0 );
185 push @barcodes,$barcode;
187 $template->param( LinesRead
=> $lines_read );
189 push @errorloop, {'barcode'=>'No valid barcodes!'};
190 $op=''; # force the initial inventory screen again.
193 $template->param( err_length
=> $err_length,
194 err_data
=> $err_data );
196 foreach my $barcode (@barcodes) {
197 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
198 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
200 my $item = GetItem
( '', $barcode );
201 if ( defined $item && $item->{'itemnumber'} ) {
202 ModItem
( { datelastseen
=> $date }, undef, $item->{'itemnumber'} );
203 push @scanned_items, $item;
205 unless ( $dont_checkin ) {
206 $qonloan->execute($barcode);
208 my $data = $qonloan->fetchrow_hashref;
209 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
211 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
213 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
218 push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
224 $template->param( date
=> $date, Number
=> $count );
225 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
228 # now build the result list: inventoried items if requested, and mis-placed items -always-
231 my @items_with_problems;
232 if ( $markseen or $op ) {
233 # retrieve all items in this range.
236 # We use datelastseen only when comparing the results to the barcode file.
237 my $paramdatelastseen = ($compareinv2barcd) ?
$datelastseen : '';
238 ($inventorylist, $totalrecords) = GetItemsForInventory
( {
239 minlocation
=> $minlocation,
240 maxlocation
=> $maxlocation,
241 location
=> $location,
242 itemtype
=> $itemtype,
243 ignoreissued
=> $ignoreissued,
244 datelastseen
=> $paramdatelastseen,
245 branchcode
=> $branchcode,
249 statushash
=> $staton,
252 # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
253 ($wrongplacelist, $totalrecords) = GetItemsForInventory
( {
254 minlocation
=> $minlocation,
255 maxlocation
=> $maxlocation,
256 location
=> $location,
258 ignoreissued
=> undef,
259 datelastseen
=> undef,
260 branchcode
=> $branchcode,
269 # If "compare barcodes list to results" has been checked, we want to alert for missing items
270 if ( $compareinv2barcd ) {
271 # set "missing" flags for all items with a datelastseen (dls) before the chosen datelastseen (cdls)
272 my $dls = output_pref
( { dt
=> dt_from_string
( $datelastseen ),
273 dateformat
=> 'iso' } );
274 foreach my $item ( @
$inventorylist ) {
275 my $cdls = output_pref
( { dt
=> dt_from_string
( $item->{datelastseen
} ),
276 dateformat
=> 'iso' } );
277 if ( $cdls lt $dls ) {
278 $item->{problem
} = 'missingitem';
279 # We have to push a copy of the item, not the reference
280 push @items_with_problems, { %$item };
287 # insert "wrongplace" to all scanned items that are not supposed to be in this range
288 # note this list is always displayed, whatever the librarian has chosen for comparison
289 my $moddatecount = 0;
290 foreach my $item ( @scanned_items ) {
292 # Saving notforloan code before it's replaced by it's authorised value for later comparison
293 $item->{notforloancode
} = $item->{notforloan
};
295 # Populating with authorised values
296 foreach my $field ( keys %$item ) {
297 # If the koha field is mapped to a marc field
298 my $fc = $item->{'frameworkcode'} || '';
299 my ($f, $sf) = GetMarcFromKohaField
("items.$field", $fc);
301 # We replace the code with it's description
302 my $av = Koha
::AuthorisedValues
->search_by_marc_field({ frameworkcode
=> $fc, tagfield
=> $f, tagsubfield
=> $sf, });
303 $av = $av->count ?
$av->unblessed : [];
304 my $authvals = { map { ( $_->{authorised_value
} => $_->{lib
} ) } @
$av };
305 if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
306 $item->{$field} = $authvals->{$item->{$field}};
311 next if $item->{onloan
}; # skip checked out items
313 # If we have scanned items with a non-matching notforloan value
314 if (none
{ $item->{'notforloancode'} eq $_ } @notforloans) {
315 $item->{problem
} = 'changestatus';
316 push @items_with_problems, { %$item };
318 if (none
{ $item->{barcode
} eq $_->{barcode
} && !$_->{'onloan'} } @
$wrongplacelist) {
319 $item->{problem
} = 'wrongplace';
320 push @items_with_problems, { %$item };
323 # Modify date last seen for scanned items
324 ModDateLastSeen
($item->{'itemnumber'});
328 if ( $compareinv2barcd ) {
329 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
330 for my $should_be_scanned ( @
$inventorylist ) {
331 my $barcode = $should_be_scanned->{barcode
};
332 unless ( grep /^$barcode$/, @scanned_barcodes ) {
333 $should_be_scanned->{problem
} = 'not_scanned';
334 push @items_with_problems, { %$should_be_scanned };
339 for my $item ( @items_with_problems ) {
340 my $biblio = C4
::Biblio
::GetBiblioData
($item->{biblionumber
});
341 $item->{title
} = $biblio->{title
};
342 $item->{author
} = $biblio->{author
};
345 # If a barcode file is given, we want to show problems, else all items
347 @results = $uploadbarcodes
348 ?
@items_with_problems
354 moddatecount
=> $moddatecount,
359 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
360 eval {use Text
::CSV
};
361 my $csv = Text
::CSV
->new or
362 die Text
::CSV
->error_diag ();
363 binmode STDOUT
, ":encoding(UTF-8)";
364 print $input->header(
366 -attachment
=> 'inventory.csv',
369 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
370 foreach my $key ( keys %$columns_def_hashref ) {
372 $key =~ s/[^\.]*\.//;
373 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
374 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
378 for my $key (qw
/ biblioitems
.title biblio
.author
379 items
.barcode items
.itemnumber
380 items
.homebranch items
.location
381 items
.itemcallnumber items
.notforloan
382 items
.itemlost items
.damaged
383 items
.withdrawn items
.stocknumber
385 push @translated_keys, $columns_def_hashref->{$key};
387 push @translated_keys, 'problem' if $uploadbarcodes;
389 $csv->combine(@translated_keys);
390 print $csv->string, "\n";
392 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
393 for my $item ( @results ) {
395 for my $key (@keys) {
396 push @line, $item->{$key};
398 if ( defined $item->{problem
} ) {
399 if ( $item->{problem
} eq 'wrongplace' ) {
400 push @line, "wrong place";
401 } elsif ( $item->{problem
} eq 'missingitem' ) {
402 push @line, "missing item";
403 } elsif ( $item->{problem
} eq 'changestatus' ) {
404 push @line, "change item status";
405 } elsif ($item->{problem
} eq 'not_scanned' ) {
406 push @line, "item not scanned";
409 $csv->combine(@line);
410 print $csv->string, "\n";
412 # Adding not found barcodes
413 foreach my $error (@errorloop) {
415 if ($error->{'ERR_BARCODE'}) {
416 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
417 push @line, "barcode not found";
418 $csv->combine(@line);
419 print $csv->string, "\n";
425 output_html_with_http_headers
$input, $cookie, $template->output;