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

(-)a/Koha/Old/Biblio.pm (-21 / +28 lines)
Lines 181-215 sub restore { Link Here
181
181
182
    $params //= {};
182
    $params //= {};
183
183
184
    my $biblio_data     = $self->unblessed;
185
    my $biblioitem      = $self->biblioitem;
186
    my $biblioitem_data = $biblioitem->unblessed;
187
    my $metadata        = $self->metadata;
188
    my $metadata_data   = $metadata->unblessed;
189
190
    my $new_biblio = Koha::Biblio->new($biblio_data)->store;
191
192
    $biblioitem_data->{biblionumber}     = $new_biblio->biblionumber;
193
    $biblioitem_data->{biblioitemnumber} = $new_biblio->biblionumber;
194
    Koha::Biblioitem->new($biblioitem_data)->store;
195
196
    delete $metadata_data->{id};
197
    $metadata_data->{biblionumber} = $new_biblio->biblionumber;
198
    Koha::Biblio::Metadata->new($metadata_data)->store;
199
200
    $metadata->delete;
201
    $biblioitem->delete;
202
    $self->delete;
203
204
    my $patron      = $params->{patron};
184
    my $patron      = $params->{patron};
205
    my $item_ids    = $params->{item_ids};
185
    my $item_ids    = $params->{item_ids};
206
    my $restore_all = $params->{restore_all} // 0;
186
    my $restore_all = $params->{restore_all} // 0;
207
187
188
    my $biblionumber = $self->biblionumber;
208
    my @restored_items;
189
    my @restored_items;
209
    my @skipped_items;
190
    my @skipped_items;
210
191
192
    my $new_biblio = Koha::Database->schema->txn_do(
193
        sub {
194
            my $biblio_data     = $self->unblessed;
195
            my $biblioitem      = $self->biblioitem;
196
            my $biblioitem_data = $biblioitem->unblessed;
197
            my $metadata        = $self->metadata;
198
            my $metadata_data   = $metadata->unblessed;
199
200
            my $biblio = Koha::Biblio->new($biblio_data)->store;
201
202
            $biblioitem_data->{biblionumber}     = $biblio->biblionumber;
203
            $biblioitem_data->{biblioitemnumber} = $biblio->biblionumber;
204
            Koha::Biblioitem->new($biblioitem_data)->store;
205
206
            delete $metadata_data->{id};
207
            $metadata_data->{biblionumber} = $biblio->biblionumber;
208
            Koha::Biblio::Metadata->new($metadata_data)->store;
209
210
            $metadata->delete;
211
            $biblioitem->delete;
212
            $self->delete;
213
214
            return $biblio;
215
        }
216
    );
217
211
    if ( $restore_all || $item_ids ) {
218
    if ( $restore_all || $item_ids ) {
212
        my $deleted_items = $self->items;
219
        my $deleted_items = Koha::Old::Items->search( { biblionumber => $biblionumber } );
213
        while ( my $deleted_item = $deleted_items->next ) {
220
        while ( my $deleted_item = $deleted_items->next ) {
214
            my $should_restore = 0;
221
            my $should_restore = 0;
215
222
(-)a/Koha/Old/Item.pm (-4 / +10 lines)
Lines 23-28 use C4::Context; Link Here
23
use C4::Log qw( logaction );
23
use C4::Log qw( logaction );
24
use Koha::Biblio;
24
use Koha::Biblio;
25
use Koha::Biblios;
25
use Koha::Biblios;
26
use Koha::Database;
26
use Koha::Exceptions;
27
use Koha::Exceptions;
27
use Koha::Item;
28
use Koha::Item;
28
use Koha::Old::Biblios;
29
use Koha::Old::Biblios;
Lines 78-89 sub restore { Link Here
78
    Koha::Exceptions::ObjectNotFound->throw("Bibliographic record not found for item")
79
    Koha::Exceptions::ObjectNotFound->throw("Bibliographic record not found for item")
79
        unless $biblio;
80
        unless $biblio;
80
81
81
    my $item_data = $self->unblessed;
82
    my $new_item = Koha::Database->schema->txn_do(
82
    delete $item_data->{deleted_on};
83
        sub {
84
            my $item_data = $self->unblessed;
85
            delete $item_data->{deleted_on};
83
86
84
    my $new_item = Koha::Item->new($item_data)->store;
87
            my $item = Koha::Item->new($item_data)->store;
88
            $self->delete;
85
89
86
    $self->delete;
90
            return $item;
91
        }
92
    );
87
93
88
    my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
94
    my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
89
    $indexer->index_records( $new_item->biblionumber, "specialUpdate", "biblioserver" );
95
    $indexer->index_records( $new_item->biblionumber, "specialUpdate", "biblioserver" );
(-)a/Koha/REST/V1/DeletedItems.pm (+7 lines)
Lines 20-25 use Modern::Perl; Link Here
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
use Koha::Exceptions;
22
use Koha::Exceptions;
23
use Koha::Exceptions::Object;
23
use Koha::Old::Items;
24
use Koha::Old::Items;
24
25
25
use Scalar::Util qw( blessed );
26
use Scalar::Util qw( blessed );
Lines 110-115 sub restore { Link Here
110
                }
111
                }
111
            );
112
            );
112
        }
113
        }
114
        if ( blessed $_ && $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
115
            return $c->render(
116
                status  => 409,
117
                openapi => { error => "Cannot restore item: another item with the same barcode already exists." }
118
            );
119
        }
113
        $c->unhandled_exception($_);
120
        $c->unhandled_exception($_);
114
    };
121
    };
115
}
122
}
(-)a/installer/data/mysql/mandatory/userpermissions.sql (-1 / +1 lines)
Lines 132-138 INSERT INTO permissions (module_bit, code, description) VALUES Link Here
132
   (13, 'records_batchmod', 'Perform batch modification of records (biblios or authorities)'),
132
   (13, 'records_batchmod', 'Perform batch modification of records (biblios or authorities)'),
133
   (13, 'marc_modification_templates', 'Manage marc modification templates'),
133
   (13, 'marc_modification_templates', 'Manage marc modification templates'),
134
   (13, 'records_batchdel', 'Perform batch deletion of records (bibliographic or authority)'),
134
   (13, 'records_batchdel', 'Perform batch deletion of records (bibliographic or authority)'),
135
   (13, 'records_restore', 'Restore deleted records'),
135
   (13, 'records_restore', 'Restore deleted bibliographic records and items'),
136
   (13, 'access_files', 'Access to the files stored on the server'),
136
   (13, 'access_files', 'Access to the files stored on the server'),
137
   (13, 'upload_general_files', 'Upload any file'),
137
   (13, 'upload_general_files', 'Upload any file'),
138
   (13, 'upload_manage', 'Manage uploaded files'),
138
   (13, 'upload_manage', 'Manage uploaded files'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-menu.inc (-1 / +1 lines)
Lines 43-49 Link Here
43
        </ul>
43
        </ul>
44
    [% END %]
44
    [% END %]
45
45
46
    [% IF ( CAN_user_tools_items_batchmod || CAN_user_tools_items_batchdel || CAN_user_tools_records_batchmod || CAN_user_tools_records_batchdel || CAN_user_tools_records_restore || CAN_user_tools_marc_modification_templates || CAN_user_editcatalogue_edit_catalogue ) %]
46
    [% IF ( CAN_user_tools_items_batchmod || CAN_user_tools_items_batchdel || CAN_user_tools_records_batchmod || CAN_user_tools_records_batchdel || CAN_user_tools_records_restore || CAN_user_tools_marc_modification_templates ) %]
47
        <h5>Batch editing</h5>
47
        <h5>Batch editing</h5>
48
        <ul>
48
        <ul>
49
            [% IF ( CAN_user_tools_items_batchmod ) %]
49
            [% IF ( CAN_user_tools_items_batchmod ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/permissions.inc (-1 / +1 lines)
Lines 461-467 Link Here
461
        <span class="sub_permission records_batchmod_subpermission"> Perform batch modification of records (biblios or authorities) </span>
461
        <span class="sub_permission records_batchmod_subpermission"> Perform batch modification of records (biblios or authorities) </span>
462
        <span class="permissioncode">([% name | html %])</span>
462
        <span class="permissioncode">([% name | html %])</span>
463
    [%- CASE 'records_restore' -%]
463
    [%- CASE 'records_restore' -%]
464
        <span class="sub_permission records_restore_subpermission"> Restore deleted records </span>
464
        <span class="sub_permission records_restore_subpermission"> Restore deleted bibliographic records and items </span>
465
        <span class="permissioncode">([% name | html %])</span>
465
        <span class="permissioncode">([% name | html %])</span>
466
    [%- CASE 'rotating_collections' -%]
466
    [%- CASE 'rotating_collections' -%]
467
        <span class="sub_permission rotating_collections_subpermission"> Manage rotating collections </span>
467
        <span class="sub_permission rotating_collections_subpermission"> Manage rotating collections </span>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/restore-records.tt (-35 / +41 lines)
Lines 216-222 Link Here
216
                }
216
                }
217
            }, 100);
217
            }, 100);
218
218
219
            function buildApiUrl(baseUrl) {
219
            function buildApiUrl(baseUrl, dateField) {
220
                var from = $("#from").val();
220
                var from = $("#from").val();
221
                var to = $("#to").val();
221
                var to = $("#to").val();
222
222
Lines 226-240 Link Here
226
226
227
                var query = {};
227
                var query = {};
228
                if (from && to) {
228
                if (from && to) {
229
                    query["me.timestamp"] = {
229
                    query[dateField] = {
230
                        "-between": [from + "T00:00:00Z", to + "T23:59:59Z"],
230
                        "-between": [from + "T00:00:00Z", to + "T23:59:59Z"],
231
                    };
231
                    };
232
                } else if (from) {
232
                } else if (from) {
233
                    query["me.timestamp"] = {
233
                    query[dateField] = {
234
                        ">=": from + "T00:00:00Z",
234
                        ">=": from + "T00:00:00Z",
235
                    };
235
                    };
236
                } else if (to) {
236
                } else if (to) {
237
                    query["me.timestamp"] = {
237
                    query[dateField] = {
238
                        "<=": to + "T23:59:59Z",
238
                        "<=": to + "T23:59:59Z",
239
                    };
239
                    };
240
                }
240
                }
Lines 245-251 Link Here
245
            // Deleted biblios DataTable
245
            // Deleted biblios DataTable
246
            var biblios_table = $("#deleted_biblios_table").kohaTable({
246
            var biblios_table = $("#deleted_biblios_table").kohaTable({
247
                ajax: {
247
                ajax: {
248
                    url: buildApiUrl("/api/v1/deleted/biblios"),
248
                    url: buildApiUrl("/api/v1/deleted/biblios", "me.timestamp"),
249
                },
249
                },
250
                embed: "items",
250
                embed: "items",
251
                order: [[3, "desc"]],
251
                order: [[3, "desc"]],
Lines 313-319 Link Here
313
            // Deleted items DataTable
313
            // Deleted items DataTable
314
            var items_table = $("#deleted_items_table").kohaTable({
314
            var items_table = $("#deleted_items_table").kohaTable({
315
                ajax: {
315
                ajax: {
316
                    url: buildApiUrl("/api/v1/deleted/items"),
316
                    url: buildApiUrl("/api/v1/deleted/items", "me.deleted_on"),
317
                },
317
                },
318
                embed: "biblio",
318
                embed: "biblio",
319
                order: [[5, "desc"]],
319
                order: [[5, "desc"]],
Lines 405-434 Link Here
405
            var modal_items_table = null;
405
            var modal_items_table = null;
406
            var modal_items_table_api = null;
406
            var modal_items_table_api = null;
407
407
408
            // Restore biblio handler
408
            // Populate and show the restore biblio modal.
409
            $("#deleted_biblios_table").on("click", ".restore-biblio", function (e) {
409
            // preselectedItemId: optional item_id to pre-check in the items list.
410
                e.preventDefault();
410
            function showRestoreBiblioModal(biblio_id, title, author, items, preselectedItemId) {
411
                var button = $(this);
412
                var biblio_id = button.data("biblio-id");
413
                var title = button.data("title");
414
                var row = biblios_table_api.row(button.closest("tr")).data();
415
416
                // Populate modal with biblio info
417
                $("#restore-modal-biblio-id").text(biblio_id);
411
                $("#restore-modal-biblio-id").text(biblio_id);
418
                $("#restore-modal-biblio-title").text(title || _("(No title)"));
412
                $("#restore-modal-biblio-title").text(title || _("(No title)"));
419
                $("#restore-modal-biblio-author").text(row.author || _("(No author)"));
413
                $("#restore-modal-biblio-author").text(author || _("(No author)"));
420
414
421
                // Check if there are deleted items
422
                var items = row.items || [];
423
                if (items.length > 0) {
415
                if (items.length > 0) {
424
                    $("#items-section").show();
416
                    $("#items-section").show();
425
417
426
                    // Destroy existing DataTable if it exists
427
                    if (modal_items_table_api) {
418
                    if (modal_items_table_api) {
428
                        modal_items_table_api.destroy();
419
                        modal_items_table_api.destroy();
429
                    }
420
                    }
430
421
431
                    // Initialize kohaTable with the items data
432
                    modal_items_table = $("#deleted-items-list").kohaTable({
422
                    modal_items_table = $("#deleted-items-list").kohaTable({
433
                        data: items,
423
                        data: items,
434
                        paging: false,
424
                        paging: false,
Lines 439-445 Link Here
439
                                    if (type === "display") {
429
                                    if (type === "display") {
440
                                        var canEdit = canEditItem(row.home_library_id);
430
                                        var canEdit = canEditItem(row.home_library_id);
441
                                        var disabled = canEdit ? '' : ' disabled';
431
                                        var disabled = canEdit ? '' : ' disabled';
442
                                        var checkbox = '<input type="checkbox" class="item-checkbox" data-item-id="' + row.item_id + '"' + disabled + '>';
432
                                        var preselected = (preselectedItemId && row.item_id == preselectedItemId && canEdit) ? ' checked' : '';
433
                                        var checkbox = '<input type="checkbox" class="item-checkbox" data-item-id="' + row.item_id + '"' + disabled + preselected + '>';
443
434
444
                                        if (!canEdit) {
435
                                        if (!canEdit) {
445
                                            return '<span title="' + _("You do not have permission to restore items from this library") + '">' + checkbox + '</span>';
436
                                            return '<span title="' + _("You do not have permission to restore items from this library") + '">' + checkbox + '</span>';
Lines 501-512 Link Here
501
                    $("#items-section").hide();
492
                    $("#items-section").hide();
502
                }
493
                }
503
494
504
                // Store biblio data
505
                $("#restore-biblio-with-items-button").data("biblio-id", biblio_id);
495
                $("#restore-biblio-with-items-button").data("biblio-id", biblio_id);
506
                $("#restore-biblio-with-items-button").data("restore-button", button);
507
496
508
                var modal = new bootstrap.Modal(document.getElementById("restoreBiblioModal"));
497
                var modal = new bootstrap.Modal(document.getElementById("restoreBiblioModal"));
509
                modal.show();
498
                modal.show();
499
            }
500
501
            // Restore biblio handler (from deleted biblios table)
502
            $("#deleted_biblios_table").on("click", ".restore-biblio", function (e) {
503
                e.preventDefault();
504
                var button = $(this);
505
                var biblio_id = button.data("biblio-id");
506
                var row = biblios_table_api.row(button.closest("tr")).data();
507
508
                $("#restore-biblio-with-items-button").data("restore-button", button);
509
                showRestoreBiblioModal(biblio_id, row.title, row.author, row.items || []);
510
            });
510
            });
511
511
512
            // Restore item handler
512
            // Restore item handler
Lines 561-590 Link Here
561
                });
561
                });
562
            });
562
            });
563
563
564
            // Restore biblio from modal
564
            // "Restore bibliographic record" button in the deleted-bib warning modal:
565
            // fetch the biblio (with items) and open the full restore modal with the
566
            // originally clicked item pre-selected.
565
            $("#restore-biblio-button").on("click", function () {
567
            $("#restore-biblio-button").on("click", function () {
566
                var button = $(this);
568
                var button = $(this);
567
                var biblio_id = button.data("biblio-id");
569
                var biblio_id = button.data("biblio-id");
568
                var item_id = button.data("item-id");
570
                var original_item_id = button.data("item-id");
569
                var item_button = button.data("item-button");
571
                var item_button = button.data("item-button");
570
572
571
                button.prop("disabled", true);
573
                button.prop("disabled", true);
572
574
573
                $.ajax({
575
                $.ajax({
574
                    url: "/api/v1/deleted/biblios/" + biblio_id,
576
                    url: "/api/v1/deleted/biblios/" + biblio_id,
575
                    type: "PUT",
577
                    type: "GET",
576
                    headers: {
578
                    headers: {
577
                        "x-koha-request-id": Math.random(),
579
                        "x-koha-embed": "items",
578
                    },
580
                    },
579
                    success: function (data) {
581
                    success: function (biblio) {
580
                        showMessage(_("Bibliographic record %s restored successfully").format(biblio_id), "success");
581
                        bootstrap.Modal.getInstance(document.getElementById("deletedBiblioModal")).hide();
582
                        bootstrap.Modal.getInstance(document.getElementById("deletedBiblioModal")).hide();
582
                        biblios_table_api.ajax.reload();
583
                        $("#restore-biblio-with-items-button").data("restore-button", item_button);
583
                        items_table_api.ajax.reload();
584
                        showRestoreBiblioModal(
585
                            biblio.biblio_id,
586
                            biblio.title,
587
                            biblio.author,
588
                            biblio.items || [],
589
                            original_item_id
590
                        );
584
                        button.prop("disabled", false);
591
                        button.prop("disabled", false);
585
                    },
592
                    },
586
                    error: function (xhr) {
593
                    error: function (xhr) {
587
                        var error_msg = _("Error restoring bibliographic record %s").format(biblio_id);
594
                        var error_msg = _("Error loading bibliographic record %s").format(biblio_id);
588
                        if (xhr.responseJSON && xhr.responseJSON.error) {
595
                        if (xhr.responseJSON && xhr.responseJSON.error) {
589
                            error_msg += ": " + xhr.responseJSON.error;
596
                            error_msg += ": " + xhr.responseJSON.error;
590
                        }
597
                        }
Lines 678-685 Link Here
678
            });
685
            });
679
686
680
            $("#filter_table").on("click", function () {
687
            $("#filter_table").on("click", function () {
681
                biblios_table_api.ajax.url(buildApiUrl("/api/v1/deleted/biblios")).load();
688
                biblios_table_api.ajax.url(buildApiUrl("/api/v1/deleted/biblios", "me.timestamp")).load();
682
                items_table_api.ajax.url(buildApiUrl("/api/v1/deleted/items")).load();
689
                items_table_api.ajax.url(buildApiUrl("/api/v1/deleted/items", "me.deleted_on")).load();
683
            });
690
            });
684
691
685
            $("#clear_filters").on("click", function () {
692
            $("#clear_filters").on("click", function () {
686
- 

Return to bug 17387