Bugzilla – Attachment 136346 Details for
Bug 28854
Add ability to create bundles of items for circulation
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 28854: Expose functionality to attach items to bundles
Bug-28854-Expose-functionality-to-attach-items-to-.patch (text/plain), 36.91 KB, created by
Martin Renvoize (ashimema)
on 2022-06-20 10:32:51 UTC
(
hide
)
Description:
Bug 28854: Expose functionality to attach items to bundles
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2022-06-20 10:32:51 UTC
Size:
36.91 KB
patch
obsolete
>From 623d993b523a54b44ea5ed19557da91860290836 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@ptfs-europe.com> >Date: Thu, 23 Sep 2021 10:01:23 +0100 >Subject: [PATCH] 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. >--- > Koha/Item.pm | 158 +++++++++++ > Koha/REST/V1/Items.pm | 121 ++++++++ > admin/columns_settings.yml | 22 ++ > api/v1/swagger/definitions/bundle_link.yaml | 14 + > api/v1/swagger/definitions/item.yaml | 1 + > api/v1/swagger/paths/items.yaml | 157 +++++++++++ > api/v1/swagger/swagger.yaml | 6 + > catalogue/detail.pl | 14 + > .../prog/css/src/staff-global.scss | 4 + > .../admin/preferences/cataloguing.pref | 4 + > .../prog/en/modules/catalogue/detail.tt | 258 +++++++++++++++++- > 11 files changed, 757 insertions(+), 2 deletions(-) > create mode 100644 api/v1/swagger/definitions/bundle_link.yaml > >diff --git a/Koha/Item.pm b/Koha/Item.pm >index 16d6f95bed..134def36a2 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 ); >@@ -1441,6 +1442,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 fro 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 \(`(?<column>.*?)`\)/ ) { >+ Koha::Exceptions::Object::FKConstraint->throw( >+ error => 'Broken FK constraint', >+ broken_fk => $+{column} >+ ); >+ } >+ } >+ elsif ( >+ $_->{msg} =~ /Duplicate entry '(.*?)' for key '(?<key>.*?)'/ ) >+ { >+ Koha::Exceptions::Object::DuplicateID->throw( >+ error => 'Duplicate ID', >+ duplicate_id => $+{key} >+ ); >+ } >+ elsif ( $_->{msg} =~ >+/Incorrect (?<type>\w+) value: '(?<value>.*)' for column \W?(?<property>\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..2e5478770f 100644 >--- a/Koha/REST/V1/Items.pm >+++ b/Koha/REST/V1/Items.pm >@@ -148,4 +148,125 @@ 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'}; >+ 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'); >+ 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/admin/columns_settings.yml b/admin/columns_settings.yml >index a838a19f9b..10c70abad6 100644 >--- a/admin/columns_settings.yml >+++ b/admin/columns_settings.yml >@@ -509,6 +509,28 @@ modules: > - > columnname: checkin_on > >+ bundle_tables: >+ columns: >+ - >+ columnname: title >+ cannot_be_toggled: 1 >+ - >+ columnname: author >+ - >+ columnname: collection_code >+ - >+ columnname: item_type >+ - >+ columnname: callnumber >+ - >+ columnname: external_id >+ - >+ 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..b525c16484 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 >diff --git a/api/v1/swagger/paths/items.yaml b/api/v1/swagger/paths/items.yaml >index 66fc1ee6a8..38399e9908 100644 >--- a/api/v1/swagger/paths/items.yaml >+++ b/api/v1/swagger/paths/items.yaml >@@ -93,6 +93,163 @@ > 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 >+"/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 88e3bef4bd..a90177a5b0 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: >@@ -153,6 +155,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 e4d84aa0ea..e9af6d2e73 100755 >--- a/catalogue/detail.pl >+++ b/catalogue/detail.pl >@@ -173,6 +173,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' }); >@@ -415,6 +420,15 @@ foreach my $item (@items) { > } > } > >+ if ($item_object->is_bundle) { >+ $itemfields{bundles} = 1; >+ $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/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >index 49a5ce8767..1580ee1214 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >@@ -2355,6 +2355,10 @@ td { > display: block; > } > >+.bundled { >+ display: block; >+} >+ > .datedue { > color: #999; > display: block; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >index 41399d7c97..30b451a373 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref >@@ -165,6 +165,10 @@ Cataloging: > - and record's last modifier name in MARC subfield > - pref: MarcFieldForModifierName > - ". <br/><strong>NOTE:</strong> Use a dollar sign between field and subfield like 123$a." >+ - >+ - Use the NOT_LOAN authorised value >+ - pref: BundleNotLoanValue >+ - to represent 'added to bundle' when an item is attached to bundle. > Display: > - > - 'Separate main entry and subdivisions with ' >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 917a7ef246..c040976989 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt >@@ -354,12 +354,12 @@ > [% IF ( analyze ) %]<th id="[% tab | html %]_usedin" data-colname="[% tab | html %]_usedin">Used in</th><th></th>[% END %] > [% IF ( ShowCourseReserves ) %]<th id="[% tab | html %]_course_reserves" data-colname="[% tab | html %]_course_reserves">Course reserves</th>[% END %] > [% IF ( SpineLabelShowPrintOnBibDetails ) %]<th id="[% tab | html %]_spinelabel" data-colname="[% tab | html %]_spinelabel" class="NoSort">Spine label</th>[% END %] >- [% IF ( CAN_user_editcatalogue_edit_items ) %]<th id="[% tab | html %]_actions" data-colname="[% tab | html %]_actions"class="NoSort"> </th>[% END %] >+ [% IF ( CAN_user_editcatalogue_edit_items ) %]<th id="[% tab | html %]_actions" data-colname="[% tab | html %]_actions"class="NoSort noExport"> </th>[% END %] > </tr> > </thead> > <tbody> > [% FOREACH item IN items %] >- <tr> >+ <tr id="item_[% item.itemnumber | html %]" data-itemnumber="[% item.itemnumber | html %]"> > [% IF (StaffDetailItemSelection) %] > <td style="text-align:center;vertical-align:middle"> > <input type="checkbox" value="[% item.itemnumber | html %]" name="itemnumber" /> >@@ -530,6 +530,11 @@ Note that permanent location is a code, and location may be an authval. > [% IF ( item.restricted ) %] > <span class="restricted">([% item.restrictedvalue | html %])</span> > [% END %] >+ >+ [% IF ( item.bundle_host ) %] >+ <span class="bundled">In bundle: [% INCLUDE 'biblio-title.inc' biblio = item.bundle_host.biblio link = 1 %]</span> >+ [% END %] >+ > </td> > <td class="datelastseen" data-order="[% item.datelastseen | html %]">[% item.datelastseen | $KohaDates %]</td> > <td class="dateaccessioned" data-order="[% item.dateaccessioned | html %]">[% item.dateaccessioned | $KohaDates %]</td> >@@ -614,6 +619,9 @@ Note that permanent location is a code, and location may be an authval. > <a class="btn btn-default btn-xs" href="/cgi-bin/koha/cataloguing/additem.pl?op=edititem&biblionumber=[% item.biblionumber | html %]&itemnumber=[% item.itemnumber | html %]#edititem"><i class="fa fa-pencil"></i> Edit</a> > [% END %] > [% END %] >+ [% IF bundlesEnabled %] >+ <button class="btn btn-default btn-xs details-control"><i class="fa fa-folder"></i> Manage bundle</button> >+ [% END %] > </td> > [% END %] > </tr> >@@ -1055,6 +1063,37 @@ Note that permanent location is a code, and location may be an authval. > > [% END %] > >+ [% IF bundlesEnabled %] >+ <div class="modal" id="bundleItemsModal" tabindex="-1" role="dialog" aria-labelledby="bundleItemsLabel"> >+ <form id="bundleItemsForm" action=""> >+ <div class="modal-dialog" role="document"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">Ã</button> >+ <h3 id="bundleItemsLabel">Add to bundle</h3> >+ </div> >+ <div class="modal-body"> >+ <div id="result"></div> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label class="required" for="external_id">Item barcode: </label> >+ <input type="text" id="external_id" name="external_id" required="required"> >+ <span class="required">Required</span> >+ </li> >+ </ol> >+ </fieldset> >+ </div> >+ <div class="modal-footer"> >+ <button type="submit" class="btn btn-default">Submit</button> >+ <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button> >+ </div> >+ </div> >+ </div> >+ </form> >+ </div> >+ [% END %] >+ > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'catalog-strings.inc' %] > [% Asset.js("js/catalog.js") | $raw %] >@@ -1360,6 +1399,7 @@ 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' %] > [% Asset.js("js/browser.js") | $raw %] > [% Asset.js("js/table_filters.js") | $raw %] > <script> >@@ -1367,7 +1407,198 @@ Note that permanent location is a code, and location may be an authval. > browser = KOHA.browser('[% searchid | html %]', parseInt(biblionumber, 10)); > browser.show(); > >+ [% IF bundlesEnabled %] >+ var bundle_settings = [% TablesSettings.GetTableSettings('catalogue', 'detail','bundle_tables','json') | $raw %]; >+ [% END %] > $(document).ready(function() { >+ >+ [% IF bundlesEnabled %] // Bundle handling >+ function createChild ( row, itemnumber ) { >+ >+ // Toolbar >+ var bundle_toolbar = $('<div id="toolbar" class="btn-toolbar"><a class="btn btn-default" data-toggle="modal" data-target="#bundleItemsModal" data-item="' + itemnumber + '"><i class="fa fa-plus"></i> Add to bundle</a></div>'); >+ >+ // This is the table we'll convert into a DataTable >+ var bundles_table = $('<table class="display tbundle" data-itemnumber="'+itemnumber+'" id="bundle_table_'+itemnumber+'" width="100%"/>'); >+ >+ // Display it the child row >+ row.child( bundle_toolbar.add(bundles_table) ).show(); >+ >+ // Initialise as a DataTable >+ var bundle_table_url = "/api/v1/items/" + itemnumber + "/bundled_items?"; >+ var bundle_table = bundles_table.kohaTable({ >+ "ajax": { >+ "url": bundle_table_url >+ }, >+ "header_filter": false, >+ "embed": [ >+ "biblio" >+ ], >+ "order": [[ 1, "asc" ]], >+ "columnDefs": [ { >+ "targets": [0,1,2,3,4,5], >+ "render": function (data, type, row, meta) { >+ if ( data && type == 'display' ) { >+ return data.escapeHtml(); >+ } >+ return data; >+ } >+ } ], >+ "columns": [ >+ { >+ "data": "biblio.title:biblio.medium", >+ "title": "Title", >+ "searchable": true, >+ "orderable": true, >+ "render": function(data, type, row, meta) { >+ var title = ""; >+ if ( row.biblio.title ) { >+ title = title.concat('<span class="biblio-title">',row.biblio.title,'</span>'); >+ } >+ if ( row.biblio.subtitle ) { >+ title = title.concat('<span class="biblio-subtitle">',row.biblio.subtitle,'</span>'); >+ } >+ if ( row.biblio.medium ) { >+ title = title.concat('<span class="biblio-medium">',row.biblio.medium,'</span>'); >+ } >+ return title; >+ } >+ }, >+ { >+ "data": "biblio.author", >+ "title": "Author", >+ "searchable": true, >+ "orderable": true, >+ }, >+ { >+ "data": "collection_code", >+ "title": "Collection code", >+ "searchable": true, >+ "orderable": true, >+ }, >+ { >+ "data": "item_type", >+ "title": "Item Type", >+ "searchable": false, >+ "orderable": true, >+ }, >+ { >+ "data": "callnumber", >+ "title": "Callnumber", >+ "searchable": true, >+ "orderable": true, >+ }, >+ { >+ "data": "external_id", >+ "title": "Barcode", >+ "searchable": true, >+ "orderable": true, >+ }, >+ { >+ "data": "lost_status:last_seen_date", >+ "title": "Status", >+ "searchable": false, >+ "orderable": true, >+ "render": function(data, type, row, meta) { >+ if ( row.lost_status ) { >+ return "Lost: " + row.lost_status; >+ } >+ return ""; >+ } >+ }, >+ { >+ "data": function( row, type, val, meta ) { >+ var result = '<button class="btn btn-default btn-xs remove" role="button" data-itemnumber="'+row.item_id+'"><i class="fa fa-minus" aria-hidden="true"></i> '+_("Remove")+'</button>\n'; >+ return result; >+ }, >+ "title": "Actions", >+ "searchable": false, >+ "orderable": false, >+ "class": "noExport" >+ } >+ ] >+ }, bundle_settings, 1); >+ >+ $(".tbundle").on("click", ".remove", function(){ >+ var bundle_table = $(this).closest('table'); >+ var host_itemnumber = bundle_table.data('itemnumber'); >+ var component_itemnumber = $(this).data('itemnumber'); >+ var unlink_item_url = "/api/v1/items/" + host_itemnumber + "/bundled_items/" + component_itemnumber; >+ $.ajax({ >+ type: "DELETE", >+ url: unlink_item_url, >+ success: function(){ >+ bundle_table.DataTable({ 'retrieve': true }).draw(false); >+ } >+ }); >+ }); >+ >+ return; >+ } >+ >+ var bundle_changed; >+ var bundle_form_active; >+ $("#bundleItemsModal").on("shown.bs.modal", function(e){ >+ var button = $(e.relatedTarget); >+ var item_id = button.data('item'); >+ $("#result").replaceWith('<div id="result"></div>'); >+ $("#bundleItemsForm").attr('action', '/api/v1/items/' + item_id + '/bundled_items'); >+ $("#external_id").focus(); >+ bundle_changed = 0; >+ bundle_form_active = item_id; >+ }); >+ >+ $("#bundleItemsForm").submit(function(event) { >+ >+ /* stop form from submitting normally */ >+ event.preventDefault(); >+ >+ /* get the action attribute from the <form action=""> element */ >+ var $form = $(this), >+ url = $form.attr('action'); >+ >+ /* Send the data using post with external_id */ >+ var posting = $.post({ >+ url: url, >+ data: JSON.stringify({ external_id: $('#external_id').val()}), >+ contentType: "application/json; charset=utf-8", >+ dataType: "json" >+ }); >+ >+ /* Report the results */ >+ posting.done(function(data) { >+ var barcode = $('#external_id').val(); >+ $('#result').replaceWith('<div id="result" class="alert alert-success">Success: Added '+barcode+'</div>'); >+ $('#external_id').val('').focus(); >+ bundle_changed = 1; >+ }); >+ posting.fail(function(data) { >+ var barcode = $('#external_id').val(); >+ if ( data.status === 409 ) { >+ var response = data.responseJSON; >+ if ( response.key === "PRIMARY" ) { >+ $('#result').replaceWith('<div id="result" class="alert alert-warning">Warning: Item '+barcode+' already attached</div>'); >+ } else { >+ $('#result').replaceWith('<div id="result" class="alert alert-danger">Failure: Item '+barcode+' belongs to another bundle</div>'); >+ } >+ } else { >+ $('#result').replaceWith('<div id="result" class="alert alert-danger">Failure: Check the logs for details</div>'); >+ } >+ $('#external_id').val('').focus(); >+ }); >+ }); >+ >+ $("#bundleItemsModal").on("hidden.bs.modal", function(e){ >+ if ( bundle_changed ) { >+ $('#bundle_table_'+bundle_form_active).DataTable({ 'retrieve': true }).ajax.reload(); >+ } >+ bundle_form_active = 0; >+ bundle_changed = 0; >+ }); >+ >+ // End bundle handling >+ [% END %] >+ > var table_names = [ 'holdings_table', 'otherholdings_table' ]; > var table_settings = [ [% TablesSettings.GetTableSettings('catalogue', 'detail','holdings_table','json') | $raw %], [% TablesSettings.GetTableSettings('catalogue', 'detail','otherholdings_table','json') | $raw %] ]; > var has_images = [ "[% itemloop_has_images | html %]", "[% otheritemloop_has_images | html %]" ]; >@@ -1383,6 +1614,28 @@ Note that permanent location is a code, and location may be an authval. > "sDom": 'C<"top pager"ilpfB><"#filter_c">tr<"bottom pager"ip>', > }; > var table = KohaTable( table_name, dt_parameters, table_settings[index], 'with_filters' ); >+ >+ [% IF bundlesEnabled %] >+ // Add event listener for opening and closing bundle details >+ $('#' + table_name + ' tbody').on('click', 'button.details-control', function () { >+ var tr = $(this).closest('tr'); >+ var dTable = $(this).closest('table').DataTable({ 'retrieve': true }); >+ >+ var itemnumber = tr.data('itemnumber'); >+ var row = dTable.row( tr ); >+ >+ if ( row.child.isShown() ) { >+ // This row is already open - close it >+ row.child.hide(); >+ tr.removeClass('shown'); >+ } >+ else { >+ // Open this row >+ createChild(row, itemnumber); >+ tr.addClass('shown'); >+ } >+ } ); >+ [% END %] > }); > > [% IF Koha.Preference('AcquisitionDetails') %] >@@ -1404,6 +1657,7 @@ Note that permanent location is a code, and location may be an authval. > "sPaginationType": "full" > })); > [% END %] >+ > }); > > [% IF (found1) %] >-- >2.20.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 28854
:
124319
|
124498
|
124616
|
125200
|
125201
|
125202
|
125203
|
125204
|
125205
|
125206
|
126103
|
126104
|
126105
|
126106
|
126107
|
126108
|
126109
|
126110
|
126148
|
126149
|
126150
|
126151
|
126152
|
126153
|
126154
|
126155
|
126166
|
126167
|
126168
|
126169
|
126170
|
126171
|
126172
|
126173
|
127498
|
127499
|
127500
|
127501
|
127502
|
127503
|
127504
|
127505
|
127659
|
127660
|
127661
|
127662
|
127663
|
127664
|
127665
|
127666
|
127829
|
127830
|
127831
|
127832
|
127833
|
127834
|
127835
|
127836
|
127837
|
127838
|
130313
|
130314
|
130315
|
130316
|
130317
|
130318
|
130319
|
130320
|
130321
|
130322
|
130323
|
130324
|
130325
|
130326
|
130327
|
130328
|
130329
|
130330
|
130331
|
130332
|
130456
|
132703
|
132704
|
132705
|
132706
|
132707
|
132708
|
132709
|
132710
|
132711
|
132712
|
132713
|
132714
|
132715
|
132716
|
132717
|
132718
|
132719
|
132720
|
132721
|
132722
|
132723
|
135019
|
135020
|
135021
|
135022
|
135023
|
135024
|
135025
|
135026
|
135027
|
135028
|
135029
|
135030
|
135031
|
135032
|
135033
|
135034
|
135035
|
135036
|
135037
|
136226
|
136227
|
136228
|
136229
|
136230
|
136231
|
136232
|
136233
|
136234
|
136235
|
136236
|
136237
|
136238
|
136239
|
136240
|
136241
|
136242
|
136243
|
136244
|
136249
|
136253
|
136254
|
136264
|
136265
|
136266
|
136267
|
136268
|
136269
|
136270
|
136271
|
136272
|
136273
|
136274
|
136275
|
136276
|
136277
|
136278
|
136279
|
136280
|
136281
|
136282
|
136283
|
136284
|
136285
|
136287
|
136340
|
136341
|
136342
|
136343
|
136344
|
136345
|
136346
|
136347
|
136348
|
136349
|
136350
|
136351
|
136352
|
136353
|
136354
|
136355
|
136356
|
136357
|
136358
|
136359
|
136360
|
136361
|
136362
|
136363
|
136364
|
136365
|
136366
|
136367
|
136368
|
136369
|
136370
|
136371
|
136498
|
136529
|
136530
|
136613
|
136614
|
136615
|
136616
|
136617
|
136618
|
136619
|
136620
|
136621
|
136622
|
136623
|
136624
|
136625
|
136626
|
136627
|
136628
|
136629
|
136630
|
136631
|
136632
|
136633
|
136634
|
136635
|
136636
|
136637
|
136638
|
136639
|
136640
|
136641
|
136642
|
136643
|
136644
|
136645
|
136646
|
136647
|
136648
|
136649
|
136650
|
136651
|
136652
|
136654
|
136655
|
136806
|
136809
|
136811
|
136814
|
136817
|
136821
|
136824
|
136826
|
136829
|
136832
|
136835
|
136838
|
136841
|
136843
|
136846
|
136849
|
136850
|
136851
|
136852
|
136853
|
136854
|
136855
|
136856
|
136857
|
136858
|
136859
|
136860
|
136861
|
136862
|
136863
|
136864
|
136865
|
136866
|
136867
|
136868
|
136869
|
136870
|
136871
|
136872
|
136873
|
136874
|
136875
|
136876
|
136877
|
136905
|
136906
|
136907
|
136908
|
136909
|
136910
|
136911
|
136912
|
136913
|
136914
|
136915
|
136916
|
136917
|
136918
|
136919
|
136920
|
136921
|
136922
|
136923
|
136924
|
136925
|
136926
|
136927
|
136928
|
136929
|
136930
|
136931
|
136932
|
136933
|
136934
|
136935
|
136936
|
136937
|
136938
|
136939
|
136940
|
136941
|
136942
|
136943
|
136944
|
136945
|
136946
|
136947
|
136949
|
136978
|
136979
|
136980
|
136981
|
136982
|
136985
|
136988
|
136989
|
136990
|
136991
|
136992
|
136993
|
136994
|
136995
|
136996
|
136997
|
136998
|
136999
|
137000
|
137001
|
137002
|
137003
|
137004
|
137005
|
137006
|
137007
|
137008
|
137009
|
137010
|
137011
|
137012
|
137013
|
137014
|
137015
|
137016
|
137017
|
137018
|
137019
|
137020
|
137021
|
137022
|
137461
|
137462
|
137463
|
137464
|
137465
|
137466
|
137467
|
137468
|
137469
|
137470
|
137471
|
137472
|
137473
|
137474
|
137475
|
137476
|
137477
|
137478
|
137479
|
137480
|
137481
|
137482
|
137483
|
137484
|
137485
|
137486
|
137487
|
137488
|
137489
|
137490
|
137491
|
137492
|
137493
|
137494
|
137495
|
137496
|
137497
|
137498
|
137499
|
137500
|
137501
|
137503
|
137506
|
137524
|
137525
|
137527
|
137528
|
137529
|
137530
|
137531
|
137532
|
137533
|
137534
|
137535
|
137536
|
137537
|
137538
|
137540
|
137541
|
137542
|
137543
|
137544
|
137545
|
137546
|
137547
|
137548
|
137549
|
137550
|
137551
|
137552
|
137553
|
137554
|
137555
|
137556
|
137557
|
137558
|
137559
|
137560
|
137561
|
137562
|
137563
|
137564
|
137565
|
137566
|
137567
|
137568
|
137569
|
137570
|
137571
|
137684
|
137685
|
139347