From a5b2f299b87e0e7d2d8ba136acb4ae7148190487 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 23 Sep 2021 08:28:15 +0100 Subject: [PATCH] Bug 28854: Squashed Patch Set for Reading Bug 28854: Database update Signed-off-by: Katrin Fischer Bug 28854: DBIC Schema Updates Signed-off-by: Katrin Fischer Bug 28854: Expose functionality to attach items to bundles This patch adds methods the the Koha::Item object for managing item bundling operations and then exposes those methods via the REST API. We include the new `BundleNotLoanValue` preference for setting not for loan values when an item is added to a bundle. Finally, we expose bundle management via the catalogue details page. Test plan: 0) Apply patches up to this point and run the database update 1) Configuration: `BundleNotLoanValue` should have been set by the database update and point to a newly added AV value. 2) Creating a new bundle * Add a new bib record * Mark the bib record as a 'collection' type by setting leader position 7 to 'c' * Add a new item to this bib record * You should see a new 'Manage bundle' button available in the 'Actions' column of the Holdings table. * Clicking 'Manage bundle' should expand the table to include a new row directly beneath this one. * Use the new 'Add to bundle' button that appears in this row to trigger a modal that allows entering the barcode of items you wish to add to the bundle * Upon closing the modal, the bundle content table should reload and contain your newly associated items. * You can subsequently remove an item from a bundle using the new 'Remove' button. 3) Not for loan * Items that have been added into a bundle should now appear as 'Not for loan' from their original biblio record and note which bundle they belong to. 4) Error cases * Try adding an item that already belongs to a bundle to another bundle: Note an error is displayed in the modal form. 5) The bundles feature can be disabled by unsetting the `BundleNotLoanValue` system preference. Signed-off-by: Katrin Fischer Bug 28854: Update circulation functionality for bundles This patch updates the circulation system to account for bundle checkins. We add a content verification step to ensure bundle content is all present at checkin and we use this comparison to mark missing items as lost. Test plan 0) Apply patches up to this point 1) Checkin an item that belongs to a bundle * An alert should be triggered noting that the item belongs to a bundle * The option to remove the item from the bundle should be clear * Click remove should result in the alert dissapearing and the item having been removed from the bundle. 2) Checkin an item bundle * A modal confirmation dialog should appear requesting each item barcode be scanned * As items are scanned they should be highlighted in yellow in the bundle content table * Upon submission; * The user will be alerted to any unexpected items that were scanned and told to put them to one side. * The user will be alerted that any missing items in the validation will have been marked as lost. * The bundle item will be marked as checked in. Signed-off-by: Katrin Fischer Bug 28854: Highlight bundle rows to clarify UI Signed-off-by: Katrin Fischer Bug 28854: Drop unique index on issue_id in return claims With the introduction of circulating items within a bundle set, we can now loose multiple items from the bundle from the same issue. Signed-off-by: Katrin Fischer Bug 28854: DBIC Update ReturnClaim Schema Signed-off-by: Katrin Fischer Bug 28854: Record and display who lost the item This patch records the bundle issue from which an item is marked as lost so that we may use that to infer who lost the item (for later charges and display). Test plan 0) Apply all patches up to this point 1) Checkout a bundle to a user 2) Checkin the bundle and do not scan one of the barcodes at confirmation * Note that the item not scanned is marked as lost 3) Navigate to the biblio for the lost item and note that it is marked as lost. 4) Navigate to the biblio for the collection and expand the collection item that contains the lost item. Note the item is marked as lost and checkout details are listed. 5) Checkin the lost item * The item should be marked as found and the return_claims line should be marked as resolved. Signed-off-by: Katrin Fischer Bug 28854: Simplified status handling for bundle inventory check Signed-off-by: Katrin Fischer Bug 28854: Add option to print content list after verification We already allowed the user to view and print an updated content list after a varification showed that items were missing from the bundle. This patch adds the option to view and print the list even if the content has been varified to have not changed. Signed-off-by: Katrin Fischer Bug 28854: Add ordering to checkin validation modal This enhancement adds simple dataTable ordering to the verification modal table at bundle checkin time. Signed-off-by: Katrin Fischer Bug 28854: Improve lost details display for bundle items This patch adds the return claim details to the bundle item status display on the catalogue details page. Signed-off-by: Katrin Fischer Bug 28854: Enable claims return view when BundleLostValue is set This patch adds the alternation of BundleLostValue to enable the return claims functionality when Bundles are enbled. Signed-off-by: Katrin Fischer Bug 28854: Highlight when a scanned item is unexpected Signed-off-by: Katrin Fischer Bug 28854: Add count of scanned items to verification modal Signed-off-by: Katrin Fischer Bug 28854: Add 'Remove from bundle' dialogue to details display This patch adds the counterpart of the 'Add to bundle' modal prompt for adding items to bundles from the catalogue details display. It allows for scanning barcodes to remove them from a bundle. Signed-off-by: Katrin Fischer Bug 28854: Add modal after checkin to print missing items list This patch adds a further modal to the post checkin alert to allow the user to print a view and print a list of items that went missing at this checkin to allow for replacements to be picked. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Only count rows in the body This patch fixes the count to only include table rows in the body, i.e. skip the header row. Signed-off-by: Katrin Fischer Bug 28854: Make barcode comparison case insensative Signed-off-by: Katrin Fischer Bug 28854: (QA follow-up) Fix QA tool complaints * Typo in code comment * Permissions on atomicupdate * Missing TT filter in template * Spelling in template Signed-off-by: Martin Renvoize Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Use Koha::Item->itemtype introduced with bug 20469 Signed-off-by: Katrin Fischer Bug 28854: Unit test for Koha::Item additions This patch adds unit tests for the new methods added to Koha::Item including: * return_claim * return_claims * is_bundle * in_bundle * bundle_host * bundle_items * add_to_bundle * remove_from_bundle Test plan 1) Run t/db_dependent/Koha/Item.t Signed-off-by: Katrin Fischer Bug 28854: Unit test for AddReturn addition This patch adds tests for the AddReturn change that adds an 'InBundle' message to the return. Signed-off-by: Katrin Fischer Bug 28854: Unit tests - chargelostitem This patch adds unit tests for the changes to chargelositem to ensure bundle charging works as expected setting the correct issue_id. Signed-off-by: Katrin Fischer Bug 28854: Add default AV and Syspref values This patch adds the new AV and System Preferences to the installer. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Translations fixes This patch wraps and unwraps strings appropriately for trasnlation. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Rename column configuration to barcode This patch updates the column configuration from 'external_id' to 'barcode' Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Move preferences to "Item bundles" Move the preferences into a section under 'Circulation' called 'Item bundles' Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Fix typo in templates and test plan * aready > already * 61 > 62 Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Improve error for item not found Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Use barcodedecode in Koha::REST::V1::Items This removes any leading or trailing whitespace from the external id passed Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Disable bundle editing for checked out This patch disables the add and remove options under bundle management when the bundled item in question is checked out. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Link bundle items to thier biblios This patch splits out the title formatting from the bundle item title display into it's own new js-biblio-format include file and adds biblio linking options. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Restore last seen date formatting We lost the date formatting somewhere along the line with rebases, this patch restores the call to $date for last seen date. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Add bundle item status to manage button This patch adds the item counts (Present|Lost) to the 'Manage bundle' button in the catalogue details page. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Make 'Manage bundle' button a 'toggle' This patch adds and removes the 'active' class to the 'Manage bundle' button such that it correctly reflects whether the manage area is expanded or not for the bundle item. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Restore block styling on bundled span This patch restores a css rule that we lost during rebases that makes the 'bundled' span that appears for bundled items on their respective details pages so that the 'In bundle: ' text in the status field of the table appears beneath the 'Not for loan (Added to bundle)' text. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Link to 'In bundle' on OPAC This patch adds the 'In bundle: Link' to the OPAC to mirror what we have on the staff client. Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Add handling for part_numbers/names This patch adds part number/name handling to the new biblio-format js include. This will add such details to bundle management tables. Signed-off-by: Katrin Fischer Bug 28854: (QA follow-up) Add missing TT filters Signed-off-by: Katrin Fischer Bug 28854: (follow-up) Remove fields from from bundle items This patch removes the itemtype and collection code fields from the bundle items table. We will add the itemtype back in at a later date with a new bug as the display code needs some careful consideration and we felt that doing that work inline here would be an unrequired blocker. Signed-off-by: Katrin Fischer --- C4/Accounts.pm | 7 +- C4/Circulation.pm | 6 + Koha/Checkouts/ReturnClaims.pm | 2 +- Koha/Item.pm | 191 ++++++++- Koha/REST/V1/Items.pm | 124 ++++++ Koha/Schema/Result/Item.pm | 30 ++ Koha/Schema/Result/ItemBundle.pm | 113 ++++++ Koha/Schema/Result/ReturnClaim.pm | 18 +- admin/columns_settings.yml | 18 + api/v1/swagger/definitions/bundle_link.yaml | 14 + api/v1/swagger/definitions/item.yaml | 9 + api/v1/swagger/paths/items.yaml | 160 ++++++++ api/v1/swagger/swagger.yaml | 6 + catalogue/detail.pl | 18 + circ/returns.pl | 98 ++++- .../data/mysql/atomicupdate/bug_28854.pl | 55 +++ installer/data/mysql/en/optional/auth_val.yml | 8 + installer/data/mysql/kohastructure.sql | 18 +- installer/data/mysql/mandatory/sysprefs.sql | 2 + .../intranet-tmpl/prog/css/src/_tables.scss | 2 +- .../prog/css/src/staff-global.scss | 14 + .../prog/en/includes/html_helpers.inc | 2 + .../prog/en/includes/js-biblio-format.inc | 64 +++ .../en/includes/modals/bundle_contents.inc | 35 ++ .../admin/preferences/circulation.pref | 11 + .../prog/en/modules/catalogue/detail.tt | 370 +++++++++++++++++- .../prog/en/modules/circ/circulation.tt | 8 +- .../prog/en/modules/circ/returns.tt | 242 ++++++++++++ .../prog/en/modules/members/moremember.tt | 2 +- .../bootstrap/en/includes/item-status.inc | 4 + opac/opac-detail.pl | 5 + t/db_dependent/Accounts.t | 16 +- t/db_dependent/Circulation.t | 19 + t/db_dependent/Koha/Item.t | 195 ++++++++- 34 files changed, 1854 insertions(+), 32 deletions(-) create mode 100644 Koha/Schema/Result/ItemBundle.pm create mode 100644 api/v1/swagger/definitions/bundle_link.yaml create mode 100755 installer/data/mysql/atomicupdate/bug_28854.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/js-biblio-format.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc diff --git a/C4/Accounts.pm b/C4/Accounts.pm index ad0d34f184..e5b7c441d8 100644 --- a/C4/Accounts.pm +++ b/C4/Accounts.pm @@ -70,7 +70,8 @@ FIXME : if no replacement price, borrower just doesn't get charged? sub chargelostitem { my $dbh = C4::Context->dbh(); my ($borrowernumber, $itemnumber, $amount, $description) = @_; - my $itype = Koha::ItemTypes->find({ itemtype => Koha::Items->find($itemnumber)->effective_itemtype() }); + my $item = Koha::Items->find($itemnumber); + my $itype = $item->itemtype; my $replacementprice = $amount; my $defaultreplacecost = $itype->defaultreplacecost; my $processfee = $itype->processfee; @@ -80,6 +81,10 @@ sub chargelostitem { $replacementprice = $defaultreplacecost; } my $checkout = Koha::Checkouts->find({ itemnumber => $itemnumber }); + if ( !$checkout && $item->in_bundle ) { + my $host = $item->bundle_host; + $checkout = $host->checkout; + } my $issue_id = $checkout ? $checkout->issue_id : undef; my $account = Koha::Account->new({ patron_id => $borrowernumber }); diff --git a/C4/Circulation.pm b/C4/Circulation.pm index a10503385f..a18864dea7 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -2433,6 +2433,12 @@ sub AddReturn { } } + # Check for bundle status + if ( $item->in_bundle ) { + my $host = $item->bundle_host; + $messages->{InBundle} = $host; + } + my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX }); $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" ); diff --git a/Koha/Checkouts/ReturnClaims.pm b/Koha/Checkouts/ReturnClaims.pm index c1c2b3bd44..1174cdd1a2 100644 --- a/Koha/Checkouts/ReturnClaims.pm +++ b/Koha/Checkouts/ReturnClaims.pm @@ -60,7 +60,7 @@ sub resolved { return Koha::Checkouts::ReturnClaims->_new_from_dbic( $results ); } -=head3 type +=head3 _type =cut diff --git a/Koha/Item.pm b/Koha/Item.pm index 38b266655b..fecdf5ddcf 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -20,6 +20,7 @@ package Koha::Item; use Modern::Perl; use List::MoreUtils qw( any ); +use Try::Tiny qw( catch try ); use Koha::Database; use Koha::DateUtils qw( dt_from_string output_pref ); @@ -440,6 +441,37 @@ sub item_group { return $item_group; } +=head3 return_claims + + my $return_claims = $item->return_claims; + +Return any return_claims associated with this item + +=cut + +sub return_claims { + my ( $self, $params, $attrs ) = @_; + my $claims_rs = $self->_result->return_claims->search($params, $attrs); + return Koha::Checkouts::ReturnClaims->_new_from_dbic( $claims_rs ); +} + +=head3 return_claim + + my $return_claim = $item->return_claim; + +Returns the most recent unresolved return_claims associated with this item + +=cut + +sub return_claim { + my ($self) = @_; + my $claims_rs = + $self->_result->return_claims->search( { resolution => undef }, + { order_by => { '-desc' => 'created_on' }, rows => 1 } )->single; + return unless $claims_rs; + return Koha::Checkouts::ReturnClaim->_new_from_dbic($claims_rs); +} + =head3 holds my $holds = $item->holds(); @@ -1113,7 +1145,7 @@ Internal function, not exported, called only by Koha::Item->store. sub _set_found_trigger { my ( $self, $pre_mod_item ) = @_; - ## If item was lost, it has now been found, reverse any list item charges if necessary. + # Reverse any lost item charges if necessary. my $no_refund_after_days = C4::Context->preference('NoRefundOnLostReturnedItemsAge'); if ($no_refund_after_days) { @@ -1465,6 +1497,163 @@ sub move_to_biblio { return $to_biblionumber; } +=head3 bundle_items + + my $bundle_items = $item->bundle_items; + +Returns the items associated with this bundle + +=cut + +sub bundle_items { + my ($self) = @_; + + if ( !$self->{_bundle_items_cached} ) { + my $bundle_items = Koha::Items->search( + { 'item_bundles_item.host' => $self->itemnumber }, + { join => 'item_bundles_item' } ); + $self->{_bundle_items} = $bundle_items; + $self->{_bundle_items_cached} = 1; + } + + return $self->{_bundle_items}; +} + +=head3 is_bundle + + my $is_bundle = $item->is_bundle; + +Returns whether the item is a bundle or not + +=cut + +sub is_bundle { + my ($self) = @_; + return $self->bundle_items->count ? 1 : 0; +} + +=head3 bundle_host + + my $bundle = $item->bundle_host; + +Returns the bundle item this item is attached to + +=cut + +sub bundle_host { + my ($self) = @_; + + my $bundle_items_rs = $self->_result->item_bundles_item; + return unless $bundle_items_rs; + return Koha::Item->_new_from_dbic($bundle_items_rs->host); +} + +=head3 in_bundle + + my $in_bundle = $item->in_bundle; + +Returns whether this item is currently in a bundle + +=cut + +sub in_bundle { + my ($self) = @_; + return $self->bundle_host ? 1 : 0; +} + +=head3 add_to_bundle + + my $link = $item->add_to_bundle($bundle_item); + +Adds the bundle_item passed to this item + +=cut + +sub add_to_bundle { + my ( $self, $bundle_item ) = @_; + + my $schema = Koha::Database->new->schema; + + my $BundleNotLoanValue = C4::Context->preference('BundleNotLoanValue'); + + try { + $schema->txn_do( + sub { + $self->_result->add_to_item_bundles_hosts( + { item => $bundle_item->itemnumber } ); + + $bundle_item->notforloan($BundleNotLoanValue)->store(); + } + ); + } + catch { + + # FIXME: See if we can move the below copy/paste from Koha::Object::store into it's own class and catch at a lower level in the Schema instantiation, take inspiration from DBIx::Error + if ( ref($_) eq 'DBIx::Class::Exception' ) { + warn $_->{msg}; + if ( $_->{msg} =~ /Cannot add or update a child row: a foreign key constraint fails/ ) { + # FK constraints + # FIXME: MySQL error, if we support more DB engines we should implement this for each + if ( $_->{msg} =~ /FOREIGN KEY \(`(?.*?)`\)/ ) { + Koha::Exceptions::Object::FKConstraint->throw( + error => 'Broken FK constraint', + broken_fk => $+{column} + ); + } + } + elsif ( + $_->{msg} =~ /Duplicate entry '(.*?)' for key '(?.*?)'/ ) + { + Koha::Exceptions::Object::DuplicateID->throw( + error => 'Duplicate ID', + duplicate_id => $+{key} + ); + } + elsif ( $_->{msg} =~ +/Incorrect (?\w+) value: '(?.*)' for column \W?(?\S+)/ + ) + { # The optional \W in the regex might be a quote or backtick + my $type = $+{type}; + my $value = $+{value}; + my $property = $+{property}; + $property =~ s/['`]//g; + Koha::Exceptions::Object::BadValue->throw( + type => $type, + value => $value, + property => $property =~ /(\w+\.\w+)$/ + ? $1 + : $property + , # results in table.column without quotes or backtics + ); + } + + # Catch-all for foreign key breakages. It will help find other use cases + $_->rethrow(); + } + else { + $_; + } + }; +} + +=head3 remove_from_bundle + +Remove this item from any bundle it may have been attached to. + +=cut + +sub remove_from_bundle { + my ($self) = @_; + + my $bundle_item_rs = $self->_result->item_bundles_item; + if ( $bundle_item_rs ) { + $bundle_item_rs->delete; + $self->notforloan(0)->store(); + return 1; + } + return 0; +} + =head2 Internal methods =head3 _after_item_action_hooks diff --git a/Koha/REST/V1/Items.pm b/Koha/REST/V1/Items.pm index d3c5d7e177..88a0675257 100644 --- a/Koha/REST/V1/Items.pm +++ b/Koha/REST/V1/Items.pm @@ -19,6 +19,8 @@ use Modern::Perl; use Mojo::Base 'Mojolicious::Controller'; +use C4::Circulation qw( barcodedecode ); + use Koha::Items; use List::MoreUtils qw( any ); @@ -148,4 +150,126 @@ sub pickup_locations { }; } +=head3 bundled_items + +Controller function that handles bundled_items Koha::Item objects + +=cut + +sub bundled_items { + my $c = shift->openapi->valid_input or return; + + my $item_id = $c->validation->param('item_id'); + my $item = Koha::Items->find( $item_id ); + + unless ($item) { + return $c->render( + status => 404, + openapi => { error => "Item not found" } + ); + } + + return try { + my $items_set = $item->bundle_items; + my $items = $c->objects->search( $items_set ); + return $c->render( + status => 200, + openapi => $items + ); + } + catch { + $c->unhandled_exception($_); + }; +} + +=head3 add_to_bundle + +Controller function that handles adding items to this bundle + +=cut + +sub add_to_bundle { + my $c = shift->openapi->valid_input or return; + + my $item_id = $c->validation->param('item_id'); + my $item = Koha::Items->find( $item_id ); + + unless ($item) { + return $c->render( + status => 404, + openapi => { error => "Item not found" } + ); + } + + my $bundle_item_id = $c->validation->param('body')->{'external_id'}; + $bundle_item_id = barcodedecode($bundle_item_id); + my $bundle_item = Koha::Items->find( { barcode => $bundle_item_id } ); + + unless ($bundle_item) { + return $c->render( + status => 404, + openapi => { error => "Bundle item not found" } + ); + } + + return try { + my $link = $item->add_to_bundle($bundle_item); + return $c->render( + status => 201, + openapi => $bundle_item + ); + } + catch { + if ( ref($_) eq 'Koha::Exceptions::Object::DuplicateID' ) { + return $c->render( + status => 409, + openapi => { + error => 'Item is already bundled', + key => $_->duplicate_id + } + ); + } + else { + $c->unhandled_exception($_); + } + }; +} + +=head3 remove_from_bundle + +Controller function that handles removing items from this bundle + +=cut + +sub remove_from_bundle { + my $c = shift->openapi->valid_input or return; + + my $item_id = $c->validation->param('item_id'); + my $item = Koha::Items->find( $item_id ); + + unless ($item) { + return $c->render( + status => 404, + openapi => { error => "Item not found" } + ); + } + + my $bundle_item_id = $c->validation->param('bundled_item_id'); + $bundle_item_id = barcodedecode($bundle_item_id); + my $bundle_item = Koha::Items->find( { itemnumber => $bundle_item_id } ); + + unless ($bundle_item) { + return $c->render( + status => 404, + openapi => { error => "Bundle item not found" } + ); + } + + $bundle_item->remove_from_bundle; + return $c->render( + status => 204, + openapi => q{} + ); +} + 1; diff --git a/Koha/Schema/Result/Item.pm b/Koha/Schema/Result/Item.pm index 01ebbe3636..55a1235096 100644 --- a/Koha/Schema/Result/Item.pm +++ b/Koha/Schema/Result/Item.pm @@ -744,6 +744,36 @@ __PACKAGE__->might_have( { cascade_copy => 0, cascade_delete => 0 }, ); +=head2 item_bundles_hosts + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "item_bundles_hosts", + "Koha::Schema::Result::ItemBundle", + { "foreign.host" => "self.itemnumber" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +=head2 item_bundles_item + +Type: might_have + +Related object: L + +=cut + +__PACKAGE__->might_have( + "item_bundles_item", + "Koha::Schema::Result::ItemBundle", + { "foreign.item" => "self.itemnumber" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 items_last_borrower Type: might_have diff --git a/Koha/Schema/Result/ItemBundle.pm b/Koha/Schema/Result/ItemBundle.pm new file mode 100644 index 0000000000..4f89ee3294 --- /dev/null +++ b/Koha/Schema/Result/ItemBundle.pm @@ -0,0 +1,113 @@ +use utf8; +package Koha::Schema::Result::ItemBundle; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +Koha::Schema::Result::ItemBundle + +=cut + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("item_bundles"); + +=head1 ACCESSORS + +=head2 item + + data_type: 'integer' + is_foreign_key: 1 + is_nullable: 0 + +=head2 host + + data_type: 'integer' + is_foreign_key: 1 + is_nullable: 0 + +=cut + +__PACKAGE__->add_columns( + "item", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, + "host", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("host", "item"); + +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("item_bundles_uniq_1", ["item"]); + +=head1 RELATIONS + +=head2 host + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "host", + "Koha::Schema::Result::Item", + { itemnumber => "host" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, +); + +=head2 item + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "item", + "Koha::Schema::Result::Item", + { itemnumber => "item" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" }, +); + + +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-03-31 13:56:51 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T02Qk/Ojl7OXlL62uQk6Og + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/Koha/Schema/Result/ReturnClaim.pm b/Koha/Schema/Result/ReturnClaim.pm index 9e67c7f0e7..6256e5da1f 100644 --- a/Koha/Schema/Result/ReturnClaim.pm +++ b/Koha/Schema/Result/ReturnClaim.pm @@ -170,20 +170,6 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -=head1 UNIQUE CONSTRAINTS - -=head2 C - -=over 4 - -=item * L - -=back - -=cut - -__PACKAGE__->add_unique_constraint("issue_id", ["issue_id"]); - =head1 RELATIONS =head2 borrowernumber @@ -277,8 +263,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-11-17 10:01:24 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Ik93SD3kLNecIyRgsBVKDQ +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-11-18 15:07:03 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:HtOvm4y611GNQcPwKzY1jg =head2 checkout diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml index 7e68c4d0b5..3f146593e6 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -525,6 +525,24 @@ modules: - columnname: checkin_on + bundle_tables: + columns: + - + columnname: title + cannot_be_toggled: 1 + - + columnname: author + - + columnname: callnumber + - + columnname: barcode + - + columnname: status + - + columnname: bundle_actions + cannot_be_toggled: 1 + cannot_be_modified: 1 + cataloguing: addbooks: reservoir-table: diff --git a/api/v1/swagger/definitions/bundle_link.yaml b/api/v1/swagger/definitions/bundle_link.yaml new file mode 100644 index 0000000000..572be83d8e --- /dev/null +++ b/api/v1/swagger/definitions/bundle_link.yaml @@ -0,0 +1,14 @@ +--- +type: object +properties: + item_id: + type: + - integer + - "null" + description: Internal item identifier + external_id: + type: + - string + - "null" + description: Item barcode +additionalProperties: false diff --git a/api/v1/swagger/definitions/item.yaml b/api/v1/swagger/definitions/item.yaml index 8b00c96c88..22df7d4aad 100644 --- a/api/v1/swagger/definitions/item.yaml +++ b/api/v1/swagger/definitions/item.yaml @@ -7,6 +7,7 @@ properties: biblio_id: type: integer description: Internal identifier for the parent bibliographic record + biblio: {} external_id: type: - string @@ -211,6 +212,14 @@ properties: exclude_from_local_holds_priority: type: boolean description: Exclude this item from local holds priority. + return_claims: + type: array + description: An array of all return claims associated with this item + return_claim: + type: + - object + - "null" + description: A return claims object if one exists that's unresolved additionalProperties: false required: - item_id diff --git a/api/v1/swagger/paths/items.yaml b/api/v1/swagger/paths/items.yaml index 66fc1ee6a8..7c947f6e3f 100644 --- a/api/v1/swagger/paths/items.yaml +++ b/api/v1/swagger/paths/items.yaml @@ -93,6 +93,166 @@ x-koha-authorization: permissions: catalogue: "1" +"/items/{item_id}/bundled_items": + post: + x-mojo-to: Items#add_to_bundle + operationId: addToBundle + tags: + - items + summary: Add item to bundle + parameters: + - $ref: "../swagger.yaml#/parameters/item_id_pp" + - name: body + in: body + description: A JSON object containing information about the new bundle link + required: true + schema: + $ref: "../swagger.yaml#/definitions/bundle_link" + consumes: + - application/json + produces: + - application/json + responses: + "201": + description: A successfully created bundle link + schema: + items: + $ref: "../swagger.yaml#/definitions/item" + "400": + description: Bad parameter + schema: + $ref: "../swagger.yaml#/definitions/error" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "404": + description: Resource not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "409": + description: Conflict in creating resource + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: Internal server error + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + catalogue: 1 + get: + x-mojo-to: Items#bundled_items + operationId: bundledItems + tags: + - items + summary: List bundled items + parameters: + - $ref: "../swagger.yaml#/parameters/item_id_pp" + - name: external_id + in: query + description: Search on the item's barcode + required: false + type: string + - $ref: "../swagger.yaml#/parameters/match" + - $ref: "../swagger.yaml#/parameters/order_by" + - $ref: "../swagger.yaml#/parameters/page" + - $ref: "../swagger.yaml#/parameters/per_page" + - $ref: "../swagger.yaml#/parameters/q_param" + - $ref: "../swagger.yaml#/parameters/q_body" + - $ref: "../swagger.yaml#/parameters/q_header" + consumes: + - application/json + produces: + - application/json + responses: + "200": + description: A list of item + schema: + type: array + items: + $ref: "../swagger.yaml#/definitions/item" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: Internal server error + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + catalogue: "1" + x-koha-embed: + - biblio + - checkout + - return_claims + - return_claim + - return_claim.patron +"/items/{item_id}/bundled_items/{bundled_item_id}": + delete: + x-mojo-to: Items#remove_from_bundle + operationId: removeFromBundle + tags: + - items + summary: Remove item from bundle + parameters: + - $ref: "../swagger.yaml#/parameters/item_id_pp" + - name: bundled_item_id + in: path + description: Internal identifier for the bundled item + required: true + type: string + consumes: + - application/json + produces: + - application/json + responses: + "204": + description: Bundle link deleted + "400": + description: Bad parameter + schema: + $ref: "../swagger.yaml#/definitions/error" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "404": + description: Resource not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: Internal server error + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + catalogue: 1 "/items/{item_id}/pickup_locations": get: x-mojo-to: Items#pickup_locations diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index dbb0c20881..7e3a8d8afc 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -10,6 +10,8 @@ definitions: $ref: ./definitions/allows_renewal.yaml basket: $ref: ./definitions/basket.yaml + bundle_link: + $ref: ./definitions/bundle_link.yaml cashup: $ref: ./definitions/cashup.yaml checkout: @@ -169,6 +171,10 @@ paths: $ref: ./paths/items.yaml#/~1items "/items/{item_id}": $ref: "./paths/items.yaml#/~1items~1{item_id}" + "/items/{item_id}/bundled_items": + $ref: ./paths/items.yaml#/~1items~1{item_id}~1bundled_items + "/items/{item_id}/bundled_items/{bundled_item_id}": + $ref: ./paths/items.yaml#/~1items~1{item_id}~1bundled_items~1{bundled_item_id} "/items/{item_id}/pickup_locations": $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations" /libraries: diff --git a/catalogue/detail.pl b/catalogue/detail.pl index 34447566b5..22a60909cb 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -206,6 +206,11 @@ if (@hostitems){ my $dat = &GetBiblioData($biblionumber); +#is biblio a collection and are bundles enabled +my $leader = $record->leader(); +$dat->{bundlesEnabled} = ( ( substr( $leader, 7, 1 ) eq 'c' ) + && C4::Context->preference('BundleNotLoanValue') ) ? 1 : 0; + #coping with subscriptions my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber); my @subscriptions = SearchSubscriptions({ biblionumber => $biblionumber, orderby => 'title' }); @@ -451,6 +456,19 @@ foreach my $item (@items) { } } + if ( $item_object->is_bundle ) { + $item->{bundled} = + $item_object->bundle_items->search( { itemlost => { '!=' => 0 } } ) + ->count; + $item->{bundled_lost} = + $item_object->bundle_items->search( { itemlost => 0 } )->count; + $item->{is_bundle} = 1; + } + + if ($item_object->in_bundle) { + $item->{bundle_host} = $item_object->bundle_host; + } + if ($currentbranch and C4::Context->preference('SeparateHoldings')) { if ($itembranchcode and $itembranchcode eq $currentbranch) { push @itemloop, $item; diff --git a/circ/returns.pl b/circ/returns.pl index 340efb4779..7399568cf5 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -338,10 +338,18 @@ if ($barcode) { $template->param( 'multiple_confirmed' => 1 ) if $query->param('multiple_confirm'); + # Block return if bundle and confirm has not been received + my $bundle_confirm = + $item + && $item->is_bundle + && !$query->param('confirm_items_bundle_return'); + $template->param( 'confirm_items_bundle_returned' => 1 ) + if $query->param('confirm_items_bundle_return'); + # do the return ( $returned, $messages, $issue, $borrower ) = AddReturn( $barcode, $userenv_branch, $exemptfine, $return_date ) - unless $needs_confirm; + unless ( $needs_confirm || $bundle_confirm ); if ($returned) { my $time_now = dt_from_string()->truncate( to => 'minute'); @@ -382,7 +390,8 @@ if ($barcode) { ); } } - } elsif ( C4::Context->preference('ShowAllCheckins') and !$messages->{'BadBarcode'} and !$needs_confirm ) { + + } elsif ( C4::Context->preference('ShowAllCheckins') and !$messages->{'BadBarcode'} and !$needs_confirm and !$bundle_confirm ) { $input{duedate} = 0; $returneditems{0} = $barcode; $riduedate{0} = 0; @@ -393,6 +402,89 @@ if ($barcode) { if ( $needs_confirm ) { $template->param( needs_confirm => $needs_confirm ); } + + if ( $bundle_confirm ) { + $template->param( + items_bundle_return_confirmation => 1, + ); + } + + # Mark missing bundle items as lost and report unexpected items + if ( $item->is_bundle && $query->param('confirm_items_bundle_return') ) { + my $BundleLostValue = C4::Context->preference('BundleLostValue'); + my $barcodes = $query->param('verify-items-bundle-contents-barcodes'); + my @barcodes = map { s/^\s+|\s+$//gr } ( split /\n/, $barcodes ); + my $expected_items = { map { $_->barcode => $_ } $item->bundle_items->as_list }; + my $verify_items = Koha::Items->search( { barcode => { 'in' => \@barcodes } } ); + my @unexpected_items; + my @missing_items; + my @bundle_items; + while ( my $verify_item = $verify_items->next ) { + # Fix and lost statuses + $verify_item->itemlost(0); + + # Update last_seen + $verify_item->datelastseen( dt_from_string()->ymd() ); + + # Update last_borrowed if actual checkin + $verify_item->datelastborrowed( dt_from_string()->ymd() ) if $issue; + + # Expected item, remove from lookup table + if ( delete $expected_items->{$verify_item->barcode} ) { + push @bundle_items, $verify_item; + } + # Unexpected item, warn and remove from bundle + else { + $verify_item->remove_from_bundle; + push @unexpected_items, $verify_item; + } + + # Store results + $verify_item->store(); + } + for my $missing_item ( keys %{$expected_items} ) { + my $bundle_item = $expected_items->{$missing_item}; + # Mark as lost if it's not already lost + if ( !$bundle_item->itemlost ) { + $bundle_item->itemlost($BundleLostValue)->store(); + + # Add return_claim record if this is an actual checkin + if ($issue) { + $bundle_item->_result->create_related( + 'return_claims', + { + issue_id => $issue->issue_id, + itemnumber => $bundle_item->itemnumber, + borrowernumber => $issue->borrowernumber, + created_by => C4::Context->userenv()->{number}, + created_on => dt_from_string + } + ); + } + push @missing_items, $bundle_item; + + # NOTE: We cannot use C4::LostItem here because the item itself doesn't have a checkout + # and thus would not get charged.. it's checked out as part of the bundle. + if ( C4::Context->preference('WhenLostChargeReplacementFee') && $issue ) { + C4::Accounts::chargelostitem( + $issue->borrowernumber, + $bundle_item->itemnumber, + $bundle_item->replacementprice, + sprintf( "%s %s %s", + $bundle_item->biblio->title || q{}, + $bundle_item->barcode || q{}, + $bundle_item->itemcallnumber || q{}, + ), + ); + } + } + } + $template->param( + unexpected_items => \@unexpected_items, + missing_items => \@missing_items, + bundle_items => \@bundle_items + ); + } } $template->param( inputloop => \@inputloop ); @@ -631,6 +723,8 @@ foreach my $code ( keys %$messages ) { ; } elsif ( $code eq 'TransferredRecall' ) { ; + } elsif ( $code eq 'InBundle' ) { + $template->param( InBundle => $messages->{InBundle} ); } else { die "Unknown error code $code"; # note we need all the (empty) elsif's above, or we die. # This forces the issue of staying in sync w/ Circulation.pm diff --git a/installer/data/mysql/atomicupdate/bug_28854.pl b/installer/data/mysql/atomicupdate/bug_28854.pl new file mode 100755 index 0000000000..d93fa9f49e --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_28854.pl @@ -0,0 +1,55 @@ +use Modern::Perl; + +return { + bug_number => "28854", + description => "Item bundles support", + up => sub { + my ($args) = @_; + my ($dbh, $out) = @$args{qw(dbh out)}; + + if( !TableExists( 'item_bundles' ) ) { + $dbh->do(q{ + CREATE TABLE `item_bundles` ( + `item` int(11) NOT NULL, + `host` int(11) NOT NULL, + PRIMARY KEY (`host`, `item`), + UNIQUE KEY `item_bundles_uniq_1` (`item`), + CONSTRAINT `item_bundles_ibfk_1` FOREIGN KEY (`item`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `item_bundles_ibfk_2` FOREIGN KEY (`host`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + }); + } + say $out "item_bundles table added"; + + my ($lost_val) = $dbh->selectrow_array( "SELECT MAX(authorised_value) FROM authorised_values WHERE category = 'LOST'", {} ); + $lost_val++; + + $dbh->do(qq{ + INSERT INTO authorised_values (category,authorised_value,lib) VALUES ('LOST',$lost_val,'Missing from bundle') + }); + say $out "Missing from bundle LOST AV added"; + + my ($nfl_val) = $dbh->selectrow_array( "SELECT MAX(authorised_value) FROM authorised_values WHERE category = 'NOT_LOAN'", {} ); + $nfl_val++; + + $dbh->do(qq{ + INSERT INTO authorised_values (category,authorised_value,lib) VALUES ('NOT_LOAN',$nfl_val,'Added to bundle') + }); + say $out "Added to bundle NOT_LOAN AV added"; + + $dbh->do(qq{ + INSERT IGNORE INTO systempreferences( `variable`, `value`, `options`, `explanation`, `type` ) + VALUES + ( 'BundleLostValue', $lost_val, '', 'Sets the LOST AV value that represents "Missing from bundle" as a lost value', 'Free' ), + ( 'BundleNotLoanValue', $nfl_val, '', 'Sets the NOT_LOAN AV value that represents "Added to bundle" as a not for loan value', 'Free') + }); + say $out "System preferences added and set"; + + if( index_exists( 'return_claims', 'issue_id' ) ) { + $dbh->do(q{ + ALTER TABLE return_claims DROP INDEX issue_id + }); + say $out "Dropped unique constraint on issue_id in return_claims"; + } + } +} diff --git a/installer/data/mysql/en/optional/auth_val.yml b/installer/data/mysql/en/optional/auth_val.yml index b680987a00..41c1cec9e6 100644 --- a/installer/data/mysql/en/optional/auth_val.yml +++ b/installer/data/mysql/en/optional/auth_val.yml @@ -106,6 +106,10 @@ tables: authorised_value: "4" lib : "Missing" + - category: "LOST" + authorised_value: "5" + lib : "Missing from bundle" + # damaged status of an item - category: "DAMAGED" authorised_value: "1" @@ -183,6 +187,10 @@ tables: authorised_value: "2" lib: "Staff Collection" + - category: "NOT_LOAN" + authorised_value: "3" + lib: "Added to bundle" + # restricted status of an item,linked to items.restricted - category: "RESTRICTED" authorised_value: "1" diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 54676b54e7..3227ab27f1 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -3173,6 +3173,23 @@ CREATE TABLE `items` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table item_bundles +-- + +DROP TABLE IF EXISTS `item_bundles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `item_bundles` ( + `item` int(11) NOT NULL, + `host` int(11) NOT NULL, + PRIMARY KEY (`host`, `item`), + UNIQUE KEY `item_bundles_uniq_1` (`item`), + CONSTRAINT `item_bundles_ibfk_1` FOREIGN KEY (`item`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `item_bundles_ibfk_2` FOREIGN KEY (`host`) REFERENCES `items` (`itemnumber`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `items_last_borrower` -- @@ -4497,7 +4514,6 @@ CREATE TABLE `return_claims` ( `resolved_on` timestamp NULL DEFAULT NULL COMMENT 'Time and date the claim was resolved', `resolved_by` int(11) DEFAULT NULL COMMENT 'ID of the staff member that resolved the claim', PRIMARY KEY (`id`), - UNIQUE KEY `issue_id` (`issue_id`), KEY `itemnumber` (`itemnumber`), KEY `rc_borrowers_ibfk` (`borrowernumber`), KEY `rc_created_by_ibfk` (`created_by`), diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index 108007b3df..04d36ff9e1 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -115,6 +115,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('BorrowerUnwantedField','',NULL,'Name the fields you don\'t need to store for a patron\'s account','free'), ('BranchTransferLimitsType','ccode','itemtype|ccode','When using branch transfer limits, choose whether to limit by itemtype or collection code.','Choice'), ('BrowseResultSelection','0',NULL,'Enable/Disable browsing search results fromt the bibliographic record detail page in staff interface','YesNo'), +('BundleLostValue','5',NULL,'Sets the LOST AV value that represents "Missing from bundle" as a lost value','Free'), +('BundleNotLoanValue','3',NULL,'Sets the NOT_LOAN AV value that represents "Added to bundle" as a not for loan value','Free'), ('CalculateFinesOnReturn','1','','Switch to control if overdue fines are calculated on return or not','YesNo'), ('CalculateFinesOnBackdate','1','','Switch to control if overdue fines are calculated on return when backdating','YesNo'), ('CalendarFirstDayOfWeek','0','0|1|2|3|4|5|6','Select the first day of week to use in the calendar.','Choice'), diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss b/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss index 6f483a9e30..a62600d96f 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss @@ -359,7 +359,7 @@ tbody { border-right: 1px solid $table-border-color; } } - &:nth-child(odd):not(.dtrg-group):not(.active) { + &:nth-child(odd):not(.dtrg-group):not(.active):not(.ok) { td { &:not(.bg-danger):not(.bg-warning):not(.bg-info):not(.bg-success):not(.bg-primary) { background-color: $table-odd-row; diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss index 0afb6c768a..cb6d2c52a6 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss @@ -1973,6 +1973,12 @@ i { font-style: italic; } +// style for bundled detail in catalogsearch +.bundled { + display: block; + font-style: italic; +} + #menu { border-right: 1px solid #B9D8D9; margin-right: .5em; @@ -2355,6 +2361,14 @@ td { display: block; } +.bundled { + display: block; +} + +td.bundle { + background-color: #FFC !important; +} + .datedue { color: #999; display: block; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc index fdcc6eadfa..6ec8a50c8f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc @@ -115,6 +115,8 @@ [% ELSE %] [% IF subfield.IS_LOST_AV && Koha.Preference("ClaimReturnedLostValue") && aval == Koha.Preference("ClaimReturnedLostValue") %] + [% ELSIF subfield.IS_LOST_AV && Koha.Preference("BundleLostValue") && aval == Koha.Preference("BundleLostValue") %] + [% ELSE %] [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/js-biblio-format.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/js-biblio-format.inc new file mode 100644 index 0000000000..fe43954c22 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/js-biblio-format.inc @@ -0,0 +1,64 @@ + diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc new file mode 100644 index 0000000000..fe0041923c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc @@ -0,0 +1,35 @@ + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index 3de1834c93..149053073b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -1284,6 +1284,17 @@ Circulation: - pref: ArticleRequestsSupportedFormats - "(Valid choices are currently: PHOTOCOPY and SCAN. Separate the supported formats by a vertical bar. The first listed format is selected by default when you request via the OPAC.)" + + Item bundles: + - + - Use the LOST authorised value + - pref: BundleLostValue + - to represent 'missing from bundle' at return. + - + - Use the NOT_LOAN authorised value + - pref: BundleNotLoanValue + - to represent 'added to bundle' when an item is attached to bundle. + Return claims: - - When marking a checkout as "claims returned", diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt index eeecfd1669..14ac3460a4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -368,12 +368,12 @@ [% IF ( analyze ) %]Used in[% END %] [% IF ( ShowCourseReserves ) %]Course reserves[% END %] [% IF ( SpineLabelShowPrintOnBibDetails ) %]Spine label[% END %] - [% IF ( CAN_user_editcatalogue_edit_items ) %] [% END %] + [% IF ( CAN_user_editcatalogue_edit_items ) %] [% END %] [% FOREACH item IN items %] - + [% IF (StaffDetailItemSelection) %] @@ -545,6 +545,11 @@ Note that permanent location is a code, and location may be an authval. [% IF ( item.restricted ) %] ([% item.restrictedvalue | html %]) [% END %] + + [% IF ( item.bundle_host ) %] + In bundle: [% INCLUDE 'biblio-title.inc' biblio = item.bundle_host.biblio link = 1 %] + [% END %] + [% item.datelastseen | $KohaDates %] [% item.issues || 0 | html %] @@ -631,6 +636,9 @@ Note that permanent location is a code, and location may be an authval. Edit [% END %] [% END %] + [% IF bundlesEnabled %] + + [% END %] [% END %] @@ -1222,6 +1230,66 @@ Note that permanent location is a code, and location may be an authval. + [% IF bundlesEnabled %] + + + + [% END %] + [% MACRO jsinclude BLOCK %] [% INCLUDE 'catalog-strings.inc' %] [% Asset.js("js/catalog.js") | $raw %] @@ -1528,6 +1596,9 @@ Note that permanent location is a code, and location may be an authval. [% INCLUDE 'datatables.inc' %] [% Asset.js("lib/jquery/plugins/jquery.dataTables.columnFilter.js") | $raw %] [% INCLUDE 'columns_settings.inc' %] + [% INCLUDE 'js-date-format.inc' %] + [% INCLUDE 'js-patron-format.inc' %] + [% INCLUDE 'js-biblio-format.inc' %] [% Asset.js("js/browser.js") | $raw %] [% Asset.js("js/table_filters.js") | $raw %] [% Asset.js("js/pages/circulation.js") | $raw %] [% Asset.js("js/checkouts.js") | $raw %] - [% IF Koha.Preference('ClaimReturnedLostValue') %] + [% IF Koha.Preference('ClaimReturnedLostValue') || Koha.Preference('BundleLostValue') %] [% Asset.js("js/resolve_claim_modal.js") | $raw %] [% END %] [% Asset.js("js/holds.js") | $raw %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt index c97b4a32da..dd295153a3 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -6,6 +6,7 @@ [% USE ItemTypes %] [% USE AuthorisedValues %] [% USE TablesSettings %] +[% PROCESS 'i18n.inc' %] [% PROCESS 'member-display-address-style.inc' %] [% SET footerjs = 1 %] [% BLOCK display_bormessagepref %] @@ -217,6 +218,39 @@ [% END %] + + [% IF missing_items %] +
+

Bundle had missing items

+

Bundle contents list updated

+

+ View updated contents list + View list of missing items +

+
+ [% END %] + + + [% IF unexpected_items %] +
+

Bundle had unexpected items

+

Please place the following items to one side

+
    + [% FOREACH unexpected_item IN unexpected_items %] +
  • [% INCLUDE 'biblio-title.inc' biblio=unexpected_item.biblio %] - [% unexpected_item.barcode | html %]
  • + [% END %] +
+
+ [% END %] + + + [% IF InBundle %] +
+

Item belongs in bundle

+

This item belongs to a bundle: [% INCLUDE 'biblio-title.inc' biblio=InBundle.biblio %] - [% InBundle.barcode | html %]

+

+
+ [% END %] [% IF ( errmsgloop ) %]
@@ -347,6 +381,14 @@

[% checkinmsg | html_line_break %]

[% END # /IF checkinmsg %] + + [% IF bundle_items && !missing_items %] +
+

Bundle verified

+

The bundle content was verified

+

View contents list

+
+ [% END %] [% END # /BLOCK all_checkin_messages %] [% IF needs_confirm %] @@ -381,6 +423,91 @@ [% END %] + [% IF items_bundle_return_confirmation %] + + [% END %] + [% IF wrongbranch %]