From 01590685ec7639a754eda3dfb6b31fb6f84ee3ab 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. Verify that, for item created at step 1, items.materials has been updated with the number of items contained in the bundle 7. Edit the biblio record and remove one of the 462/774 fields 8. Verify that the corresponding item's withdrawn status has been reset 9. Checkout and checkin the item created at step 1 10. You should see the list of items contained in the bundle. Click on 'Confirm checkin without verifying'. Verify that the item has been returned 11. Checkout and checkin the item created at step 1 again 12. 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' 13. 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 14. 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 | 11 ++ catalogue/detail.pl | 10 ++ catalogue/items-bundle-contents.pl | 53 +++++++ cataloguing/linkelements.pl | 144 ++++++++++++++++++ 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, 776 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 6e53842ee1..ad17f19f4e 100644 --- a/C4/Biblio.pm +++ b/C4/Biblio.pm @@ -3064,6 +3064,8 @@ sub ModBiblioMarc { $m_rs->metadata( $record->as_xml_record($encoding) ); $m_rs->store; + UpdateItemsBundles($biblionumber, $record); + my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX }); $indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" ); @@ -3300,6 +3302,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 }); + $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 fa5e8f2ba6..b2251e0b38 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -510,6 +510,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'} // ''; @@ -555,6 +556,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 d825168439..5c2ad2f3e6 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -432,6 +432,17 @@ modules: - columnname: checkin_on + items-bundle-contents: + items-bundle-contents-table: + columns: + - 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 9cb607b1e1..64b8fcffd3 100755 --- a/catalogue/detail.pl +++ b/catalogue/detail.pl @@ -44,6 +44,7 @@ use Koha::AuthorisedValues; use Koha::Biblios; use Koha::CoverImages; use Koha::Illrequests; +use Koha::Database; use Koha::Items; use Koha::ItemTypes; use Koha::Patrons; @@ -51,6 +52,8 @@ use Koha::Virtualshelves; use Koha::Plugins; use Koha::SearchEngine::Search; +my $schema = Koha::Database->new()->schema(); + my $query = CGI->new(); my $analyze = $query->param('analyze'); @@ -411,6 +414,12 @@ foreach my $item (@items) { $item->{cover_images} = $item_object->cover_images; } + 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 C4::Context->preference('SeparateHoldings')) { if ($itembranchcode and $itembranchcode eq $currentbranch) { push @itemloop, $item; @@ -469,6 +478,7 @@ $template->param( itemdata_publisheddate => $itemfields{publisheddate}, 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..320886b255 --- /dev/null +++ b/cataloguing/linkelements.pl @@ -0,0 +1,144 @@ +#!/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); + + 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 116ac3c15e..e3399a72a8 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -282,6 +282,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 ef3bff04b0..2af00b9db7 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -4690,6 +4690,44 @@ CREATE TABLE background_jobs ( PRIMARY KEY (id) ) 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 2080b07707..62a3f5bf20 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 d55c57cdff..5b753647e7 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -63,6 +63,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 55f13b75d8..4d90d812bd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -316,6 +316,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 %] @@ -547,6 +548,14 @@ Note that permanent location is a code, and location may be an authval. [% END %] + [% IF itemdata_items_bundle %] + + [% IF item.items_bundle.biblionumber %] + [% INCLUDE 'biblio-title.inc' biblio = item.items_bundle.biblionumber link = 1 %] + [% END %] + + [% 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..15653af7d4 --- /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 TablesSettings %] + +[% 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-title.inc' biblio=item.biblio link = 1 %][% 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 4ebc4cd40f..1879096a72 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 %] @@ -703,6 +704,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-title.inc' biblio=item.biblio link = 1 %][% 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 %] +
    @@ -1122,6 +1189,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 e9cdad3b3e..6839481289 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/inventory.tt @@ -107,6 +107,17 @@ [% END %] + [% IF items_bundles.size > 0 %] +
  • + + +
  • + [% END %]
    @@ -302,7 +313,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" + @@ -435,6 +447,12 @@ }); }); + [% INCLUDE 'select2.inc' %] + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/tools/inventory.pl b/tools/inventory.pl index 632968219d..b86d71f836 100755 --- a/tools/inventory.pl +++ b/tools/inventory.pl @@ -48,6 +48,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 = ''; @@ -129,6 +136,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, @@ -243,6 +251,7 @@ if ( $op && ( !$uploadbarcodes || $compareinv2barcd )) { minlocation => $minlocation, maxlocation => $maxlocation, class_source => $class_source, + items_bundle => $items_bundle, location => $location, ignoreissued => $ignoreissued, datelastseen => $datelastseen, @@ -260,6 +269,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