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');
35 use C4
::Branch
; # GetBranches
37 use C4
::Reports
::Guided
; #_get_column_defs
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' },
68 my $branches = GetBranches
();
70 for my $branch_hash (keys %$branches) {
71 push @branch_loop, {value
=> "$branch_hash",
72 branchname
=> $branches->{$branch_hash}->{'branchname'},
73 selected
=> ($branch_hash eq $branchcode?
1:0)};
76 @branch_loop = sort {$a->{branchname
} cmp $b->{branchname
}} @branch_loop;
77 my @authorised_value_list;
78 my $authorisedvalue_categories = '';
80 my $frameworks = getframeworks
();
81 $frameworks->{''} = {frameworkcode
=> ''}; # Add the default framework
83 for my $fwk (keys %$frameworks){
84 my $fwkcode = $frameworks->{$fwk}->{'frameworkcode'};
85 my $authcode = GetAuthValCode
('items.location', $fwkcode);
86 if ($authcode && $authorisedvalue_categories!~/\b$authcode\W/){
87 $authorisedvalue_categories.="$authcode ";
88 my $data=GetAuthorisedValues
($authcode);
89 foreach my $value (@
$data){
90 $value->{selected
}=1 if ($value->{authorised_value
} eq ($location));
92 push @authorised_value_list,@
$data;
97 for my $statfield (qw
/items.notforloan items.itemlost items.withdrawn items.damaged/){
99 $hash->{fieldname
} = $statfield;
100 $hash->{authcode
} = GetAuthValCode
($statfield);
101 if ($hash->{authcode
}){
102 my $arr = GetAuthorisedValues
($hash->{authcode
});
103 $hash->{values} = $arr;
104 push @
$statuses, $hash;
109 $template->param( statuses
=> $statuses );
110 my $staton = {}; #authorized values that are ticked
111 for my $authvfield (@
$statuses) {
112 $staton->{$authvfield->{fieldname
}} = [];
113 for my $authval (@
{$authvfield->{values}}){
114 if ( defined $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) && $input->param('status-' . $authvfield->{fieldname
} . '-' . $authval->{authorised_value
}) eq 'on' ){
115 push @
{$staton->{$authvfield->{fieldname
}}}, $authval->{authorised_value
};
122 for my $authvfield (@
$statuses) {
123 if ( scalar @
{$staton->{$authvfield->{fieldname
}}} > 0 ){
124 my $joinedvals = join ',', @
{$staton->{$authvfield->{fieldname
}}};
125 $statussth .= "$authvfield->{fieldname} in ($joinedvals) and ";
126 $notforloanlist = $joinedvals if ($authvfield->{fieldname
} eq "items.notforloan");
129 $statussth =~ s
, and $,,g
;
131 branchloop
=> \
@branch_loop,
132 authorised_values
=> \
@authorised_value_list,
133 today
=> dt_from_string
,
134 minlocation
=> $minlocation,
135 maxlocation
=> $maxlocation,
136 location
=> $location,
137 ignoreissued
=> $ignoreissued,
138 branchcode
=> $branchcode,
140 datelastseen
=> $datelastseen,
141 compareinv2barcd
=> $compareinv2barcd,
142 notforloanlist
=> $notforloanlist
146 if (defined $notforloanlist) {
147 @notforloans = split(/,/, $notforloanlist);
152 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
153 my $dbh = C4
::Context
->dbh;
154 my $date = dt_from_string
( scalar $input->param('setdate') );
155 $date = output_pref
( { dt
=> $date, dateformat
=> 'iso' } );
157 my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
158 my $qonloan = $dbh->prepare($strsth);
159 $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
160 my $qwithdrawn = $dbh->prepare($strsth);
165 my @uploadedbarcodes;
167 my $sth = $dbh->column_info(undef,undef,"items","barcode");
168 my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME');
169 my $barcode_size = $barcode_def->{barcode
}->{COLUMN_SIZE
};
173 binmode($uploadbarcodes, ":encoding(UTF-8)");
174 while (my $barcode=<$uploadbarcodes>) {
175 $barcode =~ s/\r/\n/g;
176 $barcode =~ s/\n\n/\n/g;
177 my @data = split(/\n/,$barcode);
178 push @uploadedbarcodes, @data;
180 for my $barcode (@uploadedbarcodes) {
181 next unless $barcode;
183 if (length($barcode)>$barcode_size) {
186 my $check_barcode = $barcode;
187 $check_barcode =~ s/\p{Print}//g;
188 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
191 next if length($barcode)>$barcode_size;
192 next if ( length($check_barcode)>0 );
193 push @barcodes,$barcode;
195 $template->param( LinesRead
=> $lines_read );
197 push @errorloop, {'barcode'=>'No valid barcodes!'};
198 $op=''; # force the initial inventory screen again.
201 $template->param( err_length
=> $err_length,
202 err_data
=> $err_data );
204 foreach my $barcode (@barcodes) {
205 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
206 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
208 my $item = GetItem
( '', $barcode );
209 if ( defined $item && $item->{'itemnumber'} ) {
210 ModItem
( { datelastseen
=> $date }, undef, $item->{'itemnumber'} );
211 push @scanned_items, $item;
213 unless ( $dont_checkin ) {
214 $qonloan->execute($barcode);
216 my $data = $qonloan->fetchrow_hashref;
217 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
219 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
221 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
226 push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
232 $template->param( date
=> $date, Number
=> $count );
233 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
236 # now build the result list: inventoried items if requested, and mis-placed items -always-
239 my @items_with_problems;
240 if ( $markseen or $op ) {
241 # retrieve all items in this range.
244 # We use datelastseen only when comparing the results to the barcode file.
245 my $paramdatelastseen = ($compareinv2barcd) ?
$datelastseen : '';
246 ($inventorylist, $totalrecords) = GetItemsForInventory
( {
247 minlocation
=> $minlocation,
248 maxlocation
=> $maxlocation,
249 location
=> $location,
250 itemtype
=> $itemtype,
251 ignoreissued
=> $ignoreissued,
252 datelastseen
=> $paramdatelastseen,
253 branchcode
=> $branchcode,
257 statushash
=> $staton,
258 interface
=> 'staff',
261 # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
262 ($wrongplacelist, $totalrecords) = GetItemsForInventory
( {
263 minlocation
=> $minlocation,
264 maxlocation
=> $maxlocation,
265 location
=> $location,
267 ignoreissued
=> undef,
268 datelastseen
=> undef,
269 branchcode
=> $branchcode,
274 interface
=> 'staff',
279 # If "compare barcodes list to results" has been checked, we want to alert for missing items
280 if ( $compareinv2barcd ) {
281 # set "missing" flags for all items with a datelastseen (dls) before the chosen datelastseen (cdls)
282 my $dls = output_pref
( { dt
=> dt_from_string
( $datelastseen ),
283 dateformat
=> 'iso' } );
284 foreach my $item ( @
$inventorylist ) {
285 my $cdls = output_pref
( { dt
=> dt_from_string
( $item->{datelastseen
} ),
286 dateformat
=> 'iso' } );
287 if ( $cdls lt $dls ) {
288 $item->{problem
} = 'missingitem';
289 # We have to push a copy of the item, not the reference
290 push @items_with_problems, { %$item };
297 # insert "wrongplace" to all scanned items that are not supposed to be in this range
298 # note this list is always displayed, whatever the librarian has chosen for comparison
299 my $moddatecount = 0;
300 foreach my $item ( @scanned_items ) {
302 # Saving notforloan code before it's replaced by it's authorised value for later comparison
303 $item->{notforloancode
} = $item->{notforloan
};
305 # Populating with authorised values
306 foreach my $field ( keys %$item ) {
307 # If the koha field is mapped to a marc field
308 my $fc = $item->{'frameworkcode'} || '';
309 my ($f, $sf) = GetMarcFromKohaField
("items.$field", $fc);
311 # We replace the code with it's description
312 my $authvals = C4
::Koha
::GetKohaAuthorisedValuesFromField
($f, $sf, $fc);
313 if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
314 $item->{$field} = $authvals->{$item->{$field}};
319 next if $item->{onloan
}; # skip checked out items
321 # If we have scanned items with a non-matching notforloan value
322 if (none
{ $item->{'notforloancode'} eq $_ } @notforloans) {
323 $item->{problem
} = 'changestatus';
324 push @items_with_problems, { %$item };
326 if (none
{ $item->{barcode
} eq $_->{barcode
} && !$_->{'onloan'} } @
$wrongplacelist) {
327 $item->{problem
} = 'wrongplace';
328 push @items_with_problems, { %$item };
331 # Modify date last seen for scanned items
332 ModDateLastSeen
($item->{'itemnumber'});
336 if ( $compareinv2barcd ) {
337 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
338 for my $should_be_scanned ( @
$inventorylist ) {
339 my $barcode = $should_be_scanned->{barcode
};
340 unless ( grep /^$barcode$/, @scanned_barcodes ) {
341 $should_be_scanned->{problem
} = 'not_scanned';
342 push @items_with_problems, { %$should_be_scanned };
347 for my $item ( @items_with_problems ) {
348 my $biblio = C4
::Biblio
::GetBiblioData
($item->{biblionumber
});
349 $item->{title
} = $biblio->{title
};
350 $item->{author
} = $biblio->{author
};
353 # If a barcode file is given, we want to show problems, else all items
355 @results = $uploadbarcodes
356 ?
@items_with_problems
362 moddatecount
=> $moddatecount,
367 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
368 eval {use Text
::CSV
};
369 my $csv = Text
::CSV
->new or
370 die Text
::CSV
->error_diag ();
371 binmode STDOUT
, ":encoding(UTF-8)";
372 print $input->header(
374 -attachment
=> 'inventory.csv',
377 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
378 foreach my $key ( keys %$columns_def_hashref ) {
380 $key =~ s/[^\.]*\.//;
381 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
382 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
386 for my $key (qw
/ biblioitems
.title biblio
.author
387 items
.barcode items
.itemnumber
388 items
.homebranch items
.location
389 items
.itemcallnumber items
.notforloan
390 items
.itemlost items
.damaged
391 items
.withdrawn items
.stocknumber
393 push @translated_keys, $columns_def_hashref->{$key};
395 push @translated_keys, 'problem' if $uploadbarcodes;
397 $csv->combine(@translated_keys);
398 print $csv->string, "\n";
400 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
401 for my $item ( @results ) {
403 for my $key (@keys) {
404 push @line, $item->{$key};
406 if ( defined $item->{problem
} ) {
407 if ( $item->{problem
} eq 'wrongplace' ) {
408 push @line, "wrong place";
409 } elsif ( $item->{problem
} eq 'missingitem' ) {
410 push @line, "missing item";
411 } elsif ( $item->{problem
} eq 'changestatus' ) {
412 push @line, "change item status";
413 } elsif ($item->{problem
} eq 'not_scanned' ) {
414 push @line, "item not scanned";
417 $csv->combine(@line);
418 print $csv->string, "\n";
420 # Adding not found barcodes
421 foreach my $error (@errorloop) {
423 if ($error->{'ERR_BARCODE'}) {
424 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
425 push @line, "barcode not found";
426 $csv->combine(@line);
427 print $csv->string, "\n";
433 output_html_with_http_headers
$input, $cookie, $template->output;