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 128-134 $(document).ready(function(){ Link Here
128
            <legend>Use a barcode file</legend>
128
            <legend>Use a barcode file</legend>
129
     <ol>
129
     <ol>
130
            <li><label for="uploadbarcodes">Barcode file: </label> <input type="file" id="uploadbarcodes" name="uploadbarcodes" /></li>
130
            <li><label for="uploadbarcodes">Barcode file: </label> <input type="file" id="uploadbarcodes" name="uploadbarcodes" /></li>
131
            <li><label for="setdate">Set inventory date to:</label> <input type="text" id="setdate" name="setdate" value="[% today | $KohaDates %]" class="datepickerfrom" />
131
            <li><label for="setdate">Set inventory date to:</label> <input type="text" id="setdate" name="setdate" value="[% today | $KohaDates %]" class="datepicker" />
132
            </li>
132
            </li>
133
          </ol>
133
          </ol>
134
        </fieldset>
134
        </fieldset>
Lines 200-207 $(document).ready(function(){ Link Here
200
          <ol>
200
          <ol>
201
        [% END %]
201
        [% END %]
202
202
203
        <li><label for="datelastseen">Inventory date:</label>
203
        <li><label for="datelastseen">Last inventory date:</label>
204
            <input type="text" id="datelastseen" name="datelastseen" value="[% datelastseen | $KohaDates %]" class="datepickerfrom" />
204
            <input type="text" id="datelastseen" name="datelastseen" value="[% datelastseen | $KohaDates %]" class="datepicker" />
205
            (Skip records marked as seen on or after this date.)
205
        </li>
206
        </li>
206
        <li><label for="ignoreissued">Skip items on loan: </label>
207
        <li><label for="ignoreissued">Skip items on loan: </label>
207
            [% IF (ignoreissued) %]
208
            [% IF (ignoreissued) %]
Lines 232-238 $(document).ready(function(){ Link Here
232
    [% END %]
233
    [% END %]
233
    [% IF (op) %]
234
    [% IF (op) %]
234
    <form method="post" action="/cgi-bin/koha/tools/inventory.pl" class="checkboxed">
235
    <form method="post" action="/cgi-bin/koha/tools/inventory.pl" class="checkboxed">
235
    <input type="hidden" name="markseen" value="1" />
236
    <input type="hidden" name="minlocation" value="[% minlocation %]" />
236
    <input type="hidden" name="minlocation" value="[% minlocation %]" />
237
    <input type="hidden" name="maxlocation" value="[% maxlocation %]" />
237
    <input type="hidden" name="maxlocation" value="[% maxlocation %]" />
238
    <input type="hidden" name="location" value="[% location %]" />
238
    <input type="hidden" name="location" value="[% location %]" />
Lines 276-282 $(document).ready(function(){ Link Here
276
                [% result.location | html %]
276
                [% result.location | html %]
277
            </td>
277
            </td>
278
            <td>
278
            <td>
279
            <p><a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% result.biblionumber %]" class="openWin">[% result.title | html %]</a></p><p>[% result.author | html %]</p>
279
                <a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% result.biblionumber %]" class="openWin">[% result.title | html %]</a>
280
                <p>[% result.author | html %]</p>
280
            </td>
281
            </td>
281
            <td>
282
            <td>
282
            [% result.notforloan | html %]
283
            [% result.notforloan | html %]
Lines 294-307 $(document).ready(function(){ Link Here
294
            [% result.datelastseen | $KohaDates | html %]
295
            [% result.datelastseen | $KohaDates | html %]
295
            </td>
296
            </td>
296
            <td>
297
            <td>
297
            [% IF result.problem == 'wrongplace' %]
298
            [% FOREACH problem IN result.problems %]
298
                <p>Item should not have been scanned</p>
299
                [% IF problem.key == 'wrongplace' %]
299
            [% ELSIF result.problem == 'missingitem' %]
300
                    Found in wrong place<br/>
300
                <p>Item missing</p>
301
                [% ELSIF problem.key == 'changestatus' %]
301
            [% ELSIF result.problem == 'changestatus' %]
302
                    Unknown not-for-loan status<br/>
302
                <p>Unknown not-for-loan status</p>
303
                [% ELSIF problem.key == 'not_scanned' %]
303
            [% ELSIF result.problem == 'not_scanned' %]
304
                    Missing (not scanned)<br/>
304
                <p>Item should have been scanned</p>
305
                [% ELSIF problem.key == 'checkedout' %]
306
                    Still checked out<br/>
307
                [% ELSIF problem.key == 'no_barcode' %]
308
                    No barcode<br/>
309
                [% END %]
305
            [% END %]
310
            [% END %]
306
            </td>
311
            </td>
307
        </tr>
312
        </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 40-54 use Koha::AuthorisedValues; Link Here
40
use Koha::BiblioFrameworks;
39
use Koha::BiblioFrameworks;
41
use List::MoreUtils qw( none );
40
use List::MoreUtils qw( none );
42
41
43
44
my $minlocation=$input->param('minlocation') || '';
42
my $minlocation=$input->param('minlocation') || '';
45
my $maxlocation=$input->param('maxlocation');
43
my $maxlocation=$input->param('maxlocation');
46
$maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
44
$maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation );
47
my $location=$input->param('location') || '';
45
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');
46
my $ignoreissued=$input->param('ignoreissued');
50
my $datelastseen = $input->param('datelastseen');
47
my $datelastseen = $input->param('datelastseen'); # last inventory date
51
my $markseen = $input->param('markseen');
52
my $branchcode = $input->param('branchcode') || '';
48
my $branchcode = $input->param('branchcode') || '';
53
my $branch     = $input->param('branch');
49
my $branch     = $input->param('branch');
54
my $op         = $input->param('op');
50
my $op         = $input->param('op');
Lines 105-113 for my $statfield (qw/items.notforloan items.itemlost items.withdrawn items.dama Link Here
105
    }
101
    }
106
}
102
}
107
103
108
109
$template->param( statuses => $statuses );
104
$template->param( statuses => $statuses );
110
my $staton = {};                                #authorized values that are ticked
105
my $staton = {}; #authorized values that are ticked
111
for my $authvfield (@$statuses) {
106
for my $authvfield (@$statuses) {
112
    $staton->{$authvfield->{fieldname}} = [];
107
    $staton->{$authvfield->{fieldname}} = [];
113
    for my $authval (@{$authvfield->{values}}){
108
    for my $authval (@{$authvfield->{values}}){
Lines 130-137 $template->param( Link Here
130
    compareinv2barcd         => $compareinv2barcd,
125
    compareinv2barcd         => $compareinv2barcd,
131
);
126
);
132
127
128
# Walk through uploaded barcodes, report errors, mark as seen, check in
129
my $results = {};
133
my @scanned_items;
130
my @scanned_items;
134
my @errorloop;
131
my @errorloop;
132
my $moddatecount = 0;
135
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
133
if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) {
136
    my $dbh = C4::Context->dbh;
134
    my $dbh = C4::Context->dbh;
137
    my $date = dt_from_string( scalar $input->param('setdate') );
135
    my $date = dt_from_string( scalar $input->param('setdate') );
Lines 142-149 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { Link Here
142
    $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
140
    $strsth="select * from items where items.barcode =? and items.withdrawn = 1";
143
    my $qwithdrawn = $dbh->prepare($strsth);
141
    my $qwithdrawn = $dbh->prepare($strsth);
144
142
145
    my $count = 0;
146
147
    my @barcodes;
143
    my @barcodes;
148
    my @uploadedbarcodes;
144
    my @uploadedbarcodes;
149
145
Lines 190-290 if ( $uploadbarcodes && length($uploadbarcodes) > 0 ) { Link Here
190
        } else {
186
        } else {
191
            my $item = GetItem( '', $barcode );
187
            my $item = GetItem( '', $barcode );
192
            if ( defined $item && $item->{'itemnumber'} ) {
188
            if ( defined $item && $item->{'itemnumber'} ) {
193
                ModItem( { datelastseen => $date }, undef, $item->{'itemnumber'} );
189
                # Modify date last seen for scanned items, remove lost status
194
                push @scanned_items, $item;
190
                ModItem( { itemlost => 0, datelastseen => $date }, undef, $item->{'itemnumber'} );
195
                $count++;
191
                $moddatecount++;
192
                # update item hash accordingly
193
                $item->{itemlost} = 0;
194
                $item->{datelastseen} = $date;
196
                unless ( $dont_checkin ) {
195
                unless ( $dont_checkin ) {
197
                    $qonloan->execute($barcode);
196
                    $qonloan->execute($barcode);
198
                    if ($qonloan->rows){
197
                    if ($qonloan->rows){
199
                        my $data = $qonloan->fetchrow_hashref;
198
                        my $data = $qonloan->fetchrow_hashref;
200
                        my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch});
199
                        my ($doreturn, $messages, $iteminformation, $borrower) =AddReturn($barcode, $data->{homebranch});
201
                        if ($doreturn){
200
                        if( $doreturn ) {
202
                            push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_RET'=>1}
201
                            $item->{onloan} = undef;
202
                            $item->{datelastseen} = dt_from_string;
203
                        } else {
203
                        } else {
204
                            push @errorloop, {'barcode'=>$barcode,'ERR_ONLOAN_NOT_RET'=>1}
204
                            push @errorloop, { barcode => $barcode, ERR_ONLOAN_NOT_RET => 1 };
205
                        }
205
                        }
206
                    }
206
                    }
207
                }
207
                }
208
                push @scanned_items, $item;
208
            } else {
209
            } else {
209
                push @errorloop, {'barcode'=>$barcode,'ERR_BARCODE'=>1};
210
                push @errorloop, { barcode => $barcode, ERR_BARCODE => 1 };
210
            }
211
            }
211
        }
212
        }
212
213
    }
213
    }
214
214
    $template->param( date => $date );
215
    $template->param( date => $date, Number => $count );
216
    $template->param( errorloop => \@errorloop ) if (@errorloop);
215
    $template->param( errorloop => \@errorloop ) if (@errorloop);
217
}
216
}
218
217
219
# now build the result list: inventoried items if requested, and mis-placed items -always-
218
# Build inventorylist: used as result list when you do not pass barcodes
220
my $inventorylist;
219
# This list is also used when you want to compare with barcodes
221
my $wrongplacelist;
220
my ( $inventorylist, $rightplacelist );
222
my @items_with_problems;
221
if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) {
223
if ( $markseen or $op ) {
222
    ( $inventorylist ) = GetItemsForInventory({
224
    # retrieve all items in this range.
225
    my $totalrecords;
226
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,
223
      minlocation  => $minlocation,
231
      maxlocation  => $maxlocation,
224
      maxlocation  => $maxlocation,
232
      location     => $location,
225
      location     => $location,
233
      itemtype     => $itemtype,
234
      ignoreissued => $ignoreissued,
226
      ignoreissued => $ignoreissued,
235
      datelastseen => $paramdatelastseen,
227
      datelastseen => $datelastseen,
236
      branchcode   => $branchcode,
228
      branchcode   => $branchcode,
237
      branch       => $branch,
229
      branch       => $branch,
238
      offset       => 0,
230
      offset       => 0,
239
      size         => undef,
240
      statushash   => $staton,
231
      statushash   => $staton,
241
    } );
232
    });
242
233
}
243
    # For the items that may be marked as "wrong place", we only check the location (callnumbers, location and branch)
234
# Build rightplacelist used to check if a scanned item is in the right place.
244
    ($wrongplacelist, $totalrecords) = GetItemsForInventory( {
235
if( @scanned_items ) {
236
    ( $rightplacelist ) = GetItemsForInventory({
245
      minlocation  => $minlocation,
237
      minlocation  => $minlocation,
246
      maxlocation  => $maxlocation,
238
      maxlocation  => $maxlocation,
247
      location     => $location,
239
      location     => $location,
248
      itemtype     => undef,
249
      ignoreissued => undef,
240
      ignoreissued => undef,
250
      datelastseen => undef,
241
      datelastseen => undef,
251
      branchcode   => $branchcode,
242
      branchcode   => $branchcode,
252
      branch       => $branch,
243
      branch       => $branch,
253
      offset       => 0,
244
      offset       => 0,
254
      size         => undef,
255
      statushash   => undef,
245
      statushash   => undef,
256
    } );
246
    });
257
247
    # Convert the structure to a hash on barcode
258
}
248
    $rightplacelist = {
259
249
        map { $_->{barcode} ? ( $_->{barcode}, $_ ) : (); } @$rightplacelist
260
# If "compare barcodes list to results" has been checked, we want to alert for missing items
250
    };
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 };
272
        }
273
    }
274
}
251
}
275
252
276
253
# Report scanned items that are on the wrong place, or have a wrong notforloan
277
254
# status, or are still checked out.
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 ) {
255
foreach my $item ( @scanned_items ) {
256
    $item->{notforloancode} = $item->{notforloan}; # save for later use
282
257
283
  # Saving notforloan code before it's replaced by it's authorised value for later comparison
258
    # Populating with authorised values
284
  $item->{notforloancode} = $item->{notforloan};
259
    foreach my $field ( keys %$item ) {
285
286
  # Populating with authorised values
287
  foreach my $field ( keys %$item ) {
288
        # If the koha field is mapped to a marc field
260
        # If the koha field is mapped to a marc field
289
        my $fc = $item->{'frameworkcode'} || '';
261
        my $fc = $item->{'frameworkcode'} || '';
290
        my ($f, $sf) = GetMarcFromKohaField("items.$field", $fc);
262
        my ($f, $sf) = GetMarcFromKohaField("items.$field", $fc);
Lines 299-352 foreach my $item ( @scanned_items ) { Link Here
299
        }
271
        }
300
    }
272
    }
301
273
302
    next if $item->{onloan}; # skip checked out items
303
304
    # If we have scanned items with a non-matching notforloan value
274
    # If we have scanned items with a non-matching notforloan value
305
    if (none { $item->{'notforloancode'} eq $_ } @notforloans) {
275
    if( none { $item->{'notforloancode'} eq $_ } @notforloans ) {
306
        $item->{problem} = 'changestatus';
276
        $item->{problems}->{changestatus} = 1;
307
        push @items_with_problems, { %$item };
277
        additemtoresults( $item, $results );
308
    }
309
    if (none { $item->{barcode} eq $_->{barcode} && !$_->{'onloan'} } @$wrongplacelist) {
310
        $item->{problem} = 'wrongplace';
311
        push @items_with_problems, { %$item };
312
    }
278
    }
313
279
314
    # Modify date last seen for scanned items
280
    # Report an item that is checked out (unusual!) or wrongly placed
315
    ModDateLastSeen($item->{'itemnumber'});
281
    if( $item->{onloan} ) {
316
    $moddatecount++;
282
        $item->{problems}->{checkedout} = 1;
283
        additemtoresults( $item, $results );
284
        next; # do not modify item
285
    } elsif( !exists $rightplacelist->{ $item->{barcode} } ) {
286
        $item->{problems}->{wrongplace} = 1;
287
        additemtoresults( $item, $results );
288
    }
317
}
289
}
318
290
291
# Compare barcodes with inventory list, report no_barcode and not_scanned.
292
# not_scanned can be interpreted as missing
319
if ( $compareinv2barcd ) {
293
if ( $compareinv2barcd ) {
320
    my @scanned_barcodes = map {$_->{barcode}} @scanned_items;
294
    my @scanned_barcodes = map {$_->{barcode}} @scanned_items;
321
    for my $should_be_scanned ( @$inventorylist ) {
295
    for my $item ( @$inventorylist ) {
322
        my $barcode = $should_be_scanned->{barcode};
296
        my $barcode = $item->{barcode};
323
        unless ( grep /^$barcode$/, @scanned_barcodes ) {
297
        if( !$barcode ) {
324
            $should_be_scanned->{problem} = 'not_scanned';
298
            $item->{problems}->{no_barcode} = 1;
325
            push @items_with_problems, { %$should_be_scanned };
299
        } elsif ( grep /^$barcode$/, @scanned_barcodes ) {
300
            next;
301
        } else {
302
            $item->{problems}->{not_scanned} = 1;
326
        }
303
        }
304
        additemtoresults( $item, $results );
327
    }
305
    }
328
}
306
}
329
307
330
for my $item ( @items_with_problems ) {
308
# Construct final results, add biblio information
309
my $loop = $uploadbarcodes
310
    ? [ map { $results->{$_} } keys %$results ]
311
    : $inventorylist // [];
312
for my $item ( @$loop ) {
331
    my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber});
313
    my $biblio = C4::Biblio::GetBiblioData($item->{biblionumber});
332
    $item->{title} = $biblio->{title};
314
    $item->{title} = $biblio->{title};
333
    $item->{author} = $biblio->{author};
315
    $item->{author} = $biblio->{author};
334
}
316
}
335
317
336
# If a barcode file is given, we want to show problems, else all items
337
my @results;
338
@results = $uploadbarcodes
339
            ? @items_with_problems
340
            : $op
341
                ? @$inventorylist
342
                : ();
343
344
$template->param(
318
$template->param(
345
    moddatecount => $moddatecount,
319
    moddatecount => $moddatecount,
346
    loop       => \@results,
320
    loop         => $loop,
347
    op         => $op
321
    op           => $op,
348
);
322
);
349
323
324
# Export to csv
350
if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
325
if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){
351
    eval {use Text::CSV};
326
    eval {use Text::CSV};
352
    my $csv = Text::CSV->new or
327
    my $csv = Text::CSV->new or
Lines 381-402 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ Link Here
381
    print $csv->string, "\n";
356
    print $csv->string, "\n";
382
357
383
    my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
358
    my @keys = qw / title author barcode itemnumber homebranch location itemcallnumber notforloan lost damaged withdrawn stocknumber /;
384
    for my $item ( @results ) {
359
    for my $item ( @$loop ) {
385
        my @line;
360
        my @line;
386
        for my $key (@keys) {
361
        for my $key (@keys) {
387
            push @line, $item->{$key};
362
            push @line, $item->{$key};
388
        }
363
        }
389
        if ( defined $item->{problem} ) {
364
        my $errstr = '';
390
            if ( $item->{problem} eq 'wrongplace' ) {
365
        foreach my $key ( keys %{$item->{problems}} ) {
391
                push @line, "wrong place";
366
            if( $key eq 'wrongplace' ) {
392
            } elsif ( $item->{problem} eq 'missingitem' ) {
367
                $errstr .= "wrong place,";
393
                push @line, "missing item";
368
            } elsif( $key eq 'changestatus' ) {
394
            } elsif ( $item->{problem} eq 'changestatus' ) {
369
                $errstr .= "unknown notforloan status,";
395
                push @line, "change item status";
370
            } elsif( $key eq 'not_scanned' ) {
396
            } elsif ($item->{problem} eq 'not_scanned' ) {
371
                $errstr .= "missing,";
397
                push @line, "item not scanned";
372
            } elsif( $key eq 'no_barcode' ) {
373
                $errstr .= "no barcode,";
374
            } elsif( $key eq 'checkedout' ) {
375
                $errstr .= "checked out,";
398
            }
376
            }
399
        }
377
        }
378
        $errstr =~ s/,$//;
379
        push @line, $errstr;
400
        $csv->combine(@line);
380
        $csv->combine(@line);
401
        print $csv->string, "\n";
381
        print $csv->string, "\n";
402
    }
382
    }
Lines 414-416 if (defined $input->param('CSVexport') && $input->param('CSVexport') eq 'on'){ Link Here
414
}
394
}
415
395
416
output_html_with_http_headers $input, $cookie, $template->output;
396
output_html_with_http_headers $input, $cookie, $template->output;
417
- 
397
398
sub additemtoresults {
399
    my ( $item, $results ) = @_;
400
    my $itemno = $item->{itemnumber};
401
    # since the script appends to $item, we can just overwrite the hash entry
402
    $results->{$itemno} = $item;
403
}

Return to bug 14399