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;
90 for my $statfield (qw
/items.notforloan items.itemlost items.withdrawn items.damaged/){
92 $hash->{fieldname
} = $statfield;
93 my $mss = Koha
::MarcSubfieldStructures
->search({ frameworkcode
=> '', kohafield
=> $statfield, authorised_value
=> { not => undef } });
94 $hash->{authcode
} = $mss->count ?
$mss->next->authorised_value : undef;
95 if ($hash->{authcode
}){
96 my $arr = GetAuthorisedValues
($hash->{authcode
});
97 if ( $statfield eq 'items.notforloan') {
98 # Add notforloan == 0 to the list of possible notforloan statuses
99 # The lib value is replaced in the template
100 push @
$arr, { authorised_value
=> 0, id
=> 'stat0' , lib
=> 'ignore' };
101 @notforloans = map { $_->{'authorised_value'} } @
$arr;
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
};
121 authorised_values
=> \
@authorised_value_list,
122 today
=> dt_from_string
,
123 minlocation
=> $minlocation,
124 maxlocation
=> $maxlocation,
125 location
=> $location,
126 ignoreissued
=> $ignoreissued,
127 branchcode
=> $branchcode,
129 datelastseen
=> $datelastseen,
130 compareinv2barcd
=> $compareinv2barcd,
135 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
136 my $dbh = C4
::Context
->dbh;
137 my $date = dt_from_string
( scalar $input->param('setdate') );
138 $date = output_pref
( { dt
=> $date, dateformat
=> 'iso' } );
140 my $strsth = "select * from issues, items where items.itemnumber=issues.itemnumber and items.barcode =?";
141 my $qonloan = $dbh->prepare($strsth);
142 $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
143 my $qwithdrawn = $dbh->prepare($strsth);
148 my @uploadedbarcodes;
150 my $sth = $dbh->column_info(undef,undef,"items","barcode");
151 my $barcode_def = $sth->fetchall_hashref('COLUMN_NAME');
152 my $barcode_size = $barcode_def->{barcode
}->{COLUMN_SIZE
};
156 binmode($uploadbarcodes, ":encoding(UTF-8)");
157 while (my $barcode=<$uploadbarcodes>) {
158 $barcode =~ s/\r/\n/g;
159 $barcode =~ s/\n\n/\n/g;
160 my @data = split(/\n/,$barcode);
161 push @uploadedbarcodes, @data;
163 for my $barcode (@uploadedbarcodes) {
164 next unless $barcode;
166 if (length($barcode)>$barcode_size) {
169 my $check_barcode = $barcode;
170 $check_barcode =~ s/\p{Print}//g;
171 if (length($check_barcode)>0) { # Only printable unicode characters allowed.
174 next if length($barcode)>$barcode_size;
175 next if ( length($check_barcode)>0 );
176 push @barcodes,$barcode;
178 $template->param( LinesRead
=> $lines_read );
180 push @errorloop, {'barcode'=>'No valid barcodes!'};
181 $op=''; # force the initial inventory screen again.
184 $template->param( err_length
=> $err_length,
185 err_data
=> $err_data );
187 foreach my $barcode (@barcodes) {
188 if ( $qwithdrawn->execute($barcode) && $qwithdrawn->rows ) {
189 push @errorloop, { 'barcode' => $barcode, 'ERR_WTHDRAWN' => 1 };
191 my $item = GetItem
( '', $barcode );
192 if ( defined $item && $item->{'itemnumber'} ) {
193 ModItem
( { datelastseen
=> $date }, undef, $item->{'itemnumber'} );
194 push @scanned_items, $item;
196 unless ( $dont_checkin ) {
197 $qonloan->execute($barcode);
199 my $data = $qonloan->fetchrow_hashref;
200 my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn
($barcode, $data->{homebranch
});
202 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
204 push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
209 push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
215 $template->param( date
=> $date, Number
=> $count );
216 $template->param( errorloop
=> \
@errorloop ) if (@errorloop);
219 # now build the result list: inventoried items if requested, and mis-placed items -always-
222 my @items_with_problems;
223 if ( $markseen or $op ) {
224 # retrieve all items in this range.
227 # We use datelastseen only when comparing the results to the barcode file.
228 my $paramdatelastseen = ($compareinv2barcd) ?
$datelastseen : '';
229 ($inventorylist, $totalrecords) = GetItemsForInventory
( {
230 minlocation
=> $minlocation,
231 maxlocation
=> $maxlocation,
232 location
=> $location,
233 itemtype
=> $itemtype,
234 ignoreissued
=> $ignoreissued,
235 datelastseen
=> $paramdatelastseen,
236 branchcode
=> $branchcode,
240 statushash
=> $staton,
243 # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
244 ($wrongplacelist, $totalrecords) = GetItemsForInventory
( {
245 minlocation
=> $minlocation,
246 maxlocation
=> $maxlocation,
247 location
=> $location,
249 ignoreissued
=> undef,
250 datelastseen
=> undef,
251 branchcode
=> $branchcode,
260 # If "compare barcodes list to results" has been checked, we want to alert for missing items
261 if ( $compareinv2barcd ) {
262 # set "missing" flags for all items with a datelastseen (dls) before the chosen datelastseen (cdls)
263 my $dls = output_pref
( { dt
=> dt_from_string
( $datelastseen ),
264 dateformat
=> 'iso' } );
265 foreach my $item ( @
$inventorylist ) {
266 my $cdls = output_pref
( { dt
=> dt_from_string
( $item->{datelastseen
} ),
267 dateformat
=> 'iso' } );
268 if ( $cdls lt $dls ) {
269 $item->{problem
} = 'missingitem';
270 # We have to push a copy of the item, not the reference
271 push @items_with_problems, { %$item };
278 # insert "wrongplace" to all scanned items that are not supposed to be in this range
279 # note this list is always displayed, whatever the librarian has chosen for comparison
280 my $moddatecount = 0;
281 foreach my $item ( @scanned_items ) {
283 # Saving notforloan code before it's replaced by it's authorised value for later comparison
284 $item->{notforloancode
} = $item->{notforloan
};
286 # Populating with authorised values
287 foreach my $field ( keys %$item ) {
288 # If the koha field is mapped to a marc field
289 my $fc = $item->{'frameworkcode'} || '';
290 my ($f, $sf) = GetMarcFromKohaField
("items.$field", $fc);
292 # We replace the code with it's description
293 my $av = Koha
::AuthorisedValues
->search_by_marc_field({ frameworkcode
=> $fc, tagfield
=> $f, tagsubfield
=> $sf, });
294 $av = $av->count ?
$av->unblessed : [];
295 my $authvals = { map { ( $_->{authorised_value
} => $_->{lib
} ) } @
$av };
296 if ($authvals and defined $item->{$field} and defined $authvals->{$item->{$field}}) {
297 $item->{$field} = $authvals->{$item->{$field}};
302 next if $item->{onloan
}; # skip checked out items
304 # If we have scanned items with a non-matching notforloan value
305 if (none
{ $item->{'notforloancode'} eq $_ } @notforloans) {
306 $item->{problem
} = 'changestatus';
307 push @items_with_problems, { %$item };
309 if (none
{ $item->{barcode
} eq $_->{barcode
} && !$_->{'onloan'} } @
$wrongplacelist) {
310 $item->{problem
} = 'wrongplace';
311 push @items_with_problems, { %$item };
314 # Modify date last seen for scanned items
315 ModDateLastSeen
($item->{'itemnumber'});
319 if ( $compareinv2barcd ) {
320 my @scanned_barcodes = map {$_->{barcode
}} @scanned_items;
321 for my $should_be_scanned ( @
$inventorylist ) {
322 my $barcode = $should_be_scanned->{barcode
};
323 unless ( grep /^$barcode$/, @scanned_barcodes ) {
324 $should_be_scanned->{problem
} = 'not_scanned';
325 push @items_with_problems, { %$should_be_scanned };
330 for my $item ( @items_with_problems ) {
331 my $biblio = C4
::Biblio
::GetBiblioData
($item->{biblionumber
});
332 $item->{title
} = $biblio->{title
};
333 $item->{author
} = $biblio->{author
};
336 # If a barcode file is given, we want to show problems, else all items
338 @results = $uploadbarcodes
339 ?
@items_with_problems
345 moddatecount
=> $moddatecount,
350 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
351 eval {use Text
::CSV
};
352 my $csv = Text
::CSV
->new or
353 die Text
::CSV
->error_diag ();
354 binmode STDOUT
, ":encoding(UTF-8)";
355 print $input->header(
357 -attachment
=> 'inventory.csv',
360 my $columns_def_hashref = C4
::Reports
::Guided
::_get_column_defs
($input);
361 foreach my $key ( keys %$columns_def_hashref ) {
363 $key =~ s/[^\.]*\.//;
364 $columns_def_hashref->{$initkey}=NormalizeString
($columns_def_hashref->{$initkey} // '');
365 $columns_def_hashref->{$key} = $columns_def_hashref->{$initkey};
369 for my $key (qw
/ biblioitems
.title biblio
.author
370 items
.barcode items
.itemnumber
371 items
.homebranch items
.location
372 items
.itemcallnumber items
.notforloan
373 items
.itemlost items
.damaged
374 items
.withdrawn items
.stocknumber
376 push @translated_keys, $columns_def_hashref->{$key};
378 push @translated_keys, 'problem' if $uploadbarcodes;
380 $csv->combine(@translated_keys);
381 print $csv->string, "\n";
383 my @keys = qw
/ title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
384 for my $item ( @results ) {
386 for my $key (@keys) {
387 push @line, $item->{$key};
389 if ( defined $item->{problem
} ) {
390 if ( $item->{problem
} eq 'wrongplace' ) {
391 push @line, "wrong place";
392 } elsif ( $item->{problem
} eq 'missingitem' ) {
393 push @line, "missing item";
394 } elsif ( $item->{problem
} eq 'changestatus' ) {
395 push @line, "change item status";
396 } elsif ($item->{problem
} eq 'not_scanned' ) {
397 push @line, "item not scanned";
400 $csv->combine(@line);
401 print $csv->string, "\n";
403 # Adding not found barcodes
404 foreach my $error (@errorloop) {
406 if ($error->{'ERR_BARCODE'}) {
407 push @line, map { $_ eq 'barcode' ?
$error->{'barcode'} : ''} @keys;
408 push @line, "barcode not found";
409 $csv->combine(@line);
410 print $csv->string, "\n";
416 output_html_with_http_headers
$input, $cookie, $template->output;