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

(-)a/Koha/Item.pm (+49 lines)
Lines 1443-1448 sub columns_to_str { Link Here
1443
    return $values;
1443
    return $values;
1444
}
1444
}
1445
1445
1446
sub _status {
1447
    my ($self) = @_;
1448
1449
    my @statuses;
1450
    if ( my $checkout = $self->checkout ) {
1451
        unless ( $checkout->onsite_checkout ) {
1452
            push @statuses, "checked_out";
1453
        } else {
1454
            push @statuses, "local_use";
1455
        }
1456
    } elsif ( my $transfer = $self->transfer ) {
1457
        push @statuses, "in_transit";
1458
    }
1459
    if ( $self->itemlost ) {
1460
        push @statuses, 'lost';
1461
    }
1462
    if ( $self->withdrawn ) {
1463
        push @statuses, 'withdrawn';
1464
    }
1465
    if ( $self->damaged ) {
1466
        push @statuses, 'damaged';
1467
    }
1468
    if ( $self->notforloan || $self->item_type->notforloan ) {
1469
1470
        # TODO on a big Koha::Items loop we are going to join with item_type too often, use a cache
1471
        push @statuses, 'not_for_loan';
1472
    }
1473
    if ( $self->first_hold ) {
1474
        push @statuses, 'on_hold';
1475
    }
1476
    if ( C4::Context->preference('UseRecalls') && $self->recall ) {
1477
        push @statuses, 'recalled';
1478
    }
1479
1480
    unless (@statuses) {
1481
        push @statuses, 'available';
1482
    }
1483
1484
    if ( $self->restricted ) {
1485
        push @statuses, 'restricted';
1486
    }
1487
1488
    if ( $self->in_bundle ) {
1489
        push @statuses, 'in_bundle';
1490
    }
1491
1492
    return join ',', @statuses;
1493
}
1494
1446
=head3 additional_attributes
1495
=head3 additional_attributes
1447
1496
1448
    my $attributes = $item->additional_attributes;
1497
    my $attributes = $item->additional_attributes;
(-)a/Koha/Items.pm (+198 lines)
Lines 222-227 sub filter_by_bookable { Link Here
222
    );
222
    );
223
}
223
}
224
224
225
=head3 filter_by_checked_out
226
227
  my $checked_out_items = $items->filter_by_checked_out;
228
229
Returns a new resultset, containing only those items that are currently checked out.
230
231
=cut
232
233
sub filter_by_checked_out {
234
    my ( $self, $params ) = @_;
235
236
    $params //= {};
237
    my $checkouts = Koha::Checkouts->search(
238
        { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], },
239
        {
240
            columns  => ['itemnumber'],
241
            distinct => 1
242
        }
243
    )->_resultset->as_query;
244
245
    return $self->search( { 'me.itemnumber' => { '-in' => $checkouts } } );
246
}
247
248
=head3 filter_by_in_transit
249
250
  my $in_tranist_items = $items->filter_by_in_transit;
251
252
Returns a new resultset, containing only those items that are currently in transit.
253
254
=cut
255
256
sub filter_by_in_transit {
257
    my ( $self, $params ) = @_;
258
259
    $params //= {};
260
    my $transfers = Koha::Item::Transfers->search(
261
        { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], },
262
        {
263
            columns  => ['itemnumber'],
264
            distinct => 1
265
        }
266
    )->_resultset->as_query;
267
268
    return $self->search( { 'me.itemnumber' => { '-in' => $transfers } } );
269
}
270
271
=head3 filter_by_has_holds
272
273
  my $has_hold_items = $items->filter_by_has_holds;
274
275
Returns a new resultset, containing only those items that currently have holds.
276
277
=cut
278
279
sub filter_by_has_holds {
280
    my ( $self, $params ) = @_;
281
282
    $params //= {};
283
    my $holds = Koha::Holds->search(
284
        { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], },
285
        {
286
            columns  => ['itemnumber'],
287
            distinct => 1
288
        }
289
    )->_resultset->as_query;
290
291
    return $self->search( { 'me.itemnumber' => { '-in' => $holds } } );
292
}
293
294
=head3 filter_by_has_recalls
295
296
  my $has_recalls_items = $items->filter_by_has_recalls;
297
298
Returns a new resultset, containing only those items that currently have recalls.
299
300
=cut
301
302
sub filter_by_has_recalls {
303
    my ( $self, $params ) = @_;
304
305
    $params //= {};
306
    my $recalls = Koha::Recalls->search(
307
        { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], 'me.item_level' => 1, },
308
        {
309
            columns  => ['itemnumber'],
310
            distinct => 1
311
        }
312
    )->_resultset->as_query;
313
    return $self->search( { 'me.itemnumber' => { '-in' => $recalls } } );
314
}
315
316
=head3 filter_by_in_bundle
317
318
Returns a new resultset, containing only those items that currently are part of a bundle.
319
320
=cut
321
322
sub filter_by_in_bundle {
323
    my ($self) = @_;
324
325
    my @in_bundle_items;
326
    while ( my $item = $self->next ) {
327
        push @in_bundle_items, $item if $item->in_bundle;
328
    }
329
330
    my @bundled_items = map { $_->itemnumber } @in_bundle_items;
331
    return $self->search( { 'me.itemnumber' => { '-in' => \@bundled_items } } );
332
}
333
334
=head3 filter_by_available
335
336
  my $available_items = $items->filter_by_available;
337
338
Returns a new resultset, containing only those items that are currently available.
339
340
=cut
341
342
sub filter_by_available {
343
    my ($self) = @_;
344
345
    my @all_itemnumbers = $self->get_column('itemnumber');
346
    my @not_available_itemnumbers;
347
    push @not_available_itemnumbers, $self->filter_by_checked_out->get_column('itemnumber');
348
    push @not_available_itemnumbers, $self->filter_by_in_transit->get_column('itemnumber');
349
350
    push @not_available_itemnumbers, $self->filter_by_has_holds->get_column('itemnumber');
351
    push @not_available_itemnumbers, $self->filter_by_has_recalls->get_column('itemnumber');
352
353
    my @item_types_notforloan = Koha::ItemTypes->search( { notforloan => { '!=' => 0 } } )->get_column('itemtype');
354
    return Koha::Items->search(
355
        {
356
            'me.itemnumber' => [ array_minus @all_itemnumbers, @not_available_itemnumbers ],
357
            itemlost        => 0,
358
            withdrawn       => 0,
359
            damaged         => 0,
360
            notforloan      => 0,
361
            restricted      => [ { '!=' => 0 }, undef ],
362
            'me.itype'      => { -not_in => \@item_types_notforloan },
363
        }
364
    );
365
}
366
225
=head3 move_to_biblio
367
=head3 move_to_biblio
226
368
227
 $items->move_to_biblio($to_biblio);
369
 $items->move_to_biblio($to_biblio);
Lines 485-490 sub apply_regex { Link Here
485
    return $value;
627
    return $value;
486
}
628
}
487
629
630
=head3 search
631
632
  my $search_result = $object->search( $params, $attributes );
633
634
Filters items based on the specified status.
635
636
=cut
637
638
sub search {
639
    my ( $self, $params, $attributes ) = @_;
640
    my $status = ( $params && ref($params) eq 'HASH' ) ? delete $params->{_status} : undef;
641
    if ($status) {
642
        if ( $status eq 'checked_out' ) {
643
            $self = $self->filter_by_checked_out( { onsite_checkout => 0 } );
644
        }
645
        if ( $status eq 'local_use' ) {
646
            $self = $self->filter_by_checked_out( { onsite_checkout => 1 } );
647
        }
648
        if ( $status eq 'in_transit' ) {
649
            $self = $self->filter_by_in_transit;
650
        }
651
        if ( $status eq 'lost' ) {
652
            $self = $self->search( { itemlost => { '!=' => 0 } } );
653
        }
654
        if ( $status eq 'withdrawn' ) {
655
            $self = $self->search( { withdrawn => { '!=' => 0 } } );
656
        }
657
        if ( $status eq 'damaged' ) {
658
            $self = $self->search( { damaged => { '!=' => 0 } } );
659
        }
660
        if ( $status eq 'not_for_loan' ) {
661
            my @item_types_notforloan =
662
                Koha::ItemTypes->search( { notforloan => { '!=' => 0 } } )->get_column('itemtype');
663
            $self = $self->search( [ { notforloan => { '!=' => 0 } }, { 'me.itype' => \@item_types_notforloan } ] );
664
        }
665
        if ( $status eq 'on_hold' ) {
666
            $self = $self->filter_by_has_holds;
667
        }
668
        if ( $status eq 'recalled' ) {
669
            $self = $self->filter_by_has_recalls;
670
        }
671
        if ( $status eq 'in_bundle' ) {
672
            $self = $self->filter_by_in_bundle;
673
        }
674
675
        if ( $status eq 'available' ) {
676
            $self = $self->filter_by_available;
677
        }
678
679
        if ( $status eq 'restricted' ) {
680
            $self = $self->search( { restricted => [ { '!=' => 0 } ] } );
681
        }
682
    }
683
    return $self->SUPER::search( $params, $attributes );
684
}
685
488
=head3 search_ordered
686
=head3 search_ordered
489
687
490
 $items->search_ordered;
688
 $items->search_ordered;
(-)a/api/v1/swagger/definitions/item.yaml (-1 / +6 lines)
Lines 322-325 properties: Link Here
322
      - object
322
      - object
323
      - "null"
323
      - "null"
324
    description: A return claims object if one exists that's unresolved
324
    description: A return claims object if one exists that's unresolved
325
additionalProperties: false
325
  _status:
326
    type:
327
      - string
328
      - "null"
329
    description: The status of the item
330
additionalProperties: false
(-)a/api/v1/swagger/paths/biblios.yaml (-1 / +2 lines)
Lines 458-463 Link Here
458
          type: string
458
          type: string
459
          enum:
459
          enum:
460
            - +strings
460
            - +strings
461
            - _status
461
            - home_library
462
            - home_library
462
            - holding_library
463
            - holding_library
463
            - biblio.title
464
            - biblio.title
Lines 918-921 Link Here
918
      "503":
919
      "503":
919
        description: Under maintenance
920
        description: Under maintenance
920
        schema:
921
        schema:
921
          $ref: "../swagger.yaml#/definitions/error"
922
          $ref: "../swagger.yaml#/definitions/error"
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc (-25 / +32 lines)
Lines 220-225 Link Here
220
            });
220
            });
221
    const item_types_notforloan = new Map(all_item_types.map( it => [it.itemtype, it.notforloan] ));
221
    const item_types_notforloan = new Map(all_item_types.map( it => [it.itemtype, it.notforloan] ));
222
222
223
    const statuses = {checked_out: _("Checked out"), local_use: _("On-site checkout"), in_transit: _("In transit"), lost: _("Lost"), withdrawn: _("Withdrawn"), damaged:_("Damaged"), not_for_loan: _("Not for loan"), on_hold: _("On hold"), recalled: _("Recalled"), available: _("Available"), restricted: _("Restricted"), in_bundle: _("In bundle")};
224
    const all_statuses = Object.keys(statuses).map(k => {return {_id: k, _str: statuses[k]}}).sort();
225
223
    const can_edit_items_from = [% To.json(can_edit_items_from || []) | $raw %];
226
    const can_edit_items_from = [% To.json(can_edit_items_from || []) | $raw %];
224
    const item_type_image_locations = [% To.json(item_type_image_locations) | $raw %];
227
    const item_type_image_locations = [% To.json(item_type_image_locations) | $raw %];
225
    const av_loc = new Map([% To.json(AuthorisedValues.Get('LOC')) | $raw %].map( av => [av.authorised_value, av.lib]));
228
    const av_loc = new Map([% To.json(AuthorisedValues.Get('LOC')) | $raw %].map( av => [av.authorised_value, av.lib]));
Lines 247-253 Link Here
247
    [%# In case or SeparateHoldings we may need to display the number of biblios in each tab %]
250
    [%# In case or SeparateHoldings we may need to display the number of biblios in each tab %]
248
    [%# Do we need separate/new endpoints or do we hack the somewhere client-side? %]
251
    [%# Do we need separate/new endpoints or do we hack the somewhere client-side? %]
249
    let item_table_url = "/api/v1/biblios/[% biblio.biblionumber | uri %]/items?";
252
    let item_table_url = "/api/v1/biblios/[% biblio.biblionumber | uri %]/items?";
250
    let embed = ["+strings,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"];
253
    let embed = ["+strings,_status,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"];
251
    [% IF Koha.Preference('LocalCoverImages') %]
254
    [% IF Koha.Preference('LocalCoverImages') %]
252
        embed.push('cover_image_ids');
255
        embed.push('cover_image_ids');
253
    [% END %]
256
    [% END %]
Lines 319-330 Link Here
319
            items_selection[tab_id] = [];
322
            items_selection[tab_id] = [];
320
        }
323
        }
321
324
325
        default_filters._status = function(){
326
            return $("#" + tab_id + "_status select").val();
327
        };
328
322
        let offset = 2;
329
        let offset = 2;
323
        [% UNLESS Koha.Preference('LocalCoverImages') %]offset--;[% END %]
330
        [% UNLESS Koha.Preference('LocalCoverImages') %]offset--;[% END %]
324
        let filters_options = {
331
        let filters_options = {
325
            [offset]   : () => all_item_types,
332
            [offset]   : () => all_item_types,
326
            [offset+1] : () => all_libraries,
333
            [offset+1] : () => all_libraries,
327
            [offset+2] : () => all_libraries,
334
            [offset+2] : () => all_libraries,
335
            [offset+6] : () => all_statuses,
328
        };
336
        };
329
337
330
        var items_table = $("#" + tab_id + '_table').kohaTable({
338
        var items_table = $("#" + tab_id + '_table').kohaTable({
Lines 490-518 Link Here
490
                }
498
                }
491
            },
499
            },
492
            {
500
            {
493
                data: "me.lost_status",
501
                data: "",
494
                className: "status",
502
                className: "status",
495
                searchable: false, // FIXME We are losing the ability to search on the status
503
                searchable: false,
496
                orderable: false,
504
                orderable: false,
497
                render: function (data, type, row, meta) {
505
                render: function (data, type, row, meta) {
498
                    let nodes = "";
506
                    let nodes = "";
499
                    if ( row.checkout ) {
507
                    row._status.split(",").forEach( status => {
508
                    if ( status == 'checked_out' || status == 'local_use') {
509
                        nodes += '<span>';
510
500
                        [%# Hacky for patron_to_html in case we simply want to display the patron's library name %]
511
                        [%# Hacky for patron_to_html in case we simply want to display the patron's library name %]
501
                        row.checkout.patron.library = { name: libraries_names.get(row.checkout.patron.library_id) };
512
                        row.checkout.patron.library = { name: libraries_names.get(row.checkout.patron.library_id) };
513
                        let patron_to_html = $patron_to_html(row.checkout.patron, { url: true, display_cardnumber: true, hide_patron_name });
502
514
503
                        nodes += '<span>';
515
                        if ( status == 'local_use' ) {
504
                        if ( row.checkout.onsite_checkout ) {
505
                            let patron_to_html = $patron_to_html(row.checkout.patron, { url: true, display_cardnumber: true, hide_patron_name });
506
                            nodes += _("Currently in local use by %s").format(patron_to_html);
516
                            nodes += _("Currently in local use by %s").format(patron_to_html);
507
                        } else {
517
                        } else {
508
                            nodes += '<span class="datedue">';
518
                            nodes += '<span class="datedue">';
509
                            let patron_to_html = $patron_to_html(row.checkout.patron, { url: true, display_cardnumber: true, hide_patron_name });
510
                            nodes += _("Checked out to %s").format(patron_to_html);
519
                            nodes += _("Checked out to %s").format(patron_to_html);
511
                        }
520
                        }
512
                        nodes += ': ';
521
                        nodes += ': ';
513
                        nodes += _("due %s").format($date(row.checkout.due_date, { as_due_date: true }));
522
                        nodes += _("due %s").format($date(row.checkout.due_date, { as_due_date: true }));
514
                        nodes += "</span>"
523
                        nodes += "</span>"
515
                    } else if ( row.transfer ) {
524
                    }
525
                    if ( status == 'in_transit' ) {
516
                        if ( row.transfer.datesent ) {
526
                        if ( row.transfer.datesent ) {
517
                            nodes += '<span class="intransit">%s</span>'.format(_("In transit from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.datesent)));
527
                            nodes += '<span class="intransit">%s</span>'.format(_("In transit from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.datesent)));
518
                        } else {
528
                        } else {
Lines 520-552 Link Here
520
                        }
530
                        }
521
                    }
531
                    }
522
532
523
                    if ( row.lost_status ) {
533
                    if ( status == 'lost' ) {
524
                        let lost_lib = av_lost.get(row.lost_status.toString()) || _("Unavailable (lost or missing");
534
                        let lost_lib = av_lost.get(row.lost_status.toString()) || _("Unavailable (lost or missing");
525
                        nodes += '<span class="lost">%s</span>'.format(escape_str(lost_lib));
535
                        nodes += '<span class="lost">%s</span>'.format(escape_str(lost_lib));
526
                        [% IF Koha.Preference('ClaimReturnedLostValue') %]
536
                        const hasReturnClaims = row.return_claims && row.return_claims.filter(rc => !rc.resolution).length > 0 ? true : false
527
                            const hasReturnClaims = row.return_claims.filter(rc => !rc.resolution).length > 0 ? true : false
537
                        if(hasReturnClaims) {
528
                            if(hasReturnClaims) {
538
                            nodes += '<span class="claimed_returned">' + _("(Claimed returned)") + '</span>';
529
                                nodes += '<span class="claimed_returned">' + _("(Claimed returned)") + '</span>';
539
                        }
530
                            }
531
                        [% END %]
532
                    }
540
                    }
533
541
534
                    if ( row.withdrawn ) {
542
                    if ( status == 'withdrawn' ) {
535
                        let withdrawn_lib = av_withdrawn.get(row.withdrawn.toString()) || _("Withdrawn");
543
                        let withdrawn_lib = av_withdrawn.get(row.withdrawn.toString()) || _("Withdrawn");
536
                        nodes += '<span class="wdn">%s</span>'.format(escape_str(withdrawn_lib));
544
                        nodes += '<span class="wdn">%s</span>'.format(escape_str(withdrawn_lib));
537
                    }
545
                    }
538
546
539
                    if ( row.damaged_status ) {
547
                    if ( status == 'damaged' ) {
540
                        let damaged_lib = av_damaged.get(row.damaged_status.toString()) || _("Damaged");
548
                        let damaged_lib = av_damaged.get(row.damaged_status.toString()) || _("Damaged");
541
                        nodes += '<span class="dmg">%s</span>'.format(escape_str(damaged_lib));
549
                        nodes += '<span class="dmg">%s</span>'.format(escape_str(damaged_lib));
542
                    }
550
                    }
543
551
544
                    if ( row.not_for_loan_status || item_types_notforloan.get(row.item_type_id) ) {
552
                    if ( status == 'not_for_loan' ) {
545
                        let not_for_loan_lib = av_not_for_loan.get(row.not_for_loan_status.toString());
553
                        let not_for_loan_lib = av_not_for_loan.get(row.not_for_loan_status.toString());
546
                        nodes += '<span class="notforloan">%s'.format(_("Not for loan")) + ( not_for_loan_lib ? '<span class="reason"> (%s)</span>'.format(escape_str(not_for_loan_lib)) : '' ) + '</span>';
554
                        nodes += '<span class="notforloan">%s'.format(_("Not for loan")) + ( not_for_loan_lib ? '<span class="reason"> (%s)</span>'.format(escape_str(not_for_loan_lib)) : '' ) + '</span>';
547
                    }
555
                    }
548
556
549
                    if ( row.first_hold ) {
557
                    if ( status == 'on_hold') {
550
                        if ( row.first_hold.waiting_date ) {
558
                        if ( row.first_hold.waiting_date ) {
551
                            if ( row.first_hold.desk ) {
559
                            if ( row.first_hold.desk ) {
552
                                nodes += '<span class="waitingat">%s</span>'.format(_("Waiting at %s, %s since %s.").format(row.first_hold._strings.pickup_library_id.str, row.first_hold.desk.desk_name, $date(row.first_hold.waiting_date)));
560
                                nodes += '<span class="waitingat">%s</span>'.format(_("Waiting at %s, %s since %s.").format(row.first_hold._strings.pickup_library_id.str, row.first_hold.desk.desk_name, $date(row.first_hold.waiting_date)));
Lines 580-597 Link Here
580
                            }
588
                            }
581
                        }
589
                        }
582
                    [% END %]
590
                    [% END %]
583
591
                    if ( status == 'available' ) {
584
                    if ( ! ( row.not_for_loan_status || item_types_notforloan.get(row.item_type_id) || row.checked_out_date || row.lost_status || row.withdrawn || row.damaged_status || row.transfer || row.first_hold || ( row.recall && ( row.item_id === row.recall.item_id ) ) )) {
585
                        nodes += ' <span>%s</span>'.format(_("Available"))
592
                        nodes += ' <span>%s</span>'.format(_("Available"))
586
                    }
593
                    }
587
594
588
                    if ( row.restricted_status ) {
595
                    if ( status == 'restricted') {
589
                        nodes += '<span class="restricted">(%s)</span>'.format(escape_str(av_restricted.get(row.restricted_status.toString())));
596
                        nodes += '<span class="restricted">(%s)</span>'.format(escape_str(av_restricted.get(row.restricted_status.toString())));
590
                    }
597
                    }
591
598
                    if ( status == 'in_bundle') {
592
                    if ( row.in_bundle ) {
593
                        nodes += '<span class="bundled">%s</span>'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true })));
599
                        nodes += '<span class="bundled">%s</span>'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true })));
594
                    }
600
                    }
601
                    }); //end forEach
595
                    return nodes;
602
                    return nodes;
596
                }
603
                }
597
            },
604
            },
(-)a/koha-tmpl/intranet-tmpl/prog/js/datatables.js (-3 / +6 lines)
Lines 606-612 function _dt_default_ajax (params){ Link Here
606
                    } else if ( f == '-and' ) {
606
                    } else if ( f == '-and' ) {
607
                        if (v) and_query_parameters.push(v)
607
                        if (v) and_query_parameters.push(v)
608
                    } else if ( v ) {
608
                    } else if ( v ) {
609
                        additional_filters[k] = v;
609
                        additional_filters[k] = v
610
                            .replace(/^\^/, "")
611
                            .replace(/\$$/, "");
610
                    }
612
                    }
611
                }
613
                }
612
                if ( Object.keys(additional_filters).length ) {
614
                if ( Object.keys(additional_filters).length ) {
Lines 883-891 function _dt_add_filters(table_node, table_dt, filters_options = {}) { Link Here
883
        let i = column.index();
885
        let i = column.index();
884
        var visible_i = table_dt.column.index('fromData', i);
886
        var visible_i = table_dt.column.index('fromData', i);
885
        let th = $(table_node).find('thead tr:eq(1) th:eq(%s)'.format(visible_i));
887
        let th = $(table_node).find('thead tr:eq(1) th:eq(%s)'.format(visible_i));
886
        var is_searchable = columns[i].bSearchable;
888
        var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable;
887
        $(th).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc");
889
        $(th).removeClass('sorting').removeClass("sorting_asc").removeClass("sorting_desc");
888
        if ( is_searchable ) {
890
        $(this).data("th-id", i);
891
        if (is_searchable || $(this).data("filter") || filters_options[i]) {
889
            let input_type = 'input';
892
            let input_type = 'input';
890
            let existing_search = column.search();
893
            let existing_search = column.search();
891
            if ( $(th).data('filter') || filters_options.hasOwnProperty(i)) {
894
            if ( $(th).data('filter') || filters_options.hasOwnProperty(i)) {
(-)a/t/db_dependent/Koha/Item.t (-1 / +139 lines)
Lines 20-26 Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
use utf8;
21
use utf8;
22
22
23
use Test::More tests => 38;
23
use Test::More tests => 39;
24
use Test::Exception;
24
use Test::Exception;
25
use Test::MockModule;
25
use Test::MockModule;
26
use Test::Warn;
26
use Test::Warn;
Lines 45-50 use t::lib::Dates; Link Here
45
my $schema  = Koha::Database->new->schema;
45
my $schema  = Koha::Database->new->schema;
46
my $builder = t::lib::TestBuilder->new;
46
my $builder = t::lib::TestBuilder->new;
47
47
48
subtest '_status' => sub {
49
    plan tests => 12;
50
51
    $schema->storage->txn_begin;
52
53
    my $item    = $builder->build_sample_item();
54
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
55
    t::lib::Mocks::mock_userenv( { branchcode => $library->branchcode } );
56
57
    t::lib::Mocks::mock_preference( 'UseRecalls', 1 );
58
59
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
60
61
    my @test_cases = (
62
        {
63
            setup => sub {
64
                my $onloan_item = $builder->build_sample_item();
65
                AddIssue( $patron, $onloan_item->barcode, dt_from_string );
66
                return $onloan_item;
67
            },
68
            expected_status => 'checked_out',
69
            description     => 'Checked out status correctly returned',
70
        },
71
        {
72
            setup => sub {
73
                my $onsite_item = $builder->build_sample_item();
74
                AddIssue(
75
                    $patron, $onsite_item->barcode, dt_from_string, undef, undef, undef,
76
                    { onsite_checkout => 1 }
77
                );
78
                return $onsite_item;
79
            },
80
            expected_status => 'local_use',
81
            description     => 'Local use status correctly returned',
82
        },
83
        {
84
            setup => sub {
85
                return $item;
86
            },
87
            expected_status => 'available',
88
            description     => 'Available status correctly returned',
89
        },
90
        {
91
            setup => sub {
92
                $item->itemlost(1)->store();
93
                return $item;
94
            },
95
            expected_status => 'lost',
96
            description     => 'Lost status correctly returned',
97
        },
98
        {
99
            setup => sub {
100
                $item->withdrawn(1)->store();
101
                return $item;
102
            },
103
            expected_status => qr/lost,withdrawn/,
104
            description     => 'Lost and withdrawn status correctly returned',
105
        },
106
        {
107
            setup => sub {
108
                $item->damaged(1)->store();
109
                return $item;
110
            },
111
            expected_status => qr/lost,withdrawn,damaged/,
112
            description     => 'Lost, withdrawn, and damaged status correctly returned',
113
        },
114
        {
115
            setup => sub {
116
                $item->notforloan(1)->store();
117
                return $item;
118
            },
119
            expected_status => 'not_for_loan',
120
            description     => 'Positive not_for_loan status correctly returned',
121
        },
122
        {
123
            setup => sub {
124
                $item->notforloan(-1)->store();
125
                return $item;
126
            },
127
            expected_status => 'not_for_loan',
128
            description     => 'Negative not_for_loan status correctly returned',
129
        },
130
        {
131
            setup => sub {
132
                my $itemtype = $builder->build_object( { class => "Koha::ItemTypes", value => { notforloan => 1 } } );
133
                my $notforloan_item = $builder->build_sample_item( { itype => $itemtype->itemtype, } );
134
                return $notforloan_item;
135
            },
136
            expected_status => 'not_for_loan',
137
            description     => 'Item type not_for_loan status correctly returned',
138
        },
139
        {
140
            setup => sub {
141
                my $onhold_item = $builder->build_sample_item();
142
                C4::Reserves::AddReserve(
143
                    {
144
                        branchcode     => $library->branchcode,
145
                        borrowernumber => $patron->borrowernumber,
146
                        biblionumber   => $onhold_item->biblionumber,
147
                        itemnumber     => $onhold_item->itemnumber,
148
                    }
149
                );
150
                return $onhold_item;
151
            },
152
            expected_status => 'on_hold',
153
            description     => 'On hold status correctly returned',
154
        },
155
        {
156
            setup => sub {
157
                my $recalled_item = $builder->build_sample_item();
158
                AddIssue( $patron, $recalled_item->barcode, dt_from_string );
159
                Koha::Recalls->add_recall(
160
                    { biblio => $recalled_item->biblio, item => $recalled_item, patron => $patron } );
161
                return $recalled_item;
162
            },
163
            expected_status => 'recalled',
164
            description     => 'Recalled status correctly returned',
165
        },
166
        {
167
            setup => sub {
168
                $item->restricted(1)->store();
169
                return $item;
170
            },
171
            expected_status => 'restricted',
172
            description     => 'Restricted status correctly returned',
173
        },
174
    );
175
176
    foreach my $test_case (@test_cases) {
177
        my $item = $test_case->{setup}->();
178
        ok( $item->_status() =~ /$test_case->{expected_status}/, $test_case->{description} );
179
    }
180
181
    t::lib::Mocks::mock_preference( 'UseRecalls', 0 );
182
183
    $schema->storage->txn_rollback;
184
};
185
48
subtest 'z3950_status' => sub {
186
subtest 'z3950_status' => sub {
49
    plan tests => 9;
187
    plan tests => 9;
50
188
(-)a/t/db_dependent/Koha/Items.t (-2 / +468 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 20;
22
use Test::More tests => 27;
23
23
24
use Test::MockModule;
24
use Test::MockModule;
25
use Test::Exception;
25
use Test::Exception;
Lines 34-39 use Koha::Items; Link Here
34
use Koha::Database;
34
use Koha::Database;
35
use Koha::DateUtils qw( dt_from_string );
35
use Koha::DateUtils qw( dt_from_string );
36
use Koha::Statistics;
36
use Koha::Statistics;
37
use Koha::Recalls;
37
38
38
use t::lib::TestBuilder;
39
use t::lib::TestBuilder;
39
use t::lib::Mocks;
40
use t::lib::Mocks;
Lines 68-73 is( Koha::Items->search->count, $nb_of_items + 2, 'The 2 items should have been Link Here
68
my $retrieved_item_1 = Koha::Items->find( $new_item_1->itemnumber );
69
my $retrieved_item_1 = Koha::Items->find( $new_item_1->itemnumber );
69
is( $retrieved_item_1->barcode, $new_item_1->barcode, 'Find a item by id should return the correct item' );
70
is( $retrieved_item_1->barcode, $new_item_1->barcode, 'Find a item by id should return the correct item' );
70
71
72
subtest 'search' => sub {
73
74
    plan tests => 9;
75
    $schema->storage->txn_begin;
76
77
    my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
78
    my $patron_2 = $builder->build_object( { class => 'Koha::Patrons' } );
79
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
80
81
    my $library_1 = $builder->build( { source => 'Branch' } );
82
    my $library_2 = $builder->build( { source => 'Branch' } );
83
84
    my $biblio = $builder->build_sample_biblio();
85
86
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
87
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
88
89
    my $available_items = Koha::Items->search(
90
        {
91
            _status      => 'available',
92
            biblionumber => $biblio->biblionumber,
93
        }
94
    );
95
96
    ok( $available_items->count == 2, "Filtered to 2 available items" );
97
98
    my $item_3 = $builder->build_sample_item(
99
        {
100
            biblionumber => $biblio->biblionumber,
101
            itemlost     => 1,
102
        }
103
    );
104
105
    my $item_4 = $builder->build_sample_item(
106
        {
107
            biblionumber => $biblio->biblionumber,
108
            damaged      => 1,
109
        }
110
    );
111
112
    my $item_5 = $builder->build_sample_item(
113
        {
114
            biblionumber => $biblio->biblionumber,
115
            withdrawn    => 1,
116
        }
117
    );
118
119
    my $item_6 = $builder->build_sample_item(
120
        {
121
            biblionumber => $biblio->biblionumber,
122
            notforloan   => 1,
123
        }
124
    );
125
126
    my $lost_items = Koha::Items->search(
127
        {
128
            _status      => 'lost',
129
            biblionumber => $biblio->biblionumber,
130
        }
131
    );
132
133
    my $damaged_items = Koha::Items->search(
134
        {
135
            _status      => 'damaged',
136
            biblionumber => $biblio->biblionumber,
137
        }
138
    );
139
140
    my $withdrawn_items = Koha::Items->search(
141
        {
142
            _status      => 'withdrawn',
143
            biblionumber => $biblio->biblionumber,
144
        }
145
    );
146
147
    my $notforloan_items = Koha::Items->search(
148
        {
149
            _status      => 'not_for_loan',
150
            biblionumber => $biblio->biblionumber,
151
        }
152
    );
153
154
    ok( $lost_items->count == 1,       "Filtered to 1 lost item" );
155
    ok( $damaged_items->count == 1,    "Filtered to 1 damaged item" );
156
    ok( $withdrawn_items->count == 1,  "Filtered to 1 withdrawn item" );
157
    ok( $notforloan_items->count == 1, "Filtered to 1 notforloan item" );
158
159
    C4::Circulation::AddIssue( $patron, $item_1->barcode );
160
161
    my $checked_out_items = Koha::Items->search(
162
        {
163
            _status      => 'checked_out',
164
            biblionumber => $biblio->biblionumber,
165
        }
166
    );
167
168
    ok( $checked_out_items->count == 1, "Filtered to 1 checked out item" );
169
170
    my $transfer_1 = $builder->build_object(
171
        {
172
            class => 'Koha::Item::Transfers',
173
            value => {
174
                itemnumber => $item_2->itemnumber,
175
                frombranch => $library_1->{branchcode},
176
                tobranch   => $library_2->{branchcode},
177
            }
178
        }
179
    );
180
181
    my $in_transit_items = Koha::Items->search(
182
        {
183
            _status      => 'in_transit',
184
            biblionumber => $biblio->biblionumber,
185
        }
186
    );
187
188
    ok( $in_transit_items->count == 1, "Filtered to 1 in transit item" );
189
190
    my $item_7 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
191
192
    my $hold_1 = $builder->build(
193
        {
194
            source => 'Reserve',
195
            value  => {
196
                itemnumber => $item_7->itemnumber, reservedate => dt_from_string,
197
            }
198
        }
199
    );
200
201
    my $on_hold_items = Koha::Items->search(
202
        {
203
            _status      => 'on_hold',
204
            biblionumber => $biblio->biblionumber,
205
        }
206
    );
207
208
    ok( $on_hold_items->count == 1, "Filtered to 1 on hold item" );
209
210
    my $item_8 = $builder->build_sample_item(
211
        {
212
            biblionumber => $biblio->biblionumber,
213
            restricted   => 1,
214
        }
215
    );
216
217
    my $restricted_items = Koha::Items->search(
218
        {
219
            _status      => 'restricted',
220
            biblionumber => $biblio->biblionumber,
221
        }
222
    );
223
224
    ok( $restricted_items->count == 1, "Filtered to 1 restricted item" );
225
226
    $schema->storage->txn_rollback;
227
};
228
71
subtest 'store' => sub {
229
subtest 'store' => sub {
72
    plan tests => 8;
230
    plan tests => 8;
73
231
Lines 2222-2224 subtest 'filter_by_bookable' => sub { Link Here
2222
2380
2223
    $schema->storage->txn_rollback;
2381
    $schema->storage->txn_rollback;
2224
};
2382
};
2225
- 
2383
2384
subtest 'filter_by_checked_out' => sub {
2385
    plan tests => 4;
2386
2387
    $schema->storage->txn_begin;
2388
2389
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2390
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
2391
2392
    my $biblio = $builder->build_sample_biblio();
2393
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2394
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2395
2396
    is( $biblio->items->filter_by_checked_out->count, 0, "Filtered 0 checked out items" );
2397
2398
    C4::Circulation::AddIssue( $patron, $item_1->barcode );
2399
2400
    is( $biblio->items->filter_by_checked_out->count, 1, "Filtered 1 checked out items" );
2401
2402
    C4::Circulation::AddIssue( $patron, $item_2->barcode );
2403
2404
    is( $biblio->items->filter_by_checked_out->count, 2, "Filtered 2 checked out items" );
2405
2406
    # Do the returns
2407
    C4::Circulation::AddReturn( $item_1->barcode );
2408
    C4::Circulation::AddReturn( $item_2->barcode );
2409
2410
    is( $biblio->items->filter_by_checked_out->count, 0, "Filtered 0 checked out items" );
2411
2412
    $schema->storage->txn_rollback;
2413
2414
};
2415
2416
subtest 'filter_by_in_transit' => sub {
2417
    plan tests => 3;
2418
2419
    $schema->storage->txn_begin;
2420
2421
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2422
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
2423
2424
    my $library_1 = $builder->build( { source => 'Branch' } );
2425
    my $library_2 = $builder->build( { source => 'Branch' } );
2426
2427
    my $biblio = $builder->build_sample_biblio();
2428
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2429
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2430
2431
    is( $biblio->items->filter_by_in_transit->count, 0, "Filtered 0 in transit items" );
2432
2433
    my $transfer_1 = $builder->build_object(
2434
        {
2435
            class => 'Koha::Item::Transfers',
2436
            value => {
2437
                itemnumber => $item_1->itemnumber,
2438
                frombranch => $library_1->{branchcode},
2439
                tobranch   => $library_2->{branchcode},
2440
            }
2441
        }
2442
    );
2443
2444
    is( $biblio->items->filter_by_in_transit->count, 1, "Filtered 1 in transit items" );
2445
2446
    my $transfer_2 = $builder->build_object(
2447
        {
2448
            class => 'Koha::Item::Transfers',
2449
            value => {
2450
                itemnumber => $item_2->itemnumber,
2451
                frombranch => $library_2->{branchcode},
2452
                tobranch   => $library_1->{branchcode},
2453
            }
2454
        }
2455
    );
2456
2457
    is( $biblio->items->filter_by_in_transit->count, 2, "Filtered 2 in transit items" );
2458
2459
    $schema->storage->txn_rollback;
2460
2461
};
2462
2463
subtest 'filter_by_has_holds' => sub {
2464
    plan tests => 3;
2465
2466
    $schema->storage->txn_begin;
2467
2468
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2469
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
2470
2471
    my $library_1 = $builder->build( { source => 'Branch' } );
2472
    my $library_2 = $builder->build( { source => 'Branch' } );
2473
2474
    my $biblio = $builder->build_sample_biblio();
2475
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2476
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2477
2478
    is( $biblio->items->filter_by_has_holds->count, 0, "Filtered to 0 holds" );
2479
2480
    my $hold_1 = $builder->build(
2481
        {
2482
            source => 'Reserve',
2483
            value  => {
2484
                itemnumber => $item_1->itemnumber, reservedate => dt_from_string,
2485
            }
2486
        }
2487
    );
2488
2489
    is( $biblio->items->filter_by_has_holds->count, 1, "Filtered to 1 hold" );
2490
2491
    my $hold_2 = $builder->build(
2492
        {
2493
            source => 'Reserve',
2494
            value  => {
2495
                itemnumber => $item_2->itemnumber, reservedate => dt_from_string,
2496
            }
2497
        }
2498
    );
2499
2500
    is( $biblio->items->filter_by_has_holds->count, 2, "Filtered to 2 holds" );
2501
2502
    $schema->storage->txn_rollback;
2503
2504
};
2505
2506
subtest 'filter_by_in_bundle' => sub {
2507
    plan tests => 3;
2508
2509
    $schema->storage->txn_begin;
2510
2511
    my $library = $builder->build( { source => 'Branch' } );
2512
    my $biblio  = $builder->build_sample_biblio();
2513
2514
    my $item_1 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2515
    my $item_2 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2516
    my $item_3 = $builder->build_sample_item( { biblionumber => $biblio->biblionumber, } );
2517
2518
    is( $biblio->items->filter_by_in_bundle->count, 0, "0 items in a bundle for this record" );
2519
2520
    my $in_bundle = $item_1->in_bundle;
2521
2522
    my $host_item = $builder->build_sample_item();
2523
    $schema->resultset('ItemBundle')->create( { host => $host_item->itemnumber, item => $item_1->itemnumber } );
2524
2525
    $in_bundle = $item_1->in_bundle;
2526
2527
    is( $biblio->items->filter_by_in_bundle->count, 1, "1 item in a bundle for this record" );
2528
    $schema->resultset('ItemBundle')->create( { host => $host_item->itemnumber, item => $item_2->itemnumber } );
2529
2530
    $in_bundle = $item_2->in_bundle;
2531
2532
    is( $biblio->items->filter_by_in_bundle->count, 2, "2 items in a bundle for this record" );
2533
2534
    $schema->storage->txn_rollback;
2535
2536
};
2537
2538
subtest 'filter_by_has_recalls' => sub {
2539
    plan tests => 2;
2540
2541
    $schema->storage->txn_begin;
2542
2543
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
2544
2545
    $biblio = $builder->build_sample_biblio( { author => 'Hall, Daria' } );
2546
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
2547
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
2548
2549
    my $item = $builder->build_sample_item(
2550
        {
2551
            biblionumber => $biblio->biblionumber,
2552
            library      => $library->branchcode,
2553
        }
2554
    );
2555
2556
    C4::Circulation::AddIssue( $patron, $item->barcode );
2557
2558
    is( $biblio->items->filter_by_has_recalls->count, 0, "0 items with recalls on this record" );
2559
2560
    Koha::Recalls->add_recall( { biblio => $item->biblio, item => $item, patron => $patron } );
2561
2562
    is( $biblio->items->filter_by_has_recalls->count, 1, "1 item with recalls on this record" );
2563
2564
    $schema->storage->txn_rollback;
2565
2566
};
2567
2568
subtest 'filter_by_available' => sub {
2569
    plan tests => 6;
2570
2571
    $schema->storage->txn_begin;
2572
2573
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
2574
    my $biblio  = $builder->build_sample_biblio();
2575
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
2576
    t::lib::Mocks::mock_userenv( { branchcode => $patron->branchcode } );
2577
2578
    my $item_1 = $builder->build_sample_item(
2579
        {
2580
            biblionumber => $biblio->biblionumber,
2581
            library      => $library->branchcode,
2582
            itemlost     => 0,
2583
            withdrawn    => 0,
2584
            damaged      => 0,
2585
            notforloan   => 0,
2586
            onloan       => undef,
2587
        }
2588
    );
2589
2590
    my $item_2 = $builder->build_sample_item(
2591
        {
2592
            biblionumber => $biblio->biblionumber,
2593
            library      => $library->branchcode,
2594
            itemlost     => 0,
2595
            withdrawn    => 0,
2596
            damaged      => 0,
2597
            notforloan   => 0,
2598
            onloan       => undef,
2599
        }
2600
    );
2601
2602
    my $item_3 = $builder->build_sample_item(
2603
        {
2604
            biblionumber => $biblio->biblionumber,
2605
            library      => $library->branchcode,
2606
            itemlost     => 0,
2607
            withdrawn    => 0,
2608
            damaged      => 0,
2609
            notforloan   => 0,
2610
            onloan       => undef,
2611
        }
2612
    );
2613
2614
    my $item_4 = $builder->build_sample_item(
2615
        {
2616
            biblionumber => $biblio->biblionumber,
2617
            library      => $library->branchcode,
2618
            itemlost     => 0,
2619
            withdrawn    => 0,
2620
            damaged      => 0,
2621
            notforloan   => 0,
2622
            onloan       => undef,
2623
        }
2624
    );
2625
2626
    my $item_5 = $builder->build_sample_item(
2627
        {
2628
            biblionumber => $biblio->biblionumber,
2629
            library      => $library->branchcode,
2630
            itemlost     => 0,
2631
            withdrawn    => 0,
2632
            damaged      => 0,
2633
            notforloan   => 0,
2634
            onloan       => undef,
2635
        }
2636
    );
2637
2638
    # Create items with varying states
2639
    # Test: Initial available items
2640
    is(
2641
        $biblio->items->filter_by_available->count,
2642
        5,
2643
        "Filtered to 4 available items"
2644
    );
2645
2646
    # Mark item_1 as lost
2647
    $item_1->itemlost(3)->store;
2648
    C4::Circulation::LostItem( $item_1->itemnumber, 1 );
2649
2650
    is(
2651
        $biblio->items->filter_by_available->count,
2652
        4,
2653
        "Filtered to 4 available items, 1 is lost"
2654
    );
2655
2656
    #Mark item_2 as damaged
2657
    $item_2->damaged(1)->store;
2658
2659
    is(
2660
        $biblio->items->filter_by_available->count,
2661
        3,
2662
        "Filtered to 3 available items, 1 is lost, 1 is damaged"
2663
    );
2664
2665
    #Mark item_3 as withdrawn
2666
    $item_3->withdrawn(1)->store;
2667
2668
    is(
2669
        $biblio->items->filter_by_available->count,
2670
        2,
2671
        "Filtered to 2 available items, 1 is lost, 1 is damaged, 1 is withdrawn"
2672
    );
2673
2674
    #Checkout item_4
2675
    C4::Circulation::AddIssue( $patron, $item_4->barcode );
2676
    is(
2677
        $biblio->items->filter_by_available->count,
2678
        1,
2679
        "Filtered to 1 available items, 1 is lost, 1 is damaged, 1 is withdrawn, 1 is checked out"
2680
    );
2681
2682
    #Mark item_5 as notforloan
2683
    $item_5->notforloan(1)->store;
2684
    is(
2685
        $biblio->items->filter_by_available->count,
2686
        0,
2687
        "Filtered to 0 available items, 1 is lost, 1 is damaged, 1 is withdrawn, 1 is checked out, 1 is notforloan"
2688
    );
2689
2690
    $schema->storage->txn_rollback;
2691
};

Return to bug 37334