View | Details | Raw Unified | Return to bug 14399
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt (-13 / +18 lines)
Lines 125-131 $(document).ready(function(){ Link Here
125
            <legend>Use a barcode file</legend>
125
            <legend>Use a barcode file</legend>
126
     <ol>
126
     <ol>
127
            <li><label for="uploadbarcodes">Barcode file: </label> <input type="file" id="uploadbarcodes" name="uploadbarcodes" /></li>
127
            <li><label for="uploadbarcodes">Barcode file: </label> <input type="file" id="uploadbarcodes" name="uploadbarcodes" /></li>
128
            <li><label for="setdate">Set inventory date to:</label> <input type="text" id="setdate" name="setdate" value="[% today | $KohaDates %]" class="datepickerfrom" />
128
            <li><label for="setdate">Set inventory date to:</label> <input type="text" id="setdate" name="setdate" value="[% today | $KohaDates %]" class="datepicker" />
129
            </li>
129
            </li>
130
          </ol>
130
          </ol>
131
        </fieldset>
131
        </fieldset>
Lines 203-210 $(document).ready(function(){ Link Here
203
          <ol>
203
          <ol>
204
        [% END %]
204
        [% END %]
205
205
206
        <li><label for="datelastseen">Inventory date:</label>
206
        <li><label for="datelastseen">Last inventory date:</label>
207
            <input type="text" id="datelastseen" name="datelastseen" value="[% datelastseen | $KohaDates %]" class="datepickerfrom" />
207
            <input type="text" id="datelastseen" name="datelastseen" value="[% datelastseen | $KohaDates %]" class="datepicker" />
208
            (Skip records marked as seen on or after this date.)
208
        </li>
209
        </li>
209
        <li><label for="ignoreissued">Skip items on loan: </label>
210
        <li><label for="ignoreissued">Skip items on loan: </label>
210
            [% IF (ignoreissued) %]
211
            [% IF (ignoreissued) %]
Lines 235-241 $(document).ready(function(){ Link Here
235
    [% END %]
236
    [% END %]
236
    [% IF (op) %]
237
    [% IF (op) %]
237
    <form method="post" action="/cgi-bin/koha/tools/inventory.pl" class="checkboxed">
238
    <form method="post" action="/cgi-bin/koha/tools/inventory.pl" class="checkboxed">
238
    <input type="hidden" name="markseen" value="1" />
239
    <input type="hidden" name="minlocation" value="[% minlocation %]" />
239
    <input type="hidden" name="minlocation" value="[% minlocation %]" />
240
    <input type="hidden" name="maxlocation" value="[% maxlocation %]" />
240
    <input type="hidden" name="maxlocation" value="[% maxlocation %]" />
241
    <input type="hidden" name="location" value="[% location %]" />
241
    <input type="hidden" name="location" value="[% location %]" />
Lines 277-283 $(document).ready(function(){ Link Here
277
                [% result.location | html %] [% IF ( result.itemcallnumber ) %][[% result.itemcallnumber | html %]][% ELSE %][% END %]
277
                [% result.location | html %] [% IF ( result.itemcallnumber ) %][[% result.itemcallnumber | html %]][% ELSE %][% END %]
278
            </td>
278
            </td>
279
            <td>
279
            <td>
280
            <p><a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% result.biblionumber %]" class="openWin">[% result.title | html %]</a></p><p>[% result.author | html %]</p>
280
                <a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% result.biblionumber %]" class="openWin">[% result.title | html %]</a>
281
                <p>[% result.author | html %]</p>
281
            </td>
282
            </td>
282
            <td>
283
            <td>
283
            [% result.notforloan | html %]
284
            [% result.notforloan | html %]
Lines 295-308 $(document).ready(function(){ Link Here
295
            [% result.datelastseen | $KohaDates | html %]
296
            [% result.datelastseen | $KohaDates | html %]
296
            </td>
297
            </td>
297
            <td>
298
            <td>
298
            [% IF result.problem == 'wrongplace' %]
299
            [% FOREACH problem IN result.problems %]
299
                <p>Item should not have been scanned</p>
300
                [% IF problem.key == 'wrongplace' %]
300
            [% ELSIF result.problem == 'missingitem' %]
301
                    Found in wrong place<br/>
301
                <p>Item missing</p>
302
                [% ELSIF problem.key == 'changestatus' %]
302
            [% ELSIF result.problem == 'changestatus' %]
303
                    Unknown not-for-loan status<br/>
303
                <p>Unknown not-for-loan status</p>
304
                [% ELSIF problem.key == 'not_scanned' %]
304
            [% ELSIF result.problem == 'not_scanned' %]
305
                    Missing (not scanned)<br/>
305
                <p>Item should have been scanned</p>
306
                [% ELSIF problem.key == 'checkedout' %]
307
                    Still checked out<br/>
308
                [% ELSIF problem.key == 'no_barcode' %]
309
                    No barcode<br/>
310
                [% END %]
306
            [% END %]
311
            [% END %]
307
            </td>
312
            </td>
308
        </tr>
313
        </tr>
(-)a/tools/inventory.pl (-108 / +94 lines)
Lines 1-7 Link Here
1
#!/usr/bin/perl
1
#!/usr/bin/perl
2
2
3
# Copyright 2000-2009 Biblibre S.A
3
# Copyright 2000-2009 Biblibre S.A
4
#                                         John Soros <john.soros@biblibre.com>
4
# John Soros <john.soros@biblibre.com>
5
#
5
#
6
# This file is part of Koha.
6
# This file is part of Koha.
7
#
7
#
Lines 18-25 Link Here
18
# You should have received a copy of the GNU General Public License
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>.
19
# along with Koha; if not, see <http://www.gnu.org/licenses>.
20
20
21
use strict;
21
use Modern::Perl;
22
use warnings;
23
22
24
#need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth)
23
#need to open cgi and get the fh before anything else opens a new cgi context (see C4::Auth)
25
use CGI qw ( -utf8 );
24
use CGI qw ( -utf8 );
Lines 39-53 use C4::Charset; Link Here
39
use Koha::DateUtils;
38
use Koha::DateUtils;
40
use List::MoreUtils qw( none );
39
use List::MoreUtils qw( none );
41
40
42
43
my $minlocation=$input->param('minlocation') || '';
41
my $minlocation=$input->param('minlocation') || '';
44
my $maxlocation=$input->param('maxlocation');
42
my $maxlocation=$input->param('maxlocation');
45
$maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
43
$maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
46
my $location=$input->param('location') || '';
44
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');
45
my $ignoreissued=$input->param('ignoreissued');
49
my $datelastseen = $input->param('datelastseen');
46
my $datelastseen = $input->param('datelastseen'); # last inventory date
50
my $markseen = $input->param('markseen');
51
my $branchcode = $input->param('branchcode') || '';
47
my $branchcode = $input->param('branchcode') || '';
52
my $branch     = $input->param('branch');
48
my $branch     = $input->param('branch');
53
my $op         = $input->param('op');
49
my $op         = $input->param('op');
Lines 115-123 for my $statfield (qw/items.notforloan items.itemlost items.withdrawn items.dama Link Here
115
    }
111
    }
116
}
112
}
117
113
118
119
$template->param( statuses => $statuses );
114
$template->param( statuses => $statuses );
120
my $staton = {};                                #authorized values that are ticked
115
my $staton = {}; #authorized values that are ticked
121
for my $authvfield (@$statuses) {
116
for my $authvfield (@$statuses) {
122
    $staton->{$authvfield->{fieldname}} = [];
117
    $staton->{$authvfield->{fieldname}} = [];
123
    for my $authval (@{$authvfield->{values}}){
118
    for my $authval (@{$authvfield->{values}}){
Lines 141-148 $template->param( Link Here
141
    compareinv2barcd         => $compareinv2barcd,
136
    compareinv2barcd         => $compareinv2barcd,
142
);
137
);
143
138
139
# Walk through uploaded barcodes, report errors, mark as seen, check in
140
my $results = {};
144
my @scanned_items;
141
my @scanned_items;
145
my @errorloop;
142
my @errorloop;
143
my $moddatecount = 0;
146
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
144
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
147
    my $dbh = C4::Context->dbh;
145
    my $dbh = C4::Context->dbh;
148
    my $date = dt_from_string( scalar $input->param('setdate') );
146
    my $date = dt_from_string( scalar $input->param('setdate') );
Lines 153-160 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { Link Here
153
    $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
151
    $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
154
    my $qwithdrawn = $dbh->prepare($strsth);
152
    my $qwithdrawn = $dbh->prepare($strsth);
155
153
156
    my $count = 0;
157
158
    my @barcodes;
154
    my @barcodes;
159
    my @uploadedbarcodes;
155
    my @uploadedbarcodes;
160
156
Lines 201-303 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { Link Here
201
        } else {
197
        } else {
202
            my $item = GetItem( '', $barcode );
198
            my $item = GetItem( '', $barcode );
203
            if ( defined $item && $item->{'itemnumber'} ) {
199
            if ( defined $item && $item->{'itemnumber'} ) {
204
                ModItem( { datelastseen => $date }, undef, $item->{'itemnumber'} );
200
                # Modify date last seen for scanned items, remove lost status
205
                push @scanned_items, $item;
201
                ModItem( { itemlost => 0, datelastseen => $date }, undef, $item->{'itemnumber'} );
206
                $count++;
202
                $moddatecount++;
203
                # update item hash accordingly
204
                $item->{itemlost} = 0;
205
                $item->{datelastseen} = $date;
207
                unless ( $dont_checkin ) {
206
                unless ( $dont_checkin ) {
208
                    $qonloan->execute($barcode);
207
                    $qonloan->execute($barcode);
209
                    if ($qonloan->rows){
208
                    if ($qonloan->rows){
210
                        my $data = $qonloan->fetchrow_hashref;
209
                        my $data = $qonloan->fetchrow_hashref;
211
                        my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch});
210
                        my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch});
212
                        if ($doreturn){
211
                        if( $doreturn ) {
213
                            push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
212
                            $item->{onloan} = undef;
213
                            $item->{datelastseen} = dt_from_string;
214
                        } else {
214
                        } else {
215
                            push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
215
                            push @errorloop, { barcode => $barcode, ERR_ONLOAN_NOT_RET => 1 };
216
                        }
216
                        }
217
                    }
217
                    }
218
                }
218
                }
219
                push @scanned_items, $item;
219
            } else {
220
            } else {
220
                push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
221
                push @errorloop, { barcode => $barcode, ERR_BARCODE => 1 };
221
            }
222
            }
222
        }
223
        }
223
224
    }
224
    }
225
225
    $template->param( date => $date );
226
    $template->param( date => $date, Number => $count );
227
    $template->param( errorloop => \@errorloop ) if (@errorloop);
226
    $template->param( errorloop => \@errorloop ) if (@errorloop);
228
}
227
}
229
228
230
# now build the result list: inventoried items if requested, and mis-placed items -always-
229
# Build inventorylist: used as result list when you do not pass barcodes
231
my $inventorylist;
230
# This list is also used when you want to compare with barcodes
232
my $wrongplacelist;
231
my ( $inventorylist, $rightplacelist );
233
my @items_with_problems;
232
if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
234
if ( $markseen or $op ) {
233
    ( $inventorylist ) = GetItemsForInventory({
235
    # retrieve all items in this range.
236
    my $totalrecords;
237
238
    # We use datelastseen only when comparing the results to the barcode file.
239
    my $paramdatelastseen = ($compareinv2barcd) ? $datelastseen : '';
240
    ($inventorylist, $totalrecords) = GetItemsForInventory( {
241
      minlocation  => $minlocation,
234
      minlocation  => $minlocation,
242
      maxlocation  => $maxlocation,
235
      maxlocation  => $maxlocation,
243
      location     => $location,
236
      location     => $location,
244
      itemtype     => $itemtype,
245
      ignoreissued => $ignoreissued,
237
      ignoreissued => $ignoreissued,
246
      datelastseen => $paramdatelastseen,
238
      datelastseen => $datelastseen,
247
      branchcode   => $branchcode,
239
      branchcode   => $branchcode,
248
      branch       => $branch,
240
      branch       => $branch,
249
      offset       => 0,
241
      offset       => 0,
250
      size         => undef,
251
      statushash   => $staton,
242
      statushash   => $staton,
252
      interface    => 'staff',
243
      interface    => 'staff',
253
    } );
244
    });
254
245
}
255
    # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
246
# Build rightplacelist used to check if a scanned item is in the right place.
256
    ($wrongplacelist, $totalrecords) = GetItemsForInventory( {
247
if( @scanned_items ) {
248
    ( $rightplacelist ) = GetItemsForInventory({
257
      minlocation  => $minlocation,
249
      minlocation  => $minlocation,
258
      maxlocation  => $maxlocation,
250
      maxlocation  => $maxlocation,
259
      location     => $location,
251
      location     => $location,
260
      itemtype     => undef,
261
      ignoreissued => undef,
252
      ignoreissued => undef,
262
      datelastseen => undef,
253
      datelastseen => undef,
263
      branchcode   => $branchcode,
254
      branchcode   => $branchcode,
264
      branch       => $branch,
255
      branch       => $branch,
265
      offset       => 0,
256
      offset       => 0,
266
      size         => undef,
267
      statushash   => undef,
257
      statushash   => undef,
268
      interface    => 'staff',
258
      interface    => 'staff',
269
    } );
259
    });
270
260
    # Convert the structure to a hash on barcode
261
    $rightplacelist = {
262
        map { $_->{barcode} ? ( $_->{barcode}, $_ ) : (); } @$rightplacelist
263
    };
271
}
264
}
272
265
273
# If "compare barcodes list to results" has been checked, we want to alert for missing items
266
# Report scanned items that are on the wrong place, or have a wrong notforloan
274
if ( $compareinv2barcd ) {
267
# status, or are still checked out.
275
    # set "missing" flags for all items with a datelastseen (dls) before the chosen datelastseen (cdls)
276
    my $dls = output_pref( { dt => dt_from_string( $datelastseen ),
277
                             dateformat => 'iso' } );
278
    foreach my $item ( @$inventorylist ) {
279
        my $cdls = output_pref( { dt => dt_from_string( $item->{datelastseen} ),
280
                                  dateformat => 'iso' } );
281
        if ( $cdls lt $dls ) {
282
            $item->{problem} = 'missingitem';
283
            # We have to push a copy of the item, not the reference
284
            push @items_with_problems, { %$item };
285
        }
286
    }
287
}
288
289
290
291
# insert "wrongplace" to all scanned items that are not supposed to be in this range
292
# note this list is always displayed, whatever the librarian has chosen for comparison
293
my $moddatecount = 0;
294
foreach my $item ( @scanned_items ) {
268
foreach my $item ( @scanned_items ) {
269
    $item->{notforloancode} = $item->{notforloan}; # save for later use
295
270
296
  # Saving notforloan code before it's replaced by it's authorised value for later comparison
271
    # Populating with authorised values
297
  $item->{notforloancode} = $item->{notforloan};
272
    foreach my $field ( keys %$item ) {
298
299
  # Populating with authorised values
300
  foreach my $field ( keys %$item ) {
301
        # If the koha field is mapped to a marc field
273
        # If the koha field is mapped to a marc field
302
        my $fc = $item->{'frameworkcode'} || '';
274
        my $fc = $item->{'frameworkcode'} || '';
303
        my ($f, $sf) = GetMarcFromKohaField("items.$field", $fc);
275
        my ($f, $sf) = GetMarcFromKohaField("items.$field", $fc);
Lines 310-363 foreach my $item ( @scanned_items ) { Link Here
310
        }
282
        }
311
    }
283
    }
312
284
313
    next if $item->{onloan}; # skip checked out items
314
315
    # If we have scanned items with a non-matching notforloan value
285
    # If we have scanned items with a non-matching notforloan value
316
    if (none { $item->{'notforloancode'} eq $_ } @notforloans) {
286
    if( none { $item->{'notforloancode'} eq $_ } @notforloans ) {
317
        $item->{problem} = 'changestatus';
287
        $item->{problems}->{changestatus} = 1;
318
        push @items_with_problems, { %$item };
288
        additemtoresults( $item, $results );
319
    }
320
    if (none { $item->{barcode} eq $_->{barcode} && !$_->{'onloan'} } @$wrongplacelist) {
321
        $item->{problem} = 'wrongplace';
322
        push @items_with_problems, { %$item };
323
    }
289
    }
324
290
325
    # Modify date last seen for scanned items
291
    # Report an item that is checked out (unusual!) or wrongly placed
326
    ModDateLastSeen($item->{'itemnumber'});
292
    if( $item->{onloan} ) {
327
    $moddatecount++;
293
        $item->{problems}->{checkedout} = 1;
294
        additemtoresults( $item, $results );
295
        next; # do not modify item
296
    } elsif( !exists $rightplacelist->{ $item->{barcode} } ) {
297
        $item->{problems}->{wrongplace} = 1;
298
        additemtoresults( $item, $results );
299
    }
328
}
300
}
329
301
302
# Compare barcodes with inventory list, report no_barcode and not_scanned.
303
# not_scanned can be interpreted as missing
330
if ( $compareinv2barcd ) {
304
if ( $compareinv2barcd ) {
331
    my @scanned_barcodes = map {$_->{barcode}} @scanned_items;
305
    my @scanned_barcodes = map {$_->{barcode}} @scanned_items;
332
    for my $should_be_scanned ( @$inventorylist ) {
306
    for my $item ( @$inventorylist ) {
333
        my $barcode = $should_be_scanned->{barcode};
307
        my $barcode = $item->{barcode};
334
        unless ( grep /^$barcode$/, @scanned_barcodes ) {
308
        if( !$barcode ) {
335
            $should_be_scanned->{problem} = 'not_scanned';
309
            $item->{problems}->{no_barcode} = 1;
336
            push @items_with_problems, { %$should_be_scanned };
310
        } elsif ( grep /^$barcode$/, @scanned_barcodes ) {
311
            next;
312
        } else {
313
            $item->{problems}->{not_scanned} = 1;
337
        }
314
        }
315
        additemtoresults( $item, $results );
338
    }
316
    }
339
}
317
}
340
318
341
for my $item ( @items_with_problems ) {
319
# Construct final results, add biblio information
320
my $loop = $uploadbarcodes
321
    ? [ map { $results->{$_} } keys %$results ]
322
    : $inventorylist // [];
323
for my $item ( @$loop ) {
342
    my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber});
324
    my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber});
343
    $item->{title} = $biblio->{title};
325
    $item->{title} = $biblio->{title};
344
    $item->{author} = $biblio->{author};
326
    $item->{author} = $biblio->{author};
345
}
327
}
346
328
347
# If a barcode file is given, we want to show problems, else all items
348
my @results;
349
@results = $uploadbarcodes
350
            ? @items_with_problems
351
            : $op
352
                ? @$inventorylist
353
                : ();
354
355
$template->param(
329
$template->param(
356
    moddatecount => $moddatecount,
330
    moddatecount => $moddatecount,
357
    loop       => \@results,
331
    loop         => $loop,
358
    op         => $op
332
    op           => $op,
359
);
333
);
360
334
335
# Export to csv
361
if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
336
if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
362
    eval {use Text::CSV};
337
    eval {use Text::CSV};
363
    my $csv = Text::CSV->new or
338
    my $csv = Text::CSV->new or
Lines 392-413 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ Link Here
392
    print $csv->string, "\n";
367
    print $csv->string, "\n";
393
368
394
    my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
369
    my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
395
    for my $item ( @results ) {
370
    for my $item ( @$loop ) {
396
        my @line;
371
        my @line;
397
        for my $key (@keys) {
372
        for my $key (@keys) {
398
            push @line, $item->{$key};
373
            push @line, $item->{$key};
399
        }
374
        }
400
        if ( defined $item->{problem} ) {
375
        my $errstr = '';
401
            if ( $item->{problem} eq 'wrongplace' ) {
376
        foreach my $key ( keys %{$item->{problems}} ) {
402
                push @line, "wrong place";
377
            if( $key eq 'wrongplace' ) {
403
            } elsif ( $item->{problem} eq 'missingitem' ) {
378
                $errstr .= "wrong place,";
404
                push @line, "missing item";
379
            } elsif( $key eq 'changestatus' ) {
405
            } elsif ( $item->{problem} eq 'changestatus' ) {
380
                $errstr .= "unknown notforloan status,";
406
                push @line, "change item status";
381
            } elsif( $key eq 'not_scanned' ) {
407
            } elsif ($item->{problem} eq 'not_scanned' ) {
382
                $errstr .= "missing,";
408
                push @line, "item not scanned";
383
            } elsif( $key eq 'no_barcode' ) {
384
                $errstr .= "no barcode,";
385
            } elsif( $key eq 'checkedout' ) {
386
                $errstr .= "checked out,";
409
            }
387
            }
410
        }
388
        }
389
        $errstr =~ s/,$//;
390
        push @line, $errstr;
411
        $csv->combine(@line);
391
        $csv->combine(@line);
412
        print $csv->string, "\n";
392
        print $csv->string, "\n";
413
    }
393
    }
Lines 425-427 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ Link Here
425
}
405
}
426
406
427
output_html_with_http_headers $input, $cookie, $template->output;
407
output_html_with_http_headers $input, $cookie, $template->output;
428
- 
408
409
sub additemtoresults {
410
    my ( $item, $results ) = @_;
411
    my $itemno = $item->{itemnumber};
412
    # since the script appends to $item, we can just overwrite the hash entry
413
    $results->{$itemno} = $item;
414
}

Return to bug 14399