View | Details | Raw Unified | Return to bug 32121
Collapse All | Expand All

(-)a/Koha/Exceptions/Checkin.pm (+32 lines)
Line 0 Link Here
1
package Koha::Exceptions::Checkin;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Exception;
21
22
use Exception::Class (
23
    'Koha::Exceptions::Checkin' => {
24
        isa => 'Koha::Exception',
25
    },
26
    'Koha::Exceptions::Checkin::FailedCheckin' => {
27
        isa         => 'Koha::Exceptions::Checkin',
28
        description => "Checkin failed"
29
    },
30
);
31
32
1;
(-)a/Koha/Exceptions/Item/Bundle.pm (+32 lines)
Line 0 Link Here
1
package Koha::Exceptions::Item::Bundle;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Exception;
21
22
use Exception::Class (
23
    'Koha::Exceptions::Item::Bundle' => {
24
        isa => 'Koha::Exception',
25
    },
26
    'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut' => {
27
        isa => 'Koha::Exceptions::Item::Bundle',
28
        description => 'Someone tried to add a checked out item to a bundle',
29
    },
30
);
31
32
1;
(-)a/Koha/Item.pm (-2 / +19 lines)
Lines 36-41 use Koha::Biblio::ItemGroups; Link Here
36
use Koha::Checkouts;
36
use Koha::Checkouts;
37
use Koha::CirculationRules;
37
use Koha::CirculationRules;
38
use Koha::CoverImages;
38
use Koha::CoverImages;
39
use Koha::Exceptions::Checkin;
40
use Koha::Exceptions::Item::Bundle;
39
use Koha::Exceptions::Item::Transfer;
41
use Koha::Exceptions::Item::Transfer;
40
use Koha::Item::Attributes;
42
use Koha::Item::Attributes;
41
use Koha::Item::Transfer::Limits;
43
use Koha::Item::Transfer::Limits;
Lines 1705-1711 Adds the bundle_item passed to this item Link Here
1705
=cut
1707
=cut
1706
1708
1707
sub add_to_bundle {
1709
sub add_to_bundle {
1708
    my ( $self, $bundle_item ) = @_;
1710
    my ( $self, $bundle_item, $options ) = @_;
1711
1712
    $options //= {};
1709
1713
1710
    my $schema = Koha::Database->new->schema;
1714
    my $schema = Koha::Database->new->schema;
1711
1715
Lines 1714-1719 sub add_to_bundle { Link Here
1714
    try {
1718
    try {
1715
        $schema->txn_do(
1719
        $schema->txn_do(
1716
            sub {
1720
            sub {
1721
                my $checkout = $bundle_item->checkout;
1722
                if ($checkout) {
1723
                    unless ($options->{force_checkin}) {
1724
                        Koha::Exceptions::Item::Bundle::ItemIsCheckedOut->throw();
1725
                    }
1726
1727
                    my $branchcode = C4::Context->userenv->{'branch'};
1728
                    my ($success) = C4::Circulation::AddReturn($bundle_item->barcode, $branchcode);
1729
                    unless ($success) {
1730
                        Koha::Exceptions::Checkin::FailedCheckin->throw();
1731
                    }
1732
                }
1733
1717
                $self->_result->add_to_item_bundles_hosts(
1734
                $self->_result->add_to_item_bundles_hosts(
1718
                    { item => $bundle_item->itemnumber } );
1735
                    { item => $bundle_item->itemnumber } );
1719
1736
Lines 1765-1771 sub add_to_bundle { Link Here
1765
            $_->rethrow();
1782
            $_->rethrow();
1766
        }
1783
        }
1767
        else {
1784
        else {
1768
            $_;
1785
            $_->rethrow();
1769
        }
1786
        }
1770
    };
1787
    };
1771
}
1788
}
(-)a/Koha/REST/V1/Items.pm (-3 / +19 lines)
Lines 213-219 sub add_to_bundle { Link Here
213
    }
213
    }
214
214
215
    return try {
215
    return try {
216
        my $link = $item->add_to_bundle($bundle_item);
216
        my $force_checkin = $c->validation->param('body')->{'force_checkin'};
217
        my $link = $item->add_to_bundle($bundle_item, { force_checkin => $force_checkin });
217
        return $c->render(
218
        return $c->render(
218
            status  => 201,
219
            status  => 201,
219
            openapi => $bundle_item
220
            openapi => $bundle_item
Lines 228-235 sub add_to_bundle { Link Here
228
                    key   => $_->duplicate_id
229
                    key   => $_->duplicate_id
229
                }
230
                }
230
            );
231
            );
231
        }
232
        } elsif (ref($_) eq 'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut') {
232
        else {
233
            return $c->render(
234
                status  => 409,
235
                openapi => {
236
                    error => 'Item is checked out',
237
                    key   => 'checked_out'
238
                }
239
            );
240
        } elsif (ref($_) eq 'Koha::Exceptions::Checkin::FailedCheckin') {
241
            return $c->render(
242
                status  => 409,
243
                openapi => {
244
                    error => 'Item cannot be checked in',
245
                    key   => 'failed_checkin'
246
                }
247
            );
248
        } else {
233
            $c->unhandled_exception($_);
249
            $c->unhandled_exception($_);
234
        }
250
        }
235
    };
251
    };
(-)a/api/v1/swagger/definitions/bundle_link.yaml (+4 lines)
Lines 11-14 properties: Link Here
11
      - string
11
      - string
12
      - "null"
12
      - "null"
13
    description: Item barcode
13
    description: Item barcode
14
  force_checkin:
15
    type:
16
      - boolean
17
      - "null"
14
additionalProperties: false
18
additionalProperties: false
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt (-12 / +31 lines)
Lines 1759-1794 Note that permanent location is a code, and location may be an authval. Link Here
1759
                bundle_form_active = item_id;
1759
                bundle_form_active = item_id;
1760
            });
1760
            });
1761
1761
1762
            $("#addToBundleForm").submit(function(event) {
1762
            function addToBundle (url, data) {
1763
1764
                  /* stop form from submitting normally */
1765
                  event.preventDefault();
1766
1767
                  /* get the action attribute from the <form action=""> element */
1768
                  var $form = $(this),
1769
                  url = $form.attr('action');
1770
1771
                  /* Send the data using post with external_id */
1763
                  /* Send the data using post with external_id */
1772
                  var posting = $.post({
1764
                  var posting = $.post({
1773
                      url: url,
1765
                      url: url,
1774
                      data: JSON.stringify({ external_id: $('#external_id').val()}),
1766
                      data: JSON.stringify(data),
1775
                      contentType: "application/json; charset=utf-8",
1767
                      contentType: "application/json; charset=utf-8",
1776
                      dataType: "json"
1768
                      dataType: "json"
1777
                  });
1769
                  });
1778
1770
1771
                  const barcode = data.external_id;
1772
1779
                  /* Report the results */
1773
                  /* Report the results */
1780
                  posting.done(function(data) {
1774
                  posting.done(function(data) {
1781
                      var barcode = $('#external_id').val();
1782
                      $('#addResult').replaceWith('<div id="addResult" class="alert alert-success">'+_("Success: Added '%s'").format(barcode)+'</div>');
1775
                      $('#addResult').replaceWith('<div id="addResult" class="alert alert-success">'+_("Success: Added '%s'").format(barcode)+'</div>');
1783
                      $('#external_id').val('').focus();
1776
                      $('#external_id').val('').focus();
1784
                      bundle_changed = 1;
1777
                      bundle_changed = 1;
1785
                  });
1778
                  });
1786
                  posting.fail(function(data) {
1779
                  posting.fail(function(data) {
1787
                      var barcode = $('#external_id').val();
1788
                      if ( data.status === 409 ) {
1780
                      if ( data.status === 409 ) {
1789
                          var response = data.responseJSON;
1781
                          var response = data.responseJSON;
1790
                          if ( response.key === "PRIMARY" ) {
1782
                          if ( response.key === "PRIMARY" ) {
1791
                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-warning">'+_("Warning: Item '%s' already attached").format(barcode)+'</div>');
1783
                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-warning">'+_("Warning: Item '%s' already attached").format(barcode)+'</div>');
1784
                          } else if (response.key === 'checked_out') {
1785
                              const button = $('<button type="button">')
1786
                                .addClass('btn btn-xs')
1787
                                .text(__('Check in and add to bundle'))
1788
                                .on('click', function () {
1789
                                    addToBundle(url, { external_id: barcode, force_checkin: true });
1790
                                });
1791
                              $('#addResult')
1792
                                .empty()
1793
                                .attr('class', 'alert alert-warning')
1794
                                .append(__x('Warning: Item {barcode} is checked out', { barcode }))
1795
                                .append(' ', button);
1796
                          } else if (response.key === 'failed_checkin') {
1797
                              $('#addResult')
1798
                                .empty()
1799
                                .attr('class', 'alert alert-danger')
1800
                                .append(__x('Failure: Item {barcode} cannot be checked in', { barcode }))
1792
                          } else {
1801
                          } else {
1793
                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Item '%s' belongs to another bundle").format(barcode)+'</div>');
1802
                              $('#addResult').replaceWith('<div id="addResult" class="alert alert-danger">'+_("Failure: Item '%s' belongs to another bundle").format(barcode)+'</div>');
1794
                          }
1803
                          }
Lines 1799-1804 Note that permanent location is a code, and location may be an authval. Link Here
1799
                      }
1808
                      }
1800
                      $('#external_id').val('').focus();
1809
                      $('#external_id').val('').focus();
1801
                  });
1810
                  });
1811
            }
1812
1813
            $("#addToBundleForm").submit(function(event) {
1814
                  /* stop form from submitting normally */
1815
                  event.preventDefault();
1816
1817
                  const url = this.action;
1818
                  const data = { external_id: this.elements.external_id.value };
1819
1820
                  addToBundle(url, data);
1802
            });
1821
            });
1803
1822
1804
            $("#addToBundleModal").on("hidden.bs.modal", function(e){
1823
            $("#addToBundleModal").on("hidden.bs.modal", function(e){
(-)a/t/db_dependent/Koha/Item.t (-2 / +26 lines)
Lines 213-224 subtest 'bundle_host tests' => sub { Link Here
213
};
213
};
214
214
215
subtest 'add_to_bundle tests' => sub {
215
subtest 'add_to_bundle tests' => sub {
216
    plan tests => 3;
216
    plan tests => 7;
217
217
218
    $schema->storage->txn_begin;
218
    $schema->storage->txn_begin;
219
219
220
    t::lib::Mocks::mock_preference( 'BundleNotLoanValue', 1 );
220
    t::lib::Mocks::mock_preference( 'BundleNotLoanValue', 1 );
221
221
222
    my $library = $builder->build_object({ class => 'Koha::Libraries' });
223
    t::lib::Mocks::mock_userenv({
224
        branchcode => $library->branchcode
225
    });
226
222
    my $host_item = $builder->build_sample_item();
227
    my $host_item = $builder->build_sample_item();
223
    my $bundle_item1 = $builder->build_sample_item();
228
    my $bundle_item1 = $builder->build_sample_item();
224
    my $bundle_item2 = $builder->build_sample_item();
229
    my $bundle_item2 = $builder->build_sample_item();
Lines 230-235 subtest 'add_to_bundle tests' => sub { Link Here
230
    'Koha::Exceptions::Object::DuplicateID',
235
    'Koha::Exceptions::Object::DuplicateID',
231
      'Exception thrown if you try to add the same item twice';
236
      'Exception thrown if you try to add the same item twice';
232
237
238
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
239
    C4::Circulation::AddIssue( $patron->unblessed, $bundle_item2->barcode );
240
    throws_ok { $host_item->add_to_bundle($bundle_item2) }
241
    'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut',
242
      'Exception thrown if you try to add a checked out item';
243
244
    $bundle_item2->withdrawn(1)->store;
245
    t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', 1 );
246
    throws_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
247
    'Koha::Exceptions::Checkin::FailedCheckin',
248
      'Exception thrown if you try to add a checked out item using
249
      "force_checkin" and the return is not possible';
250
251
    $bundle_item2->withdrawn(0)->store;
252
    lives_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
253
    'No exception if you try to add a checked out item using "force_checkin" and the return is possible';
254
255
    $bundle_item2->discard_changes;
256
    ok( !$bundle_item2->checkout, 'Item is not checked out after being added to a bundle' );
257
233
    $schema->storage->txn_rollback;
258
    $schema->storage->txn_rollback;
234
};
259
};
235
260
236
- 

Return to bug 32121