From ce1243c84c1855145e8bffac283a6c47bf981362 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 | 51 +++++++ api/v1/swagger/definitions/item.yaml | 7 +- api/v1/swagger/paths/biblios.yaml | 3 +- .../tables/items/catalogue_detail.inc | 132 +++++++++--------- 4 files changed, 126 insertions(+), 67 deletions(-) diff --git a/Koha/Item.pm b/Koha/Item.pm index e9501183363..ddc9e68ace7 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -1435,6 +1435,57 @@ 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..4df212b8dbf 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 -additionalProperties: false + _status: + type: + - string + - "null" + description: The status of the item +additionalProperties: false \ No newline at end of file diff --git a/api/v1/swagger/paths/biblios.yaml b/api/v1/swagger/paths/biblios.yaml index 89252ce9f12..c02b5e0d7f5 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 @@ -918,4 +919,4 @@ "503": description: Under maintenance schema: - $ref: "../swagger.yaml#/definitions/error" + $ref: "../swagger.yaml#/definitions/error" \ No newline at end of file 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 9e175e03a55..6cdd5a22db2 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 @@ -234,7 +234,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 %] @@ -472,82 +472,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 ) ) { @@ -562,18 +564,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