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 (-2 / +9 lines)
Lines 20-33 use Modern::Perl; Link Here
20
use Koha::Exception;
20
use Koha::Exception;
21
21
22
use Exception::Class (
22
use Exception::Class (
23
24
    'Koha::Exceptions::Item::Bundle' => {
23
    'Koha::Exceptions::Item::Bundle' => {
25
        isa => 'Koha::Exception',
24
        isa => 'Koha::Exception',
26
    },
25
    },
27
    'Koha::Exceptions::Item::Bundle::IsBundle' => {
26
    'Koha::Exceptions::Item::Bundle::IsBundle' => {
28
        isa         => 'Koha::Exceptions::Item::Bundle',
27
        isa         => 'Koha::Exceptions::Item::Bundle',
29
        description => "A bundle cannot be added to a bundle",
28
        description => "A bundle cannot be added to a bundle",
30
    }
29
    },
30
    'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut' => {
31
        isa         => 'Koha::Exceptions::Item::Bundle',
32
        description => 'Someone tried to add a checked out item to a bundle',
33
    },
31
);
34
);
32
35
33
=head1 NAME
36
=head1 NAME
Lines 44-49 Generic Item::Bundle exception Link Here
44
47
45
Exception to be used when attempting to add one bundle into another.
48
Exception to be used when attempting to add one bundle into another.
46
49
50
=head2 Koha::Exceptions::Item::Bundle::ItemIsCheckedOut
51
52
Exception to be used when attempting to add a checked out item to a bundle.
53
47
=cut
54
=cut
48
55
49
1;
56
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::Exceptions::Item::Bundle;
43
use Koha::Exceptions::Item::Bundle;
Lines 1704-1710 Adds the bundle_item passed to this item Link Here
1704
=cut
1706
=cut
1705
1707
1706
sub add_to_bundle {
1708
sub add_to_bundle {
1707
    my ( $self, $bundle_item ) = @_;
1709
    my ( $self, $bundle_item, $options ) = @_;
1710
1711
    $options //= {};
1708
1712
1709
    Koha::Exceptions::Item::Bundle::IsBundle->throw()
1713
    Koha::Exceptions::Item::Bundle::IsBundle->throw()
1710
      if ( $self->itemnumber eq $bundle_item->itemnumber
1714
      if ( $self->itemnumber eq $bundle_item->itemnumber
Lines 1718-1723 sub add_to_bundle { Link Here
1718
    try {
1722
    try {
1719
        $schema->txn_do(
1723
        $schema->txn_do(
1720
            sub {
1724
            sub {
1725
                my $checkout = $bundle_item->checkout;
1726
                if ($checkout) {
1727
                    unless ($options->{force_checkin}) {
1728
                        Koha::Exceptions::Item::Bundle::ItemIsCheckedOut->throw();
1729
                    }
1730
1731
                    my $branchcode = C4::Context->userenv->{'branch'};
1732
                    my ($success) = C4::Circulation::AddReturn($bundle_item->barcode, $branchcode);
1733
                    unless ($success) {
1734
                        Koha::Exceptions::Checkin::FailedCheckin->throw();
1735
                    }
1736
                }
1737
1721
                $self->_result->add_to_item_bundles_hosts(
1738
                $self->_result->add_to_item_bundles_hosts(
1722
                    { item => $bundle_item->itemnumber } );
1739
                    { item => $bundle_item->itemnumber } );
1723
1740
Lines 1769-1775 sub add_to_bundle { Link Here
1769
            $_->rethrow();
1786
            $_->rethrow();
1770
        }
1787
        }
1771
        else {
1788
        else {
1772
            $_;
1789
            $_->rethrow();
1773
        }
1790
        }
1774
    };
1791
    };
1775
}
1792
}
(-)a/Koha/REST/V1/Items.pm (-3 / +19 lines)
Lines 277-283 sub add_to_bundle { Link Here
277
    }
277
    }
278
278
279
    return try {
279
    return try {
280
        my $link = $item->add_to_bundle($bundle_item);
280
        my $force_checkin = $c->validation->param('body')->{'force_checkin'};
281
        my $link = $item->add_to_bundle($bundle_item, { force_checkin => $force_checkin });
281
        return $c->render(
282
        return $c->render(
282
            status  => 201,
283
            status  => 201,
283
            openapi => $bundle_item
284
            openapi => $bundle_item
Lines 300-307 sub add_to_bundle { Link Here
300
                    error => 'Bundles cannot be nested'
301
                    error => 'Bundles cannot be nested'
301
                }
302
                }
302
            );
303
            );
303
        }
304
        } elsif (ref($_) eq 'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut') {
304
        else {
305
            return $c->render(
306
                status  => 409,
307
                openapi => {
308
                    error => 'Item is checked out',
309
                    key   => 'checked_out'
310
                }
311
            );
312
        } elsif (ref($_) eq 'Koha::Exceptions::Checkin::FailedCheckin') {
313
            return $c->render(
314
                status  => 409,
315
                openapi => {
316
                    error => 'Item cannot be checked in',
317
                    key   => 'failed_checkin'
318
                }
319
            );
320
        } else {
305
            $c->unhandled_exception($_);
321
            $c->unhandled_exception($_);
306
        }
322
        }
307
    };
323
    };
(-)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 1806-1811 Note that permanent location is a code, and location may be an authval. Link Here
1806
                      }
1815
                      }
1807
                      $('#external_id').val('').focus();
1816
                      $('#external_id').val('').focus();
1808
                  });
1817
                  });
1818
            }
1819
1820
            $("#addToBundleForm").submit(function(event) {
1821
                  /* stop form from submitting normally */
1822
                  event.preventDefault();
1823
1824
                  const url = this.action;
1825
                  const data = { external_id: this.elements.external_id.value };
1826
1827
                  addToBundle(url, data);
1809
            });
1828
            });
1810
1829
1811
            $("#addToBundleModal").on("hidden.bs.modal", function(e){
1830
            $("#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 => 6;
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 242-247 subtest 'add_to_bundle tests' => sub { Link Here
242
    'Koha::Exceptions::Item::Bundle::IsBundle',
247
    'Koha::Exceptions::Item::Bundle::IsBundle',
243
      'Exception thrown if you try to add a bundle host to a bundle item';
248
      'Exception thrown if you try to add a bundle host to a bundle item';
244
249
250
    my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
251
    C4::Circulation::AddIssue( $patron->unblessed, $bundle_item2->barcode );
252
    throws_ok { $host_item->add_to_bundle($bundle_item2) }
253
    'Koha::Exceptions::Item::Bundle::ItemIsCheckedOut',
254
      'Exception thrown if you try to add a checked out item';
255
256
    $bundle_item2->withdrawn(1)->store;
257
    t::lib::Mocks::mock_preference( 'BlockReturnOfWithdrawnItems', 1 );
258
    throws_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
259
    'Koha::Exceptions::Checkin::FailedCheckin',
260
      'Exception thrown if you try to add a checked out item using
261
      "force_checkin" and the return is not possible';
262
263
    $bundle_item2->withdrawn(0)->store;
264
    lives_ok { $host_item->add_to_bundle( $bundle_item2, { force_checkin => 1 } ) }
265
    'No exception if you try to add a checked out item using "force_checkin" and the return is possible';
266
267
    $bundle_item2->discard_changes;
268
    ok( !$bundle_item2->checkout, 'Item is not checked out after being added to a bundle' );
269
245
    $schema->storage->txn_rollback;
270
    $schema->storage->txn_rollback;
246
};
271
};
247
272
248
- 

Return to bug 32121