From 0133051dc77288c87851764210a600cc2916b531 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 2 Oct 2024 11:02:13 +0200 Subject: [PATCH] Bug 37334: Allow embed _status for GET /biblio/{biblio_id}/items To test; 0. APPLY PATCH, restart_all 1. Go to a record details page (e.g. http://localhost:8081/cgi-bin/koha/catalogue/detail.pl?biblionumber=285) 2. Click the 'Show filters' button above the table 3. Now, at the top of the Status column you should see a dropdown to filter by status 4. You'll have to create several item status to test everything. 5. Add some new items/or change some item status to: DAMAGED, WITHDRAWN, NOTFORLOAN, RESTRICTED, and LOST 6. Test the status filter for each of these statuses, ensuring the work correctly. 7. Create more new items and have some that are checked out, ensure that filter works 8. Put some items into transit and make sure the 'In transit' filter works 9. Put some items on hold, both item and bib level. Make sure the 'On hold' filter works. 10. Turn on the USERecalls system preference and make some recalls, both item and bib level. Make sure the 'Recalled' filter works 11. Create some bundles, make sure the 'In bundle' status works. 12. Also make sure that the available filter is working. Signed-off-by: Lucas Gass Signed-off-by: Emmi Takkinen --- Koha/Item.pm | 50 +++++++ api/v1/swagger/definitions/item.yaml | 5 + api/v1/swagger/paths/biblios.yaml | 1 + .../tables/items/catalogue_detail.inc | 132 +++++++++--------- 4 files changed, 123 insertions(+), 65 deletions(-) diff --git a/Koha/Item.pm b/Koha/Item.pm index 07fc6ad5c8c..f6bc0f6951b 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -1443,6 +1443,56 @@ sub columns_to_str { return $values; } +sub _status { + my ($self) = @_; + + my @statuses; + if ( my $checkout = $self->checkout ) { + unless ( $checkout->onsite_checkout ) { + push @statuses, "checked_out"; + } else { + push @statuses, "local_use"; + } + } elsif ( my $transfer = $self->transfer ) { + if ( $transfer->datesent ) { + push @statuses, "in_transit"; + } else { + push @statuses, "transit_pending"; + } + } + if ( $self->itemlost ) { + push @statuses, 'lost'; + } + if ( $self->withdrawn ) { + push @statuses, 'withdrawn'; + } + if ( $self->damaged ) { + push @statuses, 'damaged'; + } + if ( $self->notforloan || $self->item_type->notforloan ) { + # TODO on a big Koha::Items loop we are going to join with item_type too often, use a cache + push @statuses, 'not_for_loan'; + } + if ( $self->first_hold ) { + push @statuses, 'on_hold'; + } + if ( C4::Context->preference('UseRecalls') && $self->recall ) { + push @statuses, 'recalled'; + } + + unless (@statuses ){ + push @statuses, 'available'; + } + + if ( $self->restricted ) { + push @statuses, 'restricted'; + } + + # TODO in_bundle? + + return join ',', @statuses; +} + =head3 additional_attributes my $attributes = $item->additional_attributes; diff --git a/api/v1/swagger/definitions/item.yaml b/api/v1/swagger/definitions/item.yaml index b254eb2dc34..cf57ec5f678 100644 --- a/api/v1/swagger/definitions/item.yaml +++ b/api/v1/swagger/definitions/item.yaml @@ -322,4 +322,9 @@ properties: - object - "null" description: A return claims object if one exists that's unresolved + _status: + type: + - string + - "null" + description: The status of the item additionalProperties: false diff --git a/api/v1/swagger/paths/biblios.yaml b/api/v1/swagger/paths/biblios.yaml index 89252ce9f12..75da9ba9ccb 100644 --- a/api/v1/swagger/paths/biblios.yaml +++ b/api/v1/swagger/paths/biblios.yaml @@ -458,6 +458,7 @@ type: string enum: - +strings + - _status - home_library - holding_library - biblio.title diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc index c4cff843c00..5c9624cea7d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc @@ -236,7 +236,7 @@ [%# In case or SeparateHoldings we may need to display the number of biblios in each tab %] [%# Do we need separate/new endpoints or do we hack the somewhere client-side? %] let item_table_url = "/api/v1/biblios/[% biblio.biblionumber | uri %]/items?"; - let embed = ["+strings,home_library,holding_library,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"]; + let embed = ["+strings,_status,home_library,holding_library,checkout,checkout.patron,transfer,transfer+strings,first_hold,first_hold+strings,first_hold.patron,first_hold.desk"]; [% IF Koha.Preference('LocalCoverImages') %] embed.push('cover_image_ids'); [% END %] @@ -474,82 +474,84 @@ } }, { - data: "me.lost_status", + data: "me._status", className: "status", searchable: false, // FIXME We are losing the ability to search on the status orderable: false, render: function (data, type, row, meta) { let nodes = ""; - if ( row.checkout ) { - [%# Hacky for patron_to_html in case we simply want to display the patron's library name %] - row.checkout.patron.library = { name: libraries_names.get(row.checkout.patron.library_id) }; + row._status.split(",").forEach( status => { + if ( status == 'checked_out' || status == 'local_use') { + nodes += ''; - nodes += ''; - if ( row.checkout.onsite_checkout ) { + [%# Hacky for patron_to_html in case we simply want to display the patron's library name %] + row.checkout.patron.library = { name: libraries_names.get(row.checkout.patron.library_id) }; let patron_to_html = $patron_to_html(row.checkout.patron, { url: true, display_cardnumber: true, hide_patron_name }); - nodes += _("Currently in local use by %s").format(patron_to_html); - } else { - nodes += ''; - let patron_to_html = $patron_to_html(row.checkout.patron, { url: true, display_cardnumber: true, hide_patron_name }); - nodes += _("Checked out to %s").format(patron_to_html); + + if ( status == 'local_use' ) { + nodes += _("Currently in local use by %s").format(patron_to_html); + } else { + nodes += ''; + nodes += _("Checked out to %s").format(patron_to_html); + } + nodes += ': '; + nodes += _("due %s").format($date(row.checkout.due_date, { as_due_date: true })); + nodes += "" + } - nodes += ': '; - nodes += _("due %s").format($date(row.checkout.due_date, { as_due_date: true })); - nodes += "" - } else if ( row.transfer ) { - if ( row.transfer.datesent ) { + if ( status == 'in_transit' ) { nodes += '%s'.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))); - } else { + } + + if ( status == 'transit_pending' ) { nodes += '%s'.format(_("Transit pending 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.daterequested))); } - } - if ( row.lost_status ) { - let lost_lib = av_lost.get(row.lost_status.toString()) || _("Unavailable (lost or missing"); - nodes += '%s'.format(escape_str(lost_lib)); - [% IF Koha.Preference('ClaimReturnedLostValue') %] - const hasReturnClaims = row.return_claims.filter(rc => !rc.resolution).length > 0 ? true : false + if ( status == 'lost' ) { + let lost_lib = av_lost.get(row.lost_status.toString()) || _("Unavailable (lost or missing"); + nodes += '%s'.format(escape_str(lost_lib)); + + const hasReturnClaims = row.return_claims && row.return_claims.filter(rc => !rc.resolution).length > 0 ? true : false if(hasReturnClaims) { nodes += '' + _("(Claimed returned)") + ''; } - [% END %] - } + } - if ( row.withdrawn ) { - let withdrawn_lib = av_withdrawn.get(row.withdrawn.toString()) || _("Withdrawn"); - nodes += '%s'.format(escape_str(withdrawn_lib)); - } + if ( status == 'withdrawn' ) { + let withdrawn_lib = av_withdrawn.get(row.withdrawn.toString()) || _("Withdrawn"); + nodes += '%s'.format(escape_str(withdrawn_lib)); + } - if ( row.damaged_status ) { - let damaged_lib = av_damaged.get(row.damaged_status.toString()) || _("Damaged"); - nodes += '%s'.format(escape_str(damaged_lib)); - } + if ( status == 'damaged' ) { + let damaged_lib = av_damaged.get(row.damaged_status.toString()) || _("Damaged"); + nodes += '%s'.format(escape_str(damaged_lib)); + } - if ( row.not_for_loan_status || item_types_notforloan.get(row.item_type_id) ) { - let not_for_loan_lib = av_not_for_loan.get(row.not_for_loan_status.toString()); - nodes += '%s'.format(_("Not for loan")) + ( not_for_loan_lib ? ' (%s)'.format(escape_str(not_for_loan_lib)) : '' ) + ''; - } + if ( status == 'not_for_loan' ) { + let not_for_loan_lib = av_not_for_loan.get(row.not_for_loan_status.toString()); + nodes += '%s'.format(_("Not for loan")) + ( not_for_loan_lib ? ' (%s)'.format(escape_str(not_for_loan_lib)) : '' ) + ''; + } - if ( row.first_hold ) { - if ( row.first_hold.waiting_date ) { - if ( row.first_hold.desk ) { - nodes += '%s'.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))); + if ( status == 'on_hold') { + if ( row.first_hold.waiting_date ) { + if ( row.first_hold.desk ) { + nodes += '%s'.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)))); + } else { + nodes += '%s'.format(_("Waiting at %s since %s.".format(row.first_hold._strings.pickup_library_id.str, $date(row.first_hold.waiting_date)))); + } + [% IF Koha.Preference('canreservefromotherbranches') %] + if ( row.first_hold.waiting_date || row.first_hold.priority == 1 ) { + [%# Hacky for patron_to_html in case we simply want to display the patron's library name %] + row.first_hold.patron.library = { name: libraries_names.get(row.first_hold.patron.library_id) }; + + let patron_to_html = $patron_to_html(row.first_hold.patron, { url: true, display_cardnumber: true, hide_patron_name }); + nodes += ' %s'.format(_("Hold for: %s").format(patron_to_html)); + } + [% END %] } else { - nodes += '%s'.format(_("Waiting at %s since %s.").format(row.first_hold._strings.pickup_library_id.str, $date(row.first_hold.waiting_date))); + nodes += ' %s'.format(_("There is an item level hold on this item (priority = %s).").format(row.first_hold.priority)); } - [% IF Koha.Preference('canreservefromotherbranches') %] - if ( row.first_hold.waiting_date || row.first_hold.priority == 1 ) { - [%# Hacky for patron_to_html in case we simply want to display the patron's library name %] - row.first_hold.patron.library = { name: libraries_names.get(row.first_hold.patron.library_id) }; - - let patron_to_html = $patron_to_html(row.first_hold.patron, { url: true, display_cardnumber: true, hide_patron_name }); - nodes += ' %s'.format(_("Hold for: %s").format(patron_to_html)); - } - [% END %] - } else { - nodes += ' %s'.format(_("There is an item level hold on this item (priority = %s).").format(row.first_hold.priority)); } - } [% IF Koha.Preference('UseRecalls') %] if ( row.recall && ( row.item_id === row.recall.item_id ) ) { @@ -564,18 +566,18 @@ } } [% END %] + if ( status == 'available' ) { + nodes += ' %s'.format(_("Available")) + } - 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 ) ) )) { - nodes += ' %s'.format(_("Available")) - } - - if ( row.restricted_status ) { - nodes += '(%s)'.format(escape_str(av_restricted.get(row.restricted_status.toString()))); - } + if ( status == 'restricted') { + nodes += '(%s)'.format(escape_str(av_restricted.get(row.restricted_status.toString()))); + } - if ( row.in_bundle ) { - nodes += '%s'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true }))); - } + if ( status == 'restricted') { + nodes += '%s'.format(_("In bundle: %s").format($biblio_to_html(row.bundle_host.biblio, { link: true }))); + } + }); return nodes; } }, -- 2.39.5