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
( $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
($minlocation, $maxlocation, $location, $itemtype, $ignoreissued, $paramdatelastseen, $branchcode, $branch, 0, undef, $staton);
248 # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
249 ($wrongplacelist, $totalrecords) = GetItemsForInventory
($minlocation, $maxlocation, $location, undef, undef, undef, $branchcode, $branch, 0, undef, undef);
253 # If "compare barcodes list to results" has been checked, we want to alert for missing items
254 if ( $compareinv2barcd ) {
255 # set "missing" flags for all items with a datelastseen (dls) before the choosen datelastseen (cdls)
256 my $dls = output_pref
( { dt
=> dt_from_string
( $datelastseen ),
257 dateformat
=> 'iso' } );
258 foreach my $item ( @
$inventorylist ) {
259 my $cdls = output_pref
( { dt
=> dt_from_string
( $item->{datelastseen
} ),
260 dateformat
=> 'iso' } );
261 if ( $cdls lt $dls ) {
262 $item->{problem
} = 'missingitem';
263 # We have to push a copy of the item, not the reference
264 push @items_with_problems, { %$item };
271 # insert "wrongplace" to all scanned items that are not supposed to be in this range
272 # note this list is always displayed, whatever the librarian has choosen for comparison
273 my $moddatecount = 0;
274 foreach my $item ( @scanned_items ) {
276 # Saving notforloan code before it's replaced by it's authorised value for later comparison
277 $item->{notforloancode
} = $item->{notforloan
};
279 # Populating with authorised values
280 foreach my $field ( keys %$item ) {
281 # If the koha field is mapped to a marc field
282 my $fc = $item->{'frameworkcode'} || '';
283 my ($f, $sf) = GetMarcFromKohaField
("items.$field", $fc);
285 # We replace the code with it's description
286 my $authvals = C4
::Koha
::GetKohaAuthorisedValuesFromField
($f, $sf, $fc);
287 if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
288 $item->{$field} = $authvals->{$item->{$field}};
293 next if $item->{onloan
}; # skip checked out items
295 # If we have scanned items with a non-matching notforloan value
296 if (none
{ $item->{'notforloancode'} eq $_ } @notforloans) {
297 $item->{problem
} = 'changestatus';
298 push @items_with_problems, { %$item };
300 if (none
{ $item->{barcode
} eq $_->{barcode
} && !$_->{'onloan'} } @
$wrongplacelist) {
301 $item->{problem
} = 'wrongplace';
302 push @items_with_problems, { %$item };
305 # Modify date last seen for scanned items
306 ModDateLastSeen
($item->{'itemnumber'});
310 if ( $compareinv2barcd ) {
311 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
312 for my $should_be_scanned ( @
$inventorylist ) {
313 my $barcode = $should_be_scanned->{barcode
};
314 unless ( grep /^$barcode$/, @scanned_barcodes ) {
315 $should_be_scanned->{problem
} = 'not_scanned';
316 push @items_with_problems, { %$should_be_scanned };
321 for my $item ( @items_with_problems ) {
322 my $biblio = C4
::Biblio
::GetBiblioData
($item->{biblionumber
});
323 $item->{title
} = $biblio->{title
};
324 $item->{author
} = $biblio->{author
};
327 # If a barcode file is given, we want to show problems, else all items
329 @results = $uploadbarcodes
330 ?
@items_with_problems
336 moddatecount
=> $moddatecount,
341 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
342 eval {use Text
::CSV
};
343 my $csv = Text
::CSV
->new or
344 die Text
::CSV
->error_diag ();
345 binmode STDOUT
, ":encoding(UTF-8)";
346 print $input->header(
348 -attachment
=> 'inventory.csv',
351 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
352 foreach my $key ( keys %$columns_def_hashref ) {
354 $key =~ s/[^\.]*\.//;
355 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
356 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
360 for my $key (qw
/ biblioitems
.title biblio
.author
361 items
.barcode items
.itemnumber
362 items
.homebranch items
.location
363 items
.itemcallnumber items
.notforloan
364 items
.itemlost items
.damaged
365 items
.withdrawn items
.stocknumber
367 push @translated_keys, $columns_def_hashref->{$key};
369 push @translated_keys, 'problem' if $uploadbarcodes;
371 $csv->combine(@translated_keys);
372 print $csv->string, "\n";
374 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
375 for my $item ( @results ) {
377 for my $key (@keys) {
378 push @line, $item->{$key};
380 if ( defined $item->{problem
} ) {
381 if ( $item->{problem
} eq 'wrongplace' ) {
382 push @line, "wrong place";
383 } elsif ( $item->{problem
} eq 'missingitem' ) {
384 push @line, "missing item";
385 } elsif ( $item->{problem
} eq 'changestatus' ) {
386 push @line, "change item status";
387 } elsif ($item->{problem
} eq 'not_scanned' ) {
388 push @line, "item not scanned";
391 $csv->combine(@line);
392 print $csv->string, "\n";
394 # Adding not found barcodes
395 foreach my $error (@errorloop) {
397 if ($error->{'ERR_BARCODE'}) {
398 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
399 push @line, "barcode not found";
400 $csv->combine(@line);
401 print $csv->string, "\n";
407 output_html_with_http_headers
$input, $cookie, $template->output;