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

(-)a/Koha/Item.pm (+165 lines)
Lines 20-25 package Koha::Item; Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use List::MoreUtils qw( any );
22
use List::MoreUtils qw( any );
23
use Try::Tiny qw( catch try );
23
24
24
use Koha::Database;
25
use Koha::Database;
25
use Koha::DateUtils qw( dt_from_string output_pref );
26
use Koha::DateUtils qw( dt_from_string output_pref );
Lines 1428-1433 sub move_to_biblio { Link Here
1428
    return $to_biblionumber;
1429
    return $to_biblionumber;
1429
}
1430
}
1430
1431
1432
=head3 bundle_items
1433
1434
  my $bundle_items = $item->bundle_items;
1435
1436
Returns the items associated with this bundle
1437
1438
=cut
1439
1440
sub bundle_items {
1441
    my ($self) = @_;
1442
1443
    if ( !$self->{_bundle_items_cached} ) {
1444
        my $bundle_items = Koha::Items->search(
1445
            { 'item_bundles_item.host' => $self->itemnumber },
1446
            { join                     => 'item_bundles_item' } );
1447
        $self->{_bundle_items}        = $bundle_items;
1448
        $self->{_bundle_items_cached} = 1;
1449
    }
1450
1451
    return $self->{_bundle_items};
1452
}
1453
1454
=head3 is_bundle
1455
1456
  my $is_bundle = $item->is_bundle;
1457
1458
Returns whether the item is a bundle or not
1459
1460
=cut
1461
1462
sub is_bundle {
1463
    my ($self) = @_;
1464
    return $self->bundle_items->count ? 1 : 0;
1465
}
1466
1467
=head3 bundle_host
1468
1469
  my $bundle = $item->bundle_host;
1470
1471
Returns the bundle item this item is attached to
1472
1473
=cut
1474
1475
sub bundle_host {
1476
    my ($self) = @_;
1477
1478
    if ( !$self->{_bundle_host_cached} ) {
1479
        my $bundle_item_rs = $self->_result->item_bundles_item;
1480
        $self->{_bundle_host} =
1481
          $bundle_item_rs
1482
          ? Koha::Item->_new_from_dbic($bundle_item_rs->host)
1483
          : undef;
1484
        $self->{_bundle_host_cached} = 1;
1485
    }
1486
1487
    return $self->{_bundle_host};
1488
}
1489
1490
=head3 in_bundle
1491
1492
  my $in_bundle = $item->in_bundle;
1493
1494
Returns whether this item is currently in a bundle
1495
1496
=cut
1497
1498
sub in_bundle {
1499
    my ($self) = @_;
1500
    return $self->bundle_host ? 1 : 0;
1501
}
1502
1503
=head3 add_to_bundle
1504
1505
  my $link = $item->add_to_bundle($bundle_item);
1506
1507
Adds the bundle_item passed to this item
1508
1509
=cut
1510
1511
sub add_to_bundle {
1512
    my ( $self, $bundle_item ) = @_;
1513
1514
    my $schema = Koha::Database->new->schema;
1515
1516
    my $BundleNotLoanValue = C4::Context->preference('BundleNotLoanValue');
1517
1518
    try {
1519
        $schema->txn_do(
1520
            sub {
1521
                $self->_result->add_to_item_bundles_hosts(
1522
                    { item => $bundle_item->itemnumber } );
1523
1524
                $bundle_item->notforloan($BundleNotLoanValue)->store();
1525
            }
1526
        );
1527
    }
1528
    catch {
1529
1530
        # 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
1531
        if ( ref($_) eq 'DBIx::Class::Exception' ) {
1532
            warn $_->{msg};
1533
            if ( $_->{msg} =~ /Cannot add or update a child row: a foreign key constraint fails/ ) {
1534
                # FK constraints
1535
                # FIXME: MySQL error, if we support more DB engines we should implement this for each
1536
                if ( $_->{msg} =~ /FOREIGN KEY \(`(?<column>.*?)`\)/ ) {
1537
                    Koha::Exceptions::Object::FKConstraint->throw(
1538
                        error     => 'Broken FK constraint',
1539
                        broken_fk => $+{column}
1540
                    );
1541
                }
1542
            }
1543
            elsif (
1544
                $_->{msg} =~ /Duplicate entry '(.*?)' for key '(?<key>.*?)'/ )
1545
            {
1546
                Koha::Exceptions::Object::DuplicateID->throw(
1547
                    error        => 'Duplicate ID',
1548
                    duplicate_id => $+{key}
1549
                );
1550
            }
1551
            elsif ( $_->{msg} =~
1552
/Incorrect (?<type>\w+) value: '(?<value>.*)' for column \W?(?<property>\S+)/
1553
              )
1554
            {    # The optional \W in the regex might be a quote or backtick
1555
                my $type     = $+{type};
1556
                my $value    = $+{value};
1557
                my $property = $+{property};
1558
                $property =~ s/['`]//g;
1559
                Koha::Exceptions::Object::BadValue->throw(
1560
                    type     => $type,
1561
                    value    => $value,
1562
                    property => $property =~ /(\w+\.\w+)$/
1563
                    ? $1
1564
                    : $property
1565
                    ,    # results in table.column without quotes or backtics
1566
                );
1567
            }
1568
1569
            # Catch-all for foreign key breakages. It will help find other use cases
1570
            $_->rethrow();
1571
        }
1572
        else {
1573
            $_;
1574
        }
1575
    };
1576
}
1577
1578
=head3 remove_from_bundle
1579
1580
Remove this item from any bundle it may have been attached to.
1581
1582
=cut
1583
1584
sub remove_from_bundle {
1585
    my ($self) = @_;
1586
1587
    my $bundle_item_rs = $self->_result->item_bundles_item;
1588
    if ( $bundle_item_rs ) {
1589
        $bundle_item_rs->delete;
1590
        $self->notforloan(0)->store();
1591
        return 1;
1592
    }
1593
    return 0;
1594
}
1595
1431
=head2 Internal methods
1596
=head2 Internal methods
1432
1597
1433
=head3 _after_item_action_hooks
1598
=head3 _after_item_action_hooks
(-)a/Koha/REST/V1/Items.pm (+121 lines)
Lines 148-151 sub pickup_locations { Link Here
148
    };
148
    };
149
}
149
}
150
150
151
=head3 bundled_items
152
153
Controller function that handles bundled_items Koha::Item objects
154
155
=cut
156
157
sub bundled_items {
158
    my $c = shift->openapi->valid_input or return;
159
160
    my $item_id = $c->validation->param('item_id');
161
    my $item = Koha::Items->find( $item_id );
162
163
    unless ($item) {
164
        return $c->render(
165
            status  => 404,
166
            openapi => { error => "Item not found" }
167
        );
168
    }
169
170
    return try {
171
        my $items_set = $item->bundle_items;
172
        my $items     = $c->objects->search( $items_set );
173
        return $c->render(
174
            status  => 200,
175
            openapi => $items
176
        );
177
    }
178
    catch {
179
        $c->unhandled_exception($_);
180
    };
181
}
182
183
=head3 add_to_bundle
184
185
Controller function that handles adding items to this bundle
186
187
=cut
188
189
sub add_to_bundle {
190
    my $c = shift->openapi->valid_input or return;
191
192
    my $item_id = $c->validation->param('item_id');
193
    my $item = Koha::Items->find( $item_id );
194
195
    unless ($item) {
196
        return $c->render(
197
            status  => 404,
198
            openapi => { error => "Item not found" }
199
        );
200
    }
201
202
203
    my $bundle_item_id = $c->validation->param('body')->{'external_id'};
204
    my $bundle_item = Koha::Items->find( { barcode => $bundle_item_id } );
205
206
    unless ($bundle_item) {
207
        return $c->render(
208
            status  => 404,
209
            openapi => { error => "Bundle item not found" }
210
        );
211
    }
212
213
    return try {
214
        my $link = $item->add_to_bundle($bundle_item);
215
        return $c->render(
216
            status  => 201,
217
            openapi => $bundle_item
218
        );
219
    }
220
    catch {
221
        if ( ref($_) eq 'Koha::Exceptions::Object::DuplicateID' ) {
222
            return $c->render(
223
                status  => 409,
224
                openapi => {
225
                    error => 'Item is already bundled',
226
                    key   => $_->duplicate_id
227
                }
228
            );
229
        }
230
        else {
231
            $c->unhandled_exception($_);
232
        }
233
    };
234
}
235
236
=head3 remove_from_bundle
237
238
Controller function that handles removing items from this bundle
239
240
=cut
241
242
sub remove_from_bundle {
243
    my $c = shift->openapi->valid_input or return;
244
245
    my $item_id = $c->validation->param('item_id');
246
    my $item = Koha::Items->find( $item_id );
247
248
    unless ($item) {
249
        return $c->render(
250
            status  => 404,
251
            openapi => { error => "Item not found" }
252
        );
253
    }
254
255
    my $bundle_item_id = $c->validation->param('bundled_item_id');
256
    my $bundle_item = Koha::Items->find( { itemnumber => $bundle_item_id } );
257
258
    unless ($bundle_item) {
259
        return $c->render(
260
            status  => 404,
261
            openapi => { error => "Bundle item not found" }
262
        );
263
    }
264
265
    $bundle_item->remove_from_bundle;
266
    return $c->render(
267
        status  => 204,
268
        openapi => q{}
269
    );
270
}
271
151
1;
272
1;
(-)a/admin/columns_settings.yml (+22 lines)
Lines 464-469 modules: Link Here
464
            -
464
            -
465
              columnname: checkin_on
465
              columnname: checkin_on
466
466
467
      bundle_tables:
468
        columns:
469
            -
470
              columnname: title
471
              cannot_be_toggled: 1
472
            -
473
              columnname: author
474
            -
475
              columnname: collection_code
476
            -
477
              columnname: item_type
478
            -
479
              columnname: callnumber
480
            -
481
              columnname: external_id
482
            -
483
              columnname: status
484
            -
485
              columnname: bundle_actions
486
              cannot_be_toggled: 1
487
              cannot_be_modified: 1
488
467
  cataloguing:
489
  cataloguing:
468
    addbooks:
490
    addbooks:
469
      reservoir-table:
491
      reservoir-table:
(-)a/api/v1/swagger/definitions/bundle_link.yaml (+14 lines)
Line 0 Link Here
1
---
2
type: object
3
properties:
4
  item_id:
5
    type:
6
      - integer
7
      - "null"
8
    description: Internal item identifier
9
  external_id:
10
    type:
11
      - string
12
      - "null"
13
    description: Item barcode
14
additionalProperties: false
(-)a/api/v1/swagger/definitions/item.yaml (+1 lines)
Lines 7-12 properties: Link Here
7
  biblio_id:
7
  biblio_id:
8
    type: integer
8
    type: integer
9
    description: Internal identifier for the parent bibliographic record
9
    description: Internal identifier for the parent bibliographic record
10
  biblio: {}
10
  external_id:
11
  external_id:
11
    type:
12
    type:
12
      - string
13
      - string
(-)a/api/v1/swagger/paths/items.yaml (+157 lines)
Lines 92-97 Link Here
92
    x-koha-authorization:
92
    x-koha-authorization:
93
      permissions:
93
      permissions:
94
        catalogue: "1"
94
        catalogue: "1"
95
/items/{item_id}/bundled_items:
96
  post:
97
    x-mojo-to: Items#add_to_bundle
98
    operationId: addToBundle
99
    tags:
100
      - items
101
    summary: Add item to bundle
102
    parameters:
103
      - $ref: ../parameters.json#/item_id_pp
104
      - name: body
105
        in: body
106
        description: A JSON object containing information about the new bundle link
107
        required: true
108
        schema:
109
          $ref: ../definitions.json#/bundle_link
110
    consumes:
111
      - application/json
112
    produces:
113
      - application/json
114
    responses:
115
      "201":
116
        description: A successfully created bundle link
117
        schema:
118
          items:
119
            $ref: ../definitions.json#/item
120
      "400":
121
        description: Bad parameter
122
        schema:
123
          $ref: ../definitions.json#/error
124
      "401":
125
        description: Authentication required
126
        schema:
127
          $ref: ../definitions.json#/error
128
      "403":
129
        description: Access forbidden
130
        schema:
131
          $ref: ../definitions.json#/error
132
      "404":
133
        description: Resource not found
134
        schema:
135
          $ref: ../definitions.json#/error
136
      "409":
137
        description: Conflict in creating resource
138
        schema:
139
          $ref: ../definitions.json#/error
140
      "500":
141
        description: Internal server error
142
        schema:
143
          $ref: ../definitions.json#/error
144
      "503":
145
        description: Under maintenance
146
        schema:
147
          $ref: ../definitions.json#/error
148
    x-koha-authorization:
149
      permissions:
150
        catalogue: 1
151
  get:
152
    x-mojo-to: Items#bundled_items
153
    operationId: bundledItems
154
    tags:
155
      - items
156
    summary: List bundled items
157
    parameters:
158
      - $ref: ../parameters.json#/item_id_pp
159
      - name: external_id
160
        in: query
161
        description: Search on the item's barcode
162
        required: false
163
        type: string
164
      - $ref: ../parameters.json#/match
165
      - $ref: ../parameters.json#/order_by
166
      - $ref: ../parameters.json#/page
167
      - $ref: ../parameters.json#/per_page
168
      - $ref: ../parameters.json#/q_param
169
      - $ref: ../parameters.json#/q_body
170
      - $ref: ../parameters.json#/q_header
171
    consumes:
172
      - application/json
173
    produces:
174
      - application/json
175
    responses:
176
      "200":
177
        description: A list of item
178
        schema:
179
          type: array
180
          items:
181
            $ref: ../definitions.json#/item
182
      "401":
183
        description: Authentication required
184
        schema:
185
          $ref: ../definitions.json#/error
186
      "403":
187
        description: Access forbidden
188
        schema:
189
          $ref: ../definitions.json#/error
190
      "500":
191
        description: Internal server error
192
        schema:
193
          $ref: ../definitions.json#/error
194
      "503":
195
        description: Under maintenance
196
        schema:
197
          $ref: ../definitions.json#/error
198
    x-koha-authorization:
199
      permissions:
200
        catalogue: "1"
201
    x-koha-embed:
202
      - biblio
203
      - checkout
204
/items/{item_id}/bundled_items/{bundled_item_id}:
205
  delete:
206
    x-mojo-to: Items#remove_from_bundle
207
    operationId: removeFromBundle
208
    tags:
209
      - items
210
    summary: Remove item from bundle
211
    parameters:
212
      - $ref: ../parameters.json#/item_id_pp
213
      - name: bundled_item_id
214
        in: path
215
        description: Internal identifier for the bundled item
216
        required: true
217
        type: string
218
    consumes:
219
      - application/json
220
    produces:
221
      - application/json
222
    responses:
223
      "204":
224
        description: Bundle link deleted
225
      "400":
226
        description: Bad parameter
227
        schema:
228
          $ref: ../definitions.json#/error
229
      "401":
230
        description: Authentication required
231
        schema:
232
          $ref: ../definitions.json#/error
233
      "403":
234
        description: Access forbidden
235
        schema:
236
          $ref: ../definitions.json#/error
237
      "404":
238
        description: Resource not found
239
        schema:
240
          $ref: ../definitions.json#/error
241
      "500":
242
        description: Internal server error
243
        schema:
244
          $ref: ../definitions.json#/error
245
      "503":
246
        description: Under maintenance
247
        schema:
248
          $ref: ../definitions.json#/error
249
    x-koha-authorization:
250
      permissions:
251
        catalogue: 1
95
"/items/{item_id}/pickup_locations":
252
"/items/{item_id}/pickup_locations":
96
  get:
253
  get:
97
    x-mojo-to: Items#pickup_locations
254
    x-mojo-to: Items#pickup_locations
(-)a/api/v1/swagger/swagger.yaml (+6 lines)
Lines 10-15 definitions: Link Here
10
    $ref: ./definitions/allows_renewal.yaml
10
    $ref: ./definitions/allows_renewal.yaml
11
  basket:
11
  basket:
12
    $ref: ./definitions/basket.yaml
12
    $ref: ./definitions/basket.yaml
13
  bundle_link:
14
    $ref: ./definitions/bundle_link.yaml
13
  cashup:
15
  cashup:
14
    $ref: ./definitions/cashup.yaml
16
    $ref: ./definitions/cashup.yaml
15
  checkout:
17
  checkout:
Lines 143-148 paths: Link Here
143
    $ref: ./paths/items.yaml#/~1items
145
    $ref: ./paths/items.yaml#/~1items
144
  "/items/{item_id}":
146
  "/items/{item_id}":
145
    $ref: "./paths/items.yaml#/~1items~1{item_id}"
147
    $ref: "./paths/items.yaml#/~1items~1{item_id}"
148
  "/items/{item_id}/bundled_items":
149
    $ref: ./paths/items.yaml#/~1items~1{item_id}~1bundled_items
150
  "/items/{item_id}/bundled_items/{bundled_item_id}":
151
    $ref: ./paths/items.yaml#/~1items~1{item_id}~1bundled_items~1{bundled_item_id}
146
  "/items/{item_id}/pickup_locations":
152
  "/items/{item_id}/pickup_locations":
147
    $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations"
153
    $ref: "./paths/items.yaml#/~1items~1{item_id}~1pickup_locations"
148
  /libraries:
154
  /libraries:
(-)a/catalogue/detail.pl (+14 lines)
Lines 168-173 if (@hostitems){ Link Here
168
168
169
my $dat = &GetBiblioData($biblionumber);
169
my $dat = &GetBiblioData($biblionumber);
170
170
171
#is biblio a collection and are bundles enabled
172
my $leader = $record->leader();
173
$dat->{bundlesEnabled} = ( ( substr( $leader, 7, 1 ) eq 'c' )
174
      && C4::Context->preference('BundleNotLoanValue') ) ? 1 : 0;
175
171
#coping with subscriptions
176
#coping with subscriptions
172
my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber);
177
my $subscriptionsnumber = CountSubscriptionFromBiblionumber($biblionumber);
173
my @subscriptions       = SearchSubscriptions({ biblionumber => $biblionumber, orderby => 'title' });
178
my @subscriptions       = SearchSubscriptions({ biblionumber => $biblionumber, orderby => 'title' });
Lines 410-415 foreach my $item (@items) { Link Here
410
        }
415
        }
411
    }
416
    }
412
417
418
    if ($item_object->is_bundle) {
419
        $itemfields{bundles} = 1;
420
        $item->{is_bundle} = 1;
421
    }
422
423
    if ($item_object->in_bundle) {
424
        $item->{bundle_host} = $item_object->bundle_host;
425
    }
426
413
    if ($currentbranch and C4::Context->preference('SeparateHoldings')) {
427
    if ($currentbranch and C4::Context->preference('SeparateHoldings')) {
414
        if ($itembranchcode and $itembranchcode eq $currentbranch) {
428
        if ($itembranchcode and $itembranchcode eq $currentbranch) {
415
            push @itemloop, $item;
429
            push @itemloop, $item;
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+4 lines)
Lines 2358-2363 td { Link Here
2358
    display: block;
2358
    display: block;
2359
}
2359
}
2360
2360
2361
.bundled {
2362
    display: block;
2363
}
2364
2361
.datedue {
2365
.datedue {
2362
    color: #999;
2366
    color: #999;
2363
    display: block;
2367
    display: block;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (+4 lines)
Lines 165-170 Cataloging: Link Here
165
            - and record's last modifier name in MARC subfield
165
            - and record's last modifier name in MARC subfield
166
            - pref: MarcFieldForModifierName
166
            - pref: MarcFieldForModifierName
167
            - ". <br/><strong>NOTE:</strong> Use a dollar sign between field and subfield like 123$a."
167
            - ". <br/><strong>NOTE:</strong> Use a dollar sign between field and subfield like 123$a."
168
        -
169
            - Use the NOT_LOAN authorised value
170
            - pref: BundleNotLoanValue
171
            - to represent 'added to bundle' when an item is attached to bundle.
168
    Display:
172
    Display:
169
        -
173
        -
170
            - 'Separate main entry and subdivisions with '
174
            - 'Separate main entry and subdivisions with '
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt (-3 / +250 lines)
Lines 340-351 Link Here
340
                [% IF ( analyze ) %]<th id="[% tab | html %]_usedin" data-colname="[% tab | html %]_usedin">Used in</th><th></th>[% END %]
340
                [% IF ( analyze ) %]<th id="[% tab | html %]_usedin" data-colname="[% tab | html %]_usedin">Used in</th><th></th>[% END %]
341
                [% IF ( ShowCourseReserves ) %]<th id="[% tab | html %]_course_reserves" data-colname="[% tab | html %]_course_reserves">Course reserves</th>[% END %]
341
                [% IF ( ShowCourseReserves ) %]<th id="[% tab | html %]_course_reserves" data-colname="[% tab | html %]_course_reserves">Course reserves</th>[% END %]
342
                [% IF ( SpineLabelShowPrintOnBibDetails ) %]<th id="[% tab | html %]_spinelabel" data-colname="[% tab | html %]_spinelabel" class="NoSort">Spine label</th>[% END %]
342
                [% IF ( SpineLabelShowPrintOnBibDetails ) %]<th id="[% tab | html %]_spinelabel" data-colname="[% tab | html %]_spinelabel" class="NoSort">Spine label</th>[% END %]
343
                [% IF ( CAN_user_editcatalogue_edit_items ) %]<th id="[% tab | html %]_actions" data-colname="[% tab | html %]_actions"class="NoSort">&nbsp;</th>[% END %]
343
                [% IF ( CAN_user_editcatalogue_edit_items ) %]<th id="[% tab | html %]_actions" data-colname="[% tab | html %]_actions"class="NoSort noExport">&nbsp;</th>[% END %]
344
            </tr>
344
            </tr>
345
        </thead>
345
        </thead>
346
        <tbody>
346
        <tbody>
347
            [% FOREACH item IN items %]
347
            [% FOREACH item IN items %]
348
                <tr>
348
                <tr id="item_[% item.itemnumber | html %]" data-itemnumber="[% item.itemnumber | html %]">
349
                [% IF (StaffDetailItemSelection) %]
349
                [% IF (StaffDetailItemSelection) %]
350
                    <td style="text-align:center;vertical-align:middle">
350
                    <td style="text-align:center;vertical-align:middle">
351
                        <input type="checkbox" value="[% item.itemnumber | html %]" name="itemnumber" />
351
                        <input type="checkbox" value="[% item.itemnumber | html %]" name="itemnumber" />
Lines 515-520 Note that permanent location is a code, and location may be an authval. Link Here
515
                        [% IF ( item.restricted ) %]
515
                        [% IF ( item.restricted ) %]
516
                            <span class="restricted">([% item.restrictedvalue | html %])</span>
516
                            <span class="restricted">([% item.restrictedvalue | html %])</span>
517
                        [% END %]
517
                        [% END %]
518
519
                        [% IF ( item.bundle_host ) %]
520
                            <span class="bundled">In bundle: [% INCLUDE 'biblio-title.inc' biblio = item.bundle_host.biblio link = 1 %]</span>
521
                        [% END %]
522
518
                    </td>
523
                    </td>
519
                    <td class="datelastseen" data-order="[% item.datelastseen | html %]">[% item.datelastseen | $KohaDates %]</td>
524
                    <td class="datelastseen" data-order="[% item.datelastseen | html %]">[% item.datelastseen | $KohaDates %]</td>
520
                    <td class="dateaccessioned" data-order="[% item.dateaccessioned | html %]">[% item.dateaccessioned | $KohaDates %]</td>
525
                    <td class="dateaccessioned" data-order="[% item.dateaccessioned | html %]">[% item.dateaccessioned | $KohaDates %]</td>
Lines 599-604 Note that permanent location is a code, and location may be an authval. Link Here
599
                                <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>
604
                                <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>
600
                            [% END %]
605
                            [% END %]
601
                        [% END %]
606
                        [% END %]
607
                        [% IF bundlesEnabled %]
608
                            <button class="btn btn-default btn-xs details-control"><i class="fa fa-folder"></i> Manage bundle</button>
609
                        [% END %]
602
                    </td>
610
                    </td>
603
                [% END %]
611
                [% END %]
604
                </tr>
612
                </tr>
Lines 1039-1044 Note that permanent location is a code, and location may be an authval. Link Here
1039
1047
1040
[% END %]
1048
[% END %]
1041
1049
1050
    [% IF bundlesEnabled %]
1051
    <div class="modal" id="bundleItemsModal" tabindex="-1" role="dialog" aria-labelledby="bundleItemsLabel">
1052
        <form id="bundleItemsForm" action="">
1053
            <div class="modal-dialog" role="document">
1054
                <div class="modal-content">
1055
                    <div class="modal-header">
1056
                        <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
1057
                        <h3 id="bundleItemsLabel">Add to bundle</h3>
1058
                    </div>
1059
                    <div class="modal-body">
1060
                        <div id="result"></div>
1061
                        <fieldset class="rows">
1062
                            <ol>
1063
                                <li>
1064
                                    <label class="required" for="external_id">Item barcode: </label>
1065
                                    <input type="text" id="external_id" name="external_id" required="required">
1066
                                    <span class="required">Required</span>
1067
                                </li>
1068
                            </ol>
1069
                        </fieldset>
1070
                    </div>
1071
                    <div class="modal-footer">
1072
                        <button type="submit" class="btn btn-default">Submit</button>
1073
                        <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
1074
                    </div>
1075
                </div>
1076
            </div>
1077
        </form>
1078
    </div>
1079
    [% END %]
1080
1042
[% MACRO jsinclude BLOCK %]
1081
[% MACRO jsinclude BLOCK %]
1043
    [% INCLUDE 'catalog-strings.inc' %]
1082
    [% INCLUDE 'catalog-strings.inc' %]
1044
    [% Asset.js("js/catalog.js") | $raw %]
1083
    [% Asset.js("js/catalog.js") | $raw %]
Lines 1341-1346 Note that permanent location is a code, and location may be an authval. Link Here
1341
    [% INCLUDE 'datatables.inc' %]
1380
    [% INCLUDE 'datatables.inc' %]
1342
    [% Asset.js("lib/jquery/plugins/jquery.dataTables.columnFilter.js") | $raw %]
1381
    [% Asset.js("lib/jquery/plugins/jquery.dataTables.columnFilter.js") | $raw %]
1343
    [% INCLUDE 'columns_settings.inc' %]
1382
    [% INCLUDE 'columns_settings.inc' %]
1383
    [% INCLUDE 'js-date-format.inc' %]
1344
    [% Asset.js("js/browser.js") | $raw %]
1384
    [% Asset.js("js/browser.js") | $raw %]
1345
    [% Asset.js("js/table_filters.js") | $raw %]
1385
    [% Asset.js("js/table_filters.js") | $raw %]
1346
    <script>
1386
    <script>
Lines 1348-1354 Note that permanent location is a code, and location may be an authval. Link Here
1348
        browser = KOHA.browser('[% searchid | html %]', parseInt(biblionumber, 10));
1388
        browser = KOHA.browser('[% searchid | html %]', parseInt(biblionumber, 10));
1349
        browser.show();
1389
        browser.show();
1350
1390
1391
        [% IF bundlesEnabled %]
1392
        var bundle_columns = [% TablesSettings.GetColumns('catalogue', 'detail','bundle_tables','json') | $raw %];
1393
        [% END %]
1351
        $(document).ready(function() {
1394
        $(document).ready(function() {
1395
1396
            [% IF bundlesEnabled %] // Bundle handling
1397
            function createChild ( row, itemnumber ) {
1398
1399
                // Toolbar
1400
                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>');
1401
1402
                // This is the table we'll convert into a DataTable
1403
                var bundles_table = $('<table class="display tbundle" data-itemnumber="'+itemnumber+'" id="bundle_table_'+itemnumber+'" width="100%"/>');
1404
1405
                // Display it the child row
1406
                row.child( bundle_toolbar.add(bundles_table) ).show();
1407
1408
                // Initialise as a DataTable
1409
                var bundle_table_url = "/api/v1/items/" + itemnumber + "/bundled_items?";
1410
                var bundle_table = bundles_table.kohaTable({
1411
                    "ajax": {
1412
                        "url": bundle_table_url
1413
                    },
1414
                    "header_filter": false,
1415
                    "embed": [
1416
                        "biblio"
1417
                    ],
1418
                    "order": [[ 1, "asc" ]],
1419
                    "columnDefs": [ {
1420
                        "targets": [0,1,2,3,4,5],
1421
                        "render": function (data, type, row, meta) {
1422
                            if ( data && type == 'display' ) {
1423
                                return data.escapeHtml();
1424
                            }
1425
                            return data;
1426
                        }
1427
                    } ],
1428
                    "columns": [
1429
                        {
1430
                            "data": "biblio.title:biblio.medium",
1431
                            "title": "Title",
1432
                            "searchable": true,
1433
                            "orderable": true,
1434
                            "render": function(data, type, row, meta) {
1435
                                var title = "";
1436
                                if ( row.biblio.title ) {
1437
                                    title = title.concat('<span class="biblio-title">',row.biblio.title,'</span>');
1438
                                }
1439
                                if ( row.biblio.medium ) {
1440
                                    title = title.concat('<span class="biblio-medium">',row.biblio.medium,'</span>');
1441
                                }
1442
                                return title;
1443
                            }
1444
                        },
1445
                        {
1446
                            "data": "biblio.author",
1447
                            "title": "Author",
1448
                            "searchable": true,
1449
                            "orderable": true,
1450
                        },
1451
                        {
1452
                            "data": "collection_code",
1453
                            "title": "Collection code",
1454
                            "searchable": true,
1455
                            "orderable": true,
1456
                        },
1457
                        {
1458
                            "data": "item_type",
1459
                            "title": "Item Type",
1460
                            "searchable": false,
1461
                            "orderable": true,
1462
                        },
1463
                        {
1464
                            "data": "callnumber",
1465
                            "title": "Callnumber",
1466
                            "searchable": true,
1467
                            "orderable": true,
1468
                        },
1469
                        {
1470
                            "data": "external_id",
1471
                            "title": "Barcode",
1472
                            "searchable": true,
1473
                            "orderable": true,
1474
                        },
1475
                        {
1476
                            "data": "lost_status:last_seen_date",
1477
                            "title": "Status",
1478
                            "searchable": false,
1479
                            "orderable": true,
1480
                            "render": function(data, type, row, meta) {
1481
                                if ( row.lost_status ) {
1482
                                    return "Lost: " + row.lost_status;
1483
                                }
1484
                                return "";
1485
                            }
1486
                        },
1487
                        {
1488
                            "data": function( row, type, val, meta ) {
1489
                                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';
1490
                                return result;
1491
                            },
1492
                            "title": "Actions",
1493
                            "searchable": false,
1494
                            "orderable": false,
1495
                            "class": "noExport"
1496
                        }
1497
                    ]
1498
                }, bundle_columns, 1);
1499
1500
                $(".tbundle").on("click", ".remove", function(){
1501
                    var bundle_table = $(this).closest('table');
1502
                    var host_itemnumber = bundle_table.data('itemnumber');
1503
                    var component_itemnumber = $(this).data('itemnumber');
1504
                    var unlink_item_url = "/api/v1/items/" + host_itemnumber + "/bundled_items/" + component_itemnumber;
1505
                    $.ajax({
1506
                        type: "DELETE",
1507
                        url: unlink_item_url,
1508
                        success: function(){
1509
                            bundle_table.DataTable({ 'retrieve': true }).draw(false);
1510
                        }
1511
                    });
1512
                });
1513
1514
                return;
1515
            }
1516
1517
            var bundle_changed;
1518
            var bundle_form_active;
1519
            $("#bundleItemsModal").on("shown.bs.modal", function(e){
1520
                var button = $(e.relatedTarget);
1521
                var item_id = button.data('item');
1522
                $("#result").replaceWith('<div id="result"></div>');
1523
                $("#bundleItemsForm").attr('action', '/api/v1/items/' + item_id + '/bundled_items');
1524
                $("#external_id").focus();
1525
                bundle_changed = 0;
1526
                bundle_form_active = item_id;
1527
            });
1528
1529
            $("#bundleItemsForm").submit(function(event) {
1530
1531
                  /* stop form from submitting normally */
1532
                  event.preventDefault();
1533
1534
                  /* get the action attribute from the <form action=""> element */
1535
                  var $form = $(this),
1536
                  url = $form.attr('action');
1537
1538
                  /* Send the data using post with external_id */
1539
                  var posting = $.post(url, JSON.stringify({
1540
                      external_id: $('#external_id').val()
1541
                  }), null, "json");
1542
1543
                  /* Report the results */
1544
                  posting.done(function(data) {
1545
                      var barcode = $('#external_id').val();
1546
                      $('#result').replaceWith('<div id="result" class="alert alert-success">Success: Added '+barcode+'</div>');
1547
                      $('#external_id').val('').focus();
1548
                      bundle_changed = 1;
1549
                  });
1550
                  posting.fail(function(data) {
1551
                      var barcode = $('#external_id').val();
1552
                      if ( data.status === 409 ) {
1553
                          var response = data.responseJSON;
1554
                          if ( response.key === "PRIMARY" ) {
1555
                              $('#result').replaceWith('<div id="result" class="alert alert-warning">Warning: Item '+barcode+' already attached</div>');
1556
                          } else {
1557
                              $('#result').replaceWith('<div id="result" class="alert alert-danger">Failure: Item '+barcode+' belongs to another bundle</div>');
1558
                          }
1559
                      } else {
1560
                          $('#result').replaceWith('<div id="result" class="alert alert-danger">Failure: Check the logs for details</div>');
1561
                      }
1562
                      $('#external_id').val('').focus();
1563
                  });
1564
            });
1565
1566
            $("#bundleItemsModal").on("hidden.bs.modal", function(e){
1567
                if ( bundle_changed ) {
1568
                    $('#bundle_table_'+bundle_form_active).DataTable({ 'retrieve': true }).ajax.reload();
1569
                }
1570
                bundle_form_active = 0;
1571
                bundle_changed = 0;
1572
            });
1573
1574
            // End bundle handling
1575
            [% END %]
1576
1352
            var ids = ['holdings_table', 'otherholdings_table'];
1577
            var ids = ['holdings_table', 'otherholdings_table'];
1353
            var columns_settings = [ [% TablesSettings.GetColumns('catalogue', 'detail','holdings_table','json') | $raw %], [% TablesSettings.GetColumns('catalogue', 'detail','otherholdings_table','json')  | $raw %] ];
1578
            var columns_settings = [ [% TablesSettings.GetColumns('catalogue', 'detail','holdings_table','json') | $raw %], [% TablesSettings.GetColumns('catalogue', 'detail','otherholdings_table','json')  | $raw %] ];
1354
            var has_images = ["[% itemloop_has_images | html %]", "[% otheritemloop_has_images | html %]"];
1579
            var has_images = ["[% itemloop_has_images | html %]", "[% otheritemloop_has_images | html %]"];
Lines 1365-1370 Note that permanent location is a code, and location may be an authval. Link Here
1365
                    "sDom": 'C<"top pager"ilpfB><"#filter_c">tr<"bottom pager"ip>',
1590
                    "sDom": 'C<"top pager"ilpfB><"#filter_c">tr<"bottom pager"ip>',
1366
                };
1591
                };
1367
                var table = KohaTable(id, dt_parameters, columns_settings[i], 'with_filters');
1592
                var table = KohaTable(id, dt_parameters, columns_settings[i], 'with_filters');
1593
1594
                [% IF bundlesEnabled %]
1595
                // Add event listener for opening and closing bundle details
1596
                $('#' + id + ' tbody').on('click', 'button.details-control', function () {
1597
                    var tr = $(this).closest('tr');
1598
                    var dTable = $(this).closest('table').DataTable({ 'retrieve': true });
1599
1600
                    var itemnumber = tr.data('itemnumber');
1601
                    var row = dTable.row( tr );
1602
1603
                    if ( row.child.isShown() ) {
1604
                        // This row is already open - close it
1605
                        row.child.hide();
1606
                        tr.removeClass('shown');
1607
                    }
1608
                    else {
1609
                        // Open this row
1610
                        createChild(row, itemnumber);
1611
                        tr.addClass('shown');
1612
                    }
1613
                } );
1614
                [% END %]
1368
            }
1615
            }
1369
1616
1370
            [% IF Koha.Preference('AcquisitionDetails') %]
1617
            [% IF Koha.Preference('AcquisitionDetails') %]
Lines 1386-1391 Note that permanent location is a code, and location may be an authval. Link Here
1386
                    "sPaginationType": "full"
1633
                    "sPaginationType": "full"
1387
                }));
1634
                }));
1388
            [% END %]
1635
            [% END %]
1636
1389
        });
1637
        });
1390
1638
1391
        $(document).ready(function() {
1639
        $(document).ready(function() {
1392
- 

Return to bug 28854