From b4a58bcd4375d418d166ab23a55ab813e53086e0 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Tue, 12 Nov 2019 15:21:15 +0100 Subject: [PATCH] Bug 24023: Add ability to create bundles of items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds the ability to link items to a biblio to create bundles of items that can be reserved and checked out like a regular item. A withdrawn status will be set to linked items so that they can't be checked out while they are part of a bundle. When returning a bundle, the list of items that are part of this bundle will be displayed, allowing the librarian to verify that all items are there. Bundles can also be used in the inventory tool as a filter. Test plan: 0. Apply the patch, run updatedatabase + update_dbix_class_files 1. Create a biblio record with at least a title. Create an item for this biblio record 2. In the biblio 'Edit' menu you have a new option: 'Link elements'. Click on it 3. Enter a list of valid and invalid barcodes in the textarea, select a withdrawn status and click on 'Continue' 4. Verify that new fields have been added to the MARC record (462 for UNIMARC, 774 otherwise) 5. Verify that the withdrawn status has been correctly set for the items you have linked 6. Check that the biblionumber of the parent bibliographic record is in each linked items uri field 7. Verify that, for item created at step 1, items.materials has been updated with the number of items contained in the bundle 8. Edit the biblio record and remove one of the 462/774 fields 9. Verify that the corresponding item's withdrawn status has been reset 10. Check that the uri field in the corresponding items was removed 11. Checkout and checkin the item created at step 1 12. You should see the list of items contained in the bundle. Click on 'Confirm checkin without verifying'. Verify that the item has been returned 13. Checkout and checkin the item created at step 1 again 14. Now click on 'Verify items bundle contents'. A textarea lets you enter barcodes. Enter some (but not all) barcodes that are in the bundle. Select a 'lost' status and click on 'Confirm checkin and mark missing items as lost' 15. Verify that the items corresponding to barcodes you did not type in the textarea are now marked as lost with the lost status you chose 16. Go to the inventory tool, use the bundle filter and verify that it works Sponsored-by: Bibliothèque Départementale de l'Yonne --- C4/Biblio.pm | 45 ++++++ C4/Items.pm | 6 + Koha/Template/Plugin/Biblio.pm | 9 ++ admin/columns_settings.yml | 10 ++ catalogue/detail.pl | 10 ++ catalogue/items-bundle-contents.pl | 53 +++++++ cataloguing/linkelements.pl | 147 ++++++++++++++++++ circ/returns.pl | 31 ++++ .../atomicupdate/bug-24023-items-bundles.perl | 37 +++++ installer/data/mysql/kohastructure.sql | 38 +++++ .../prog/en/includes/biblio-view-menu.inc | 7 + .../prog/en/includes/cat-toolbar.inc | 1 + .../prog/en/includes/item-status.inc | 84 ++++++++++ .../prog/en/modules/catalogue/detail.tt | 9 ++ .../catalogue/items-bundle-contents.tt | 79 ++++++++++ .../en/modules/cataloguing/linkelements.tt | 91 +++++++++++ .../prog/en/modules/circ/returns.tt | 92 +++++++++++ .../prog/en/modules/tools/inventory.tt | 20 ++- tools/inventory.pl | 10 ++ 19 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 catalogue/items-bundle-contents.pl create mode 100755 cataloguing/linkelements.pl create mode 100644 installer/data/mysql/atomicupdate/bug-24023-items-bundles.perl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/items-bundle-contents.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/linkelements.tt diff --git a/C4/Biblio.pm b/C4/Biblio.pm index 443e445245..c126251984 100644 --- a/C4/Biblio.pm +++ b/C4/Biblio.pm @@ -3175,6 +3175,8 @@ sub ModBiblioMarc { $m_rs->metadata( $record->as_xml_record($encoding) ); $m_rs->store; + UpdateItemsBundles($biblionumber, $record); + ModZebra( $biblionumber, "specialUpdate", "biblioserver" ); return $biblionumber; @@ -3430,6 +3432,49 @@ sub RemoveAllNsb { return $record; } +=head2 UpdateItemsBundles + +Remove from items bundle the items that were removed from the MARC record (keep +the table in sync with the MARC record) + +This is called by ModBiblio + + UpdateItemsBundles($biblionumber, $record) + +=cut + +sub UpdateItemsBundles { + my ($biblionumber, $record) = @_; + + my $schema = Koha::Database->new()->schema(); + my $items_bundle_rs = $schema->resultset('ItemsBundle'); + my $items_rs = $schema->resultset('Item'); + my $items_bundle = $items_bundle_rs->find({ biblionumber => $biblionumber }); + return unless $items_bundle; + + my $tag = C4::Context->preference('marcflavour') eq 'UNIMARC' ? '462' : '774'; + my @fields = $record->field($tag); + my %fields_by_itemnumber = map { (scalar $_->subfield('9')) // '' => $_ } @fields; + + foreach my $items_bundle_item ($items_bundle->items_bundle_items) { + my $itemnumber = $items_bundle_item->get_column('itemnumber'); + if (not exists $fields_by_itemnumber{$itemnumber}) { + $items_bundle_item->itemnumber->update({ withdrawn => 0, uri => undef }); + $items_bundle_item->delete(); + } + } + + my $items_count = $items_bundle->items_bundle_items->count(); + # If bundle is empty, delete it + if ($items_count == 0) { + $items_bundle->delete(); + } + + my $materials = $items_count || undef; + $items_rs->search({ biblionumber => $biblionumber }) + ->update_all({ materials => $materials }); +} + 1; diff --git a/C4/Items.pm b/C4/Items.pm index cca700258d..8013dafd29 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -746,6 +746,7 @@ sub GetItemsForInventory { my $minlocation = $parameters->{'minlocation'} // ''; my $maxlocation = $parameters->{'maxlocation'} // ''; my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource'); + my $items_bundle = $parameters->{'items_bundle'} // ''; my $location = $parameters->{'location'} // ''; my $itemtype = $parameters->{'itemtype'} // ''; my $ignoreissued = $parameters->{'ignoreissued'} // ''; @@ -791,6 +792,11 @@ sub GetItemsForInventory { push @bind_params, $max_cnsort; } + if ($items_bundle) { + push @where_strings, 'items.itemnumber IN (SELECT itemnumber FROM items_bundle_item WHERE items_bundle_id = ?)'; + push @bind_params, $items_bundle; + } + if ($datelastseen) { $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 }); push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)'; diff --git a/Koha/Template/Plugin/Biblio.pm b/Koha/Template/Plugin/Biblio.pm index e9d7a73009..df3da0b0cf 100644 --- a/Koha/Template/Plugin/Biblio.pm +++ b/Koha/Template/Plugin/Biblio.pm @@ -63,4 +63,13 @@ sub CanArticleRequest { return $biblio ? $biblio->can_article_request( $borrower ) : 0; } +sub IsItemsBundle { + my ($self, $biblionumber) = @_; + + my $items_bundle_rs = Koha::Database->new()->schema()->resultset('ItemsBundle'); + my $count = $items_bundle_rs->search({ biblionumber => $biblionumber })->count; + + return $count > 0; +} + 1; diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml index c1fdf398ca..37eba6952a 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -258,6 +258,16 @@ modules: - columnname: checkin_on + items-bundle-contents: + items-bundle-contents-table: + - columnname: title + - columnname: author + - columnname: ccode + - columnname: itemtype + - columnname: callnumber + - columnname: barcode + - columnname: status + cataloguing: additem: itemst: diff --git a/catalogue/detail.pl b/catalogue/detail.pl index 3e2ca01cf5..05c956c13b 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -43,12 +43,15 @@ use C4::CourseReserves qw(GetItemCourseReservesInfo); use C4::Acquisition qw(GetOrdersByBiblionumber); use Koha::AuthorisedValues; use Koha::Biblios; +use Koha::Database; use Koha::Items; use Koha::ItemTypes; use Koha::Patrons; use Koha::Virtualshelves; use Koha::Plugins; +my $schema = Koha::Database->new()->schema(); + my $query = CGI->new(); my $analyze = $query->param('analyze'); @@ -373,6 +376,12 @@ foreach my $item (@items) { } } + my $items_bundle_item = $schema->resultset('ItemsBundleItem')->find({ itemnumber => $item->{itemnumber} }); + if ($items_bundle_item) { + $itemfields{items_bundle} = 1; + $item->{items_bundle} = $items_bundle_item->items_bundle; + } + if ($currentbranch and $currentbranch ne "NO_LIBRARY_SET" and C4::Context->preference('SeparateHoldings')) { if ($itembranchcode and $itembranchcode eq $currentbranch) { @@ -409,6 +418,7 @@ $template->param( itemdata_stocknumber => $itemfields{stocknumber}, volinfo => $itemfields{enumchron}, itemdata_itemnotes => $itemfields{itemnotes}, + itemdata_items_bundle => $itemfields{items_bundle}, itemdata_nonpublicnotes => $itemfields{itemnotes_nonpublic}, z3950_search_params => C4::Search::z3950_search_args($dat), hostrecords => $hostrecords, diff --git a/catalogue/items-bundle-contents.pl b/catalogue/items-bundle-contents.pl new file mode 100644 index 0000000000..6969a64014 --- /dev/null +++ b/catalogue/items-bundle-contents.pl @@ -0,0 +1,53 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI qw(-utf8); +use C4::Auth; +use C4::Output; + +use Koha::Database; +use Koha::Biblios; +use Koha::Items; + +my $schema = Koha::Database->new()->schema(); + +my $cgi = CGI->new(); + +my ($template, $borrowernumber, $cookie) = get_template_and_user({ + template_name => 'catalogue/items-bundle-contents.tt', + query => $cgi, + type => 'intranet', + authnotrequired => 0, + flagsrequired => { catalogue => 1 }, +}); + +my $biblionumber = $cgi->param('biblionumber'); +my $biblio = Koha::Biblios->find($biblionumber); + +my $items_bundle_rs = $schema->resultset('ItemsBundle'); +my $items_bundle = $items_bundle_rs->find({ biblionumber => $biblionumber }); +my @items = map { scalar Koha::Items->find($_->get_column('itemnumber')) } $items_bundle->items_bundle_items; + +$template->param( + biblionumber => $biblionumber, + biblio => $biblio, + items => \@items, +); + +output_html_with_http_headers $cgi, $cookie, $template->output; diff --git a/cataloguing/linkelements.pl b/cataloguing/linkelements.pl new file mode 100755 index 0000000000..fa6b9eccc6 --- /dev/null +++ b/cataloguing/linkelements.pl @@ -0,0 +1,147 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI qw ( -utf8 ); +use Encode; +use List::MoreUtils qw/uniq/; +use MARC::Record; + +use Koha::Biblios; +use Koha::Items; +use Koha::Database; + +use C4::Auth; +use C4::Output; +use C4::Biblio; +use C4::Context; +use C4::Items; + +my $cgi = new CGI; + +my ($template, $loggedinuser, $cookie) = get_template_and_user({ + template_name => 'cataloguing/linkelements.tt', + query => $cgi, + type => 'intranet', + authnotrequired => 0, + flagsrequired => { editcatalogue => 'edit_catalogue' }, +}); + +my $biblionumber = $cgi->param('biblionumber'); +my $sessionId = $cgi->cookie('CGISESSID'); +my $session = C4::Auth::get_session($sessionId); +my $schema = Koha::Database->new()->schema(); +my $items_bundle_rs = $schema->resultset('ItemsBundle'); +my $items_bundle_item_rs = $schema->resultset('ItemsBundleItem'); + +if ($cgi->request_method() eq 'POST') { + my $barcodelist = $cgi->param('barcodelist'); + my $withdrawn = $cgi->param('withdrawn'); + my @barcodes = uniq( split(/\s*\n\s*/, $barcodelist) ); + my @items; + my @notfound_barcodes; + my @found_barcodes; + my @alreadylinked_barcodes; + foreach my $barcode (@barcodes) { + my $item = Koha::Items->find( { barcode => $barcode } ); + if ($item) { + my $items_bundle_item = $items_bundle_item_rs->find({ itemnumber => $item->itemnumber }); + if ($items_bundle_item && $items_bundle_item->items_bundle->get_column('biblionumber') ne $biblionumber) { + # This item is already linked to another biblio + push @alreadylinked_barcodes, $barcode + } else { + push @items, $item; + push @found_barcodes, $barcode; + } + } else { + push @notfound_barcodes, $barcode; + } + } + + if (@items) { + my $biblio = Koha::Biblios->find($biblionumber); + my $record = GetMarcBiblio({ biblionumber => $biblionumber }); + my $tag = C4::Context->preference('marcflavour') eq 'UNIMARC' ? '462' : '774'; + foreach my $item (@items) { + # Remove fields that references the same itemnumber + # to avoid duplicates + my @fields = $record->field($tag); + my @duplicate_fields = grep { + $_->subfield('9') eq $item->itemnumber; + } @fields; + $record->delete_fields(@duplicate_fields); + + my $field = MARC::Field->new($tag, '', '', + 't' => $item->biblio->title, + '0' => $item->biblionumber, + '9' => $item->itemnumber); + $record->insert_fields_ordered($field); + + # Adding biblionumber to items + ModItem({uri => $biblionumber}, $item->biblionumber, $item->itemnumber); + + my $items_bundle = $items_bundle_rs->find_or_create({ biblionumber => $biblionumber }); + $items_bundle_item_rs->find_or_create({ + items_bundle_id => $items_bundle->items_bundle_id, + itemnumber => $item->itemnumber, + }); + + if (defined $withdrawn and $withdrawn ne '') { + $item->withdrawn($withdrawn)->store(); + } + } + ModBiblio($record, $biblionumber, $biblio->frameworkcode); + } + + # Store barcodes in the session to display messages after the redirect + $session->param('linkelements_notfound_barcodes', \@notfound_barcodes); + $session->param('linkelements_found_barcodes', \@found_barcodes); + $session->param('linkelements_alreadylinked_barcodes', \@alreadylinked_barcodes); + $session->flush(); + + print $cgi->redirect('/cgi-bin/koha/cataloguing/linkelements.pl?biblionumber=' . $biblionumber); + exit; +} + +my @notfound_barcodes = map { + Encode::is_utf8($_) ? $_ : Encode::decode('UTF-8', $_) +} @{ $session->param('linkelements_notfound_barcodes') // [] }; + +my @found_barcodes = map { + Encode::is_utf8($_) ? $_ : Encode::decode('UTF-8', $_) +} @{ $session->param('linkelements_found_barcodes') // [] }; + +my @alreadylinked_barcodes = map { + Encode::is_utf8($_) ? $_ : Encode::decode('UTF-8', $_) +} @{ $session->param('linkelements_alreadylinked_barcodes') // [] }; + +$session->clear([ + 'linkelements_notfound_barcodes', + 'linkelements_found_barcodes', + 'linkelements_alreadylinked_barcodes', +]); +$session->flush(); + +$template->param( + biblionumber => $biblionumber, + notfound_barcodes => \@notfound_barcodes, + found_barcodes => \@found_barcodes, + alreadylinked_barcodes => \@alreadylinked_barcodes, +); + +output_html_with_http_headers $cgi, $cookie, $template->output; diff --git a/circ/returns.pl b/circ/returns.pl index 5e7d8e24bc..3c12883e8d 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -294,6 +294,37 @@ if ($barcode) { additional_materials => $materials, issue => $checkout, ); + + my $items_bundle_rs = Koha::Database->new->schema->resultset('ItemsBundle'); + my $items_bundle = $items_bundle_rs->find({ biblionumber => $biblio->biblionumber }); + if ($items_bundle) { + my @items_bundle_items = map { + scalar Koha::Items->find($_->get_column('itemnumber')) + } $items_bundle->items_bundle_items; + + my $confirm = $query->param('confirm_items_bundle_return'); + unless ($confirm) { + $template->param( + items_bundle_return_confirmation => 1, + items_bundle_items => \@items_bundle_items, + ); + + output_html_with_http_headers $query, $cookie, $template->output; + exit; + } + + my $mark_missing_items_as_lost = $query->param('confirm_items_bundle_mark_missing_items_as_lost'); + if ($mark_missing_items_as_lost) { + my $barcodes = $query->param('verify-items-bundle-contents-barcodes'); + my $lost = $query->param('verify-items-bundle-contents-lost'); + my @barcodes = map { s/^\s+|\s+$//gr } (split /\n/, $barcodes); + foreach my $item (@items_bundle_items) { + unless (grep { $_ eq $item->barcode } @barcodes) { + $item->itemlost($lost)->store(); + } + } + } + } } # FIXME else we should not call AddReturn but set BadBarcode directly instead my %input = ( diff --git a/installer/data/mysql/atomicupdate/bug-24023-items-bundles.perl b/installer/data/mysql/atomicupdate/bug-24023-items-bundles.perl new file mode 100644 index 0000000000..2e6b7dc431 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug-24023-items-bundles.perl @@ -0,0 +1,37 @@ +$DBversion = 'XXX'; +if( CheckVersion( $DBversion ) ) { + $dbh->do(q{ + CREATE TABLE IF NOT EXISTS items_bundle ( + items_bundle_id SERIAL, + biblionumber INT NOT NULL, + CONSTRAINT pk_items_bundle_items_bundle_id + PRIMARY KEY (items_bundle_id), + CONSTRAINT fk_items_bundle_biblio_biblionumber + FOREIGN KEY (biblionumber) REFERENCES biblio (biblionumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT uniq_items_bundle_biblionumber + UNIQUE KEY (biblionumber) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci + }); + + $dbh->do(q{ + CREATE TABLE IF NOT EXISTS items_bundle_item ( + items_bundle_item_id SERIAL, + items_bundle_id BIGINT UNSIGNED NOT NULL, + itemnumber INT NOT NULL, + CONSTRAINT pk_items_bundle_item_items_bundle_item_id + PRIMARY KEY (items_bundle_item_id), + CONSTRAINT fk_items_bundle_item_itemnumber + FOREIGN KEY (itemnumber) REFERENCES items (itemnumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT fk_items_bundle_item_items_bundle_id + FOREIGN KEY (items_bundle_id) REFERENCES items_bundle (items_bundle_id) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT uniq_items_bundle_item_itemnumber + UNIQUE KEY (itemnumber) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci + }); + + SetVersion( $DBversion ); + print "Upgrade to $DBversion done (Bug 24023 - Items bundles)\n"; +} diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 9a0d45420c..73e2de293a 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -4448,6 +4448,44 @@ CREATE TABLE return_claims ( CONSTRAINT `rc_resolved_by_ibfk` FOREIGN KEY (`resolved_by`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +-- +-- Table structure for table items_bundle +-- + +DROP TABLE IF EXISTS items_bundle; +CREATE TABLE items_bundle ( + items_bundle_id SERIAL, + biblionumber INT NOT NULL, + CONSTRAINT pk_items_bundle_items_bundle_id + PRIMARY KEY (items_bundle_id), + CONSTRAINT fk_items_bundle_biblio_biblionumber + FOREIGN KEY (biblionumber) REFERENCES biblio (biblionumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT uniq_items_bundle_biblionumber + UNIQUE KEY (biblionumber) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table items_bundle_item +-- + +DROP TABLE IF EXISTS items_bundle_item; +CREATE TABLE items_bundle_item ( + items_bundle_item_id SERIAL, + items_bundle_id BIGINT UNSIGNED NOT NULL, + itemnumber INT NOT NULL, + CONSTRAINT pk_items_bundle_item_items_bundle_item_id + PRIMARY KEY (items_bundle_item_id), + CONSTRAINT fk_items_bundle_item_itemnumber + FOREIGN KEY (itemnumber) REFERENCES items (itemnumber) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT fk_items_bundle_item_items_bundle_id + FOREIGN KEY (items_bundle_id) REFERENCES items_bundle (items_bundle_id) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT uniq_items_bundle_item_itemnumber + UNIQUE KEY (itemnumber) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc index d13312a0df..09033368fa 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc @@ -1,5 +1,6 @@ [% USE Koha %] [% USE Biblio %] +[% PROCESS 'i18n.inc' %] [% SET biblio_object_id = object || biblionumber || biblio.biblionumber %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc index 7ab50f59b6..5a231bfae9 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -62,6 +62,7 @@ CAN_user_serials_create_subscription ) %] [% IF ( CAN_user_editcatalogue_edit_catalogue ) %]
  • Edit as new (duplicate)
  • +
  • Link elements
  • Replace record via Z39.50/SRU
  • [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc new file mode 100644 index 0000000000..ca6dc1d1ff --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc @@ -0,0 +1,84 @@ +[% USE AuthorisedValues %] +[% USE Branches %] +[% USE Koha %] +[% USE KohaDates %] +[% PROCESS 'i18n.inc' %] +[% transfer = item.get_transfer() %] +[% IF item.checkout %] + + [% IF item.checkout.onsite_checkout %] + [% t('Currently in local use by') | html %] + [% ELSE %] + [% t('Checked out to') | html %] + [% END %] + [% INCLUDE 'patron-title.inc' patron=item.checkout.patron hide_patron_infos_if_needed=1 %] + : [% tx('due {date_due}', { date_due = item.checkout.date_due }) | html %] + +[% ELSIF transfer %] + [% datesent = BLOCK %][% transfer.datesent | $KohaDates %][% END %] + [% tx('In transit from {frombranch} to {tobranch} since {datesent}', { frombranch = Branches.GetName(transfer.frombranch), tobranch = Branches.GetName(transfer.tobranch), datesent = datesent }) %] +[% END %] + +[% IF item.itemlost %] + [% itemlost_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.itemlost', authorised_value = item.itemlost }) %] + [% IF itemlost_description %] + [% itemlost_description | html %] + [% ELSE %] + [% t('Unavailable (lost or missing)') | html %] + [% END %] +[% END %] + +[% IF item.withdrawn %] + [% withdrawn_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.withdrawn', authorised_value = item.withdrawn }) %] + [% IF withdrawn_description %] + [% withdrawn_description | html %] + [% ELSE %] + [% t('Withdrawn') | html %] + [% END %] +[% END %] + +[% IF item.damaged %] + [% damaged_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.damaged', authorised_value = item.damaged }) %] + [% IF damaged_description %] + [% damaged_description | html %] + [% ELSE %] + [% t('Damaged') | html %] + [% END %] +[% END %] + +[% IF item.notforloan || item.effective_itemtype.notforloan %] + + [% t('Not for loan') | html %] + [% notforloan_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.notforloan', authorised_value = item.notforloan }) %] + [% IF notforloan_description %] + ([% notforloan_description | html %]) + [% END %] + +[% END %] + +[% hold = item.holds.next %] +[% IF hold %] + + [% IF hold.waitingdate %] + Waiting at [% Branches.GetName(hold.get_column('branchcode')) | html %] since [% hold.waitingdate | $KohaDates %]. + [% ELSE %] + Item-level hold (placed [% hold.reservedate | $KohaDates %]) for delivery at [% Branches.GetName(hold.get_column('branchcode')) | html %]. + [% END %] + [% IF Koha.Preference('canreservefromotherbranches') %] + [% t('Hold for:') | html %] + [% INCLUDE 'patron-title.inc' patron=hold.borrower hide_patron_infos_if_needed=1 %] + [% END %] + +[% END %] +[% UNLESS item.notforloan || item.effective_itemtype.notforloan || item.onloan || item.itemlost || item.withdrawn || item.damaged || transfer || hold %] + [% t('Available') | html %] +[% END %] + +[% IF ( item.restricted ) %] + [% restricted_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.restricted', authorised_value = item.restricted }) %] + [% IF restricted_description %] + ([% restricted_description | html %]) + [% ELSE %] + [% t('Restricted') | html %] + [% END %] +[% END %] 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 d7fc0ec3f2..136766e0ab 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -237,6 +237,7 @@ [% IF ( hostrecords ) %]Host records[% END %] [% IF ( analyze ) %]Used in[% END %] [% IF ( ShowCourseReserves ) %]Course Reserves[% END %] + [% IF itemdata_items_bundle %]Items bundle[% END %] [% IF ( SpineLabelShowPrintOnBibDetails ) %]Spine label[% END %] [% IF ( CAN_user_editcatalogue_edit_items ) %] [% END %] @@ -423,6 +424,14 @@ [% END %] + [% IF itemdata_items_bundle %] + + [% INCLUDE 'biblio-default-view.inc' biblionumber = item.items_bundle.biblionumber.biblionumber %] + [% item.items_bundle.biblionumber.title | html %] + + + [% END %] + [% IF ( SpineLabelShowPrintOnBibDetails ) %] Print label [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/items-bundle-contents.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/items-bundle-contents.tt new file mode 100644 index 0000000000..aa1fe3fdec --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/items-bundle-contents.tt @@ -0,0 +1,79 @@ +[% USE raw %] +[% USE Asset %] +[% USE AuthorisedValues %] +[% USE ItemTypes %] +[% USE ColumnsSettings %] + +[% INCLUDE 'doc-head-open.inc' %] +Koha › Catalog › Items bundle contents for [% INCLUDE 'biblio-title-head.inc' %] +[% INCLUDE 'doc-head-close.inc' %] + + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
    +
    +
    + [% INCLUDE 'cat-toolbar.inc' %] + +

    Items bundle contents for [% INCLUDE 'biblio-title.inc' %]

    + +
    + + + + + + + + + + + + + + [% FOREACH item IN items %] + + + + + + + + + + [% END %] + +
    TitleAuthorCollection codeItem typeCallnumberBarcodeStatus
    [% INCLUDE 'biblio-default-view.inc' biblionumber=item.biblionumber %][% INCLUDE 'biblio-title.inc' biblio=item.biblio %][% item.biblio.author | html %][% AuthorisedValues.GetByCode('CCODE', item.ccode) | html %][% ItemTypes.GetDescription(item.itype) | html %][% item.itemcallnumber | html %][% item.barcode | html %][% INCLUDE 'item-status.inc' item=item %]
    +
    +
    + +
    + +
    +
    + + [% Asset.js('js/table_filters.js') | $raw %] + [% Asset.js('lib/jquery/plugins/jquery.dataTables.columnFilter.js') | $raw %] + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'columns_settings.inc' %] + + +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/linkelements.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/linkelements.tt new file mode 100644 index 0000000000..cf5cb0a7d0 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/linkelements.tt @@ -0,0 +1,91 @@ +[% USE AuthorisedValues %] +[% PROCESS 'i18n.inc' %] +[% INCLUDE 'doc-head-open.inc' %] +Link elements together +[% INCLUDE 'doc-head-close.inc' %] + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +[% BLOCK barcodes_table %] + + + + + + + + [% FOREACH barcode IN barcodes %] + + + + [% END %] + +
    [% t('Barcode') | html %]
    [% barcode | html %]
    +[% END %] + +
    +
    +
    +
    +

    [% t('Link elements to the biblio') | html %]

    + + [% IF ( notfound_barcodes ) %] +
    +

    [% t('Warning: the following barcodes were not found:') | html %]

    + [% PROCESS barcodes_table barcodes = notfound_barcodes %] +
    + [% END %] + + [% IF ( alreadylinked_barcodes ) %] +
    +

    [% t('Warning: the following barcodes are already linked to another biblio') | html %]

    + [% PROCESS barcodes_table barcodes = alreadylinked_barcodes %] +
    + [% END %] + + [% IF found_barcodes %] +
    +

    [% t('The following barcodes were linked:') | html %]

    + + [% PROCESS barcodes_table barcodes = found_barcodes %] +
    + [% END %] + +
    +
    + [% t('Items') | html %] +
      +
    1. + + +
    2. +
    3. + + +
    4. +
    + +
    + +
    + + Cancel +
    +
    +
    +
    +
    +[% INCLUDE 'intranet-bottom.inc' %] 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 a65294b985..0979d20e00 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -7,6 +7,7 @@ [% USE ItemTypes %] [% USE AuthorisedValues %] [% USE ColumnsSettings %] +[% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% BLOCK display_bormessagepref %] [% IF ( bormessagepref ) %] @@ -664,6 +665,72 @@ [% INCLUDE all_checkin_messages %]
    + [% IF items_bundle_return_confirmation %] +
    [% t('The returned item is an items bundle and needs confirmation') | html %]
    + + + + + + + + + + + + + + + [% FOREACH item IN items_bundle_items %] + + + + + + + + + + [% END %] + +
    [% t('Title') | html %][% t('Author') | html %][% t('Collection code') | html %][% t('Item type') | html %][% t('Callnumber') | html %][% t('Barcode') | html %][% t('Status') | html %]
    [% INCLUDE 'biblio-default-view.inc' biblionumber=item.biblionumber %][% INCLUDE 'biblio-title.inc' biblio=item.biblio %][% item.biblio.author | html %][% AuthorisedValues.GetByCode('CCODE', item.ccode) | html %][% ItemTypes.GetDescription(item.itype) | html %][% item.itemcallnumber | html %][% item.barcode | html %][% INCLUDE 'item-status.inc' item=item %]
    + +
    +
    + + + +
    + + +
    + +
    +
    +
    + + +
    [% t('Scan barcodes found in the items bundle and compare it to the table above') | html %]
    +
    + +
    + + +
    [% t('If some items are missing, they will be marked as lost with this status') | html %]
    +
    + + + + + +
    +
    + [% END %] +
    @@ -1076,6 +1143,31 @@ $('[data-toggle="tooltip"]').tooltip(); }); + + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt index 7739c83527..48c1048dc0 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt @@ -93,6 +93,17 @@ [% END %] + [% IF items_bundles.size > 0 %] +
  • + + +
  • + [% END %]
    @@ -283,7 +294,8 @@ $('#locationloop').val() || $('#minlocation').val() || $('#maxlocation').val() || - $('#statuses input:checked').length + $('#statuses input:checked').length || + $('#items_bundle').val() ) ) { return confirm( _("You have not selected any catalog filters and are about to compare a file of barcodes to your entire catalog.") + "\n\n" + @@ -401,6 +413,12 @@ }); }); + [% INCLUDE 'select2.inc' %] + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/tools/inventory.pl b/tools/inventory.pl index 19d7090ffd..b7eb165248 100755 --- a/tools/inventory.pl +++ b/tools/inventory.pl @@ -47,6 +47,7 @@ my $minlocation=$input->param('minlocation') || ''; my $maxlocation=$input->param('maxlocation'); my $class_source=$input->param('class_source'); $maxlocation=$minlocation.'Z' unless ( $maxlocation || ! $minlocation ); +my $items_bundle = $input->param('items_bundle'); my $location=$input->param('location') || ''; my $ignoreissued=$input->param('ignoreissued'); my $ignore_waiting_holds = $input->param('ignore_waiting_holds'); @@ -68,6 +69,12 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user( } ); +my $schema = Koha::Database->new()->schema(); +my $items_bundle_rs = $schema->resultset('ItemsBundle'); +my @items_bundles = $items_bundle_rs->search(undef, { + order_by => { -asc => ['biblionumber.title'] }, + join => ['biblionumber'], +}); my @authorised_value_list; my $authorisedvalue_categories = ''; @@ -125,6 +132,7 @@ my $pref_class = C4::Context->preference("DefaultClassificationSource"); $template->param( authorised_values => \@authorised_value_list, + items_bundles => \@items_bundles, today => dt_from_string, minlocation => $minlocation, maxlocation => $maxlocation, @@ -237,6 +245,7 @@ if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) { minlocation => $minlocation, maxlocation => $maxlocation, class_source => $class_source, + items_bundle => $items_bundle, location => $location, ignoreissued => $ignoreissued, datelastseen => $datelastseen, @@ -254,6 +263,7 @@ if( @scanned_items ) { maxlocation => $maxlocation, class_source => $class_source, location => $location, + items_bundle => $items_bundle, ignoreissued => undef, datelastseen => undef, branchcode => $branchcode, -- 2.20.1