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

Return to bug 28854