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

(-)a/Koha/BackgroundJob.pm (+2 lines)
Lines 449-454 sub core_types_to_classes { Link Here
449
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
449
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
450
        batch_item_record_deletion          => 'Koha::BackgroundJob::BatchDeleteItem',
450
        batch_item_record_deletion          => 'Koha::BackgroundJob::BatchDeleteItem',
451
        batch_item_record_modification      => 'Koha::BackgroundJob::BatchUpdateItem',
451
        batch_item_record_modification      => 'Koha::BackgroundJob::BatchUpdateItem',
452
        batch_add_display_items             => 'Koha::BackgroundJob::BatchAddDisplayItems',
453
        batch_delete_display_items          => 'Koha::BackgroundJob::BatchDeleteDisplayItems',
452
        erm_sushi_harvester                 => 'Koha::BackgroundJob::ErmSushiHarvester',
454
        erm_sushi_harvester                 => 'Koha::BackgroundJob::ErmSushiHarvester',
453
        batch_hold_cancel                   => 'Koha::BackgroundJob::BatchCancelHold',
455
        batch_hold_cancel                   => 'Koha::BackgroundJob::BatchCancelHold',
454
        create_eholdings_from_biblios       => 'Koha::BackgroundJob::CreateEHoldingsFromBiblios',
456
        create_eholdings_from_biblios       => 'Koha::BackgroundJob::CreateEHoldingsFromBiblios',
(-)a/Koha/BackgroundJob/BatchAddDisplayItems.pm (+208 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchAddDisplayItems;
2
3
# Copyright 2025-2026 Open Fifth Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Try::Tiny qw( catch try );
22
use C4::Context;
23
24
use Koha::DateUtils qw(dt_from_string);
25
use Koha::DisplayItem;
26
use Koha::DisplayItems;
27
use Koha::Items;
28
use Koha::Displays;
29
30
use base 'Koha::BackgroundJob';
31
32
=head1 NAME
33
34
Koha::BackgroundJob::BatchAddDisplayItems - Background job to add multiple items to a display
35
36
=head1 API
37
38
=head2 Class methods
39
40
=head3 job_type
41
42
Return the job type 'batch_add_display_items'.
43
44
=cut
45
46
sub job_type {
47
    return 'batch_add_display_items';
48
}
49
50
=head3 process
51
52
Process the batch addition of items to a display
53
54
=cut
55
56
sub process {
57
    my ( $self, $args ) = @_;
58
59
    if ( $self->status eq 'cancelled' ) {
60
        return;
61
    }
62
63
    $self->start;
64
65
    my $display_id  = $args->{display_id};
66
    my @barcodes    = @{ $args->{barcodes} };
67
    my $date_remove = $args->{date_remove};
68
69
    my $report = {
70
        total_records => scalar @barcodes,
71
        total_success => 0,
72
        total_errors  => 0,
73
    };
74
75
    my @messages;
76
    my @added_items;
77
    my @failed_items;
78
79
    # Validate display exists
80
    my $display = Koha::Displays->find($display_id);
81
    unless ($display) {
82
        push @messages, {
83
            type       => 'error',
84
            code       => 'display_not_found',
85
            display_id => $display_id,
86
        };
87
        $self->finish( { messages => \@messages, report => $report } );
88
        return;
89
    }
90
91
    # Calculate date_remove from display_days if not provided
92
    unless ($date_remove) {
93
        if ( $display->display_days ) {
94
            my $dt = dt_from_string();
95
            $dt->add( days => $display->display_days );
96
            $date_remove = $dt->ymd;
97
        }
98
    }
99
100
    try {
101
        my $schema = Koha::Database->new->schema;
102
        $schema->txn_do(
103
            sub {
104
                for my $barcode ( sort { $a <=> $b } @barcodes ) {
105
106
                    last if $self->get_from_storage->status eq 'cancelled';
107
108
                    my $item = Koha::Items->find( { barcode => $barcode } );
109
                    unless ($item) {
110
                        push @failed_items, {
111
                            barcode => $barcode,
112
                            error   => 'Item with barcode not found'
113
                        };
114
                        $report->{total_errors}++;
115
                        next;
116
                    }
117
                    my $itemnumber = $item->itemnumber;
118
119
                    # Check if item is already in this display
120
                    my $existing = Koha::DisplayItems->search(
121
                        {
122
                            display_id => $display_id,
123
                            itemnumber => $itemnumber,
124
                        }
125
                    )->next;
126
127
                    if ($existing) {
128
                        push @failed_items, {
129
                            itemnumber => $itemnumber,
130
                            barcode    => $barcode,
131
                            error      => 'Item already in display'
132
                        };
133
                        $report->{total_errors}++;
134
                        next;
135
                    }
136
137
                    # Create display item
138
                    my $display_item = Koha::DisplayItem->new(
139
                        {
140
                            display_id   => $display_id,
141
                            itemnumber   => $itemnumber,
142
                            biblionumber => $item->biblionumber,
143
                            date_remove  => $date_remove,
144
                        }
145
                    )->store;
146
147
                    push @added_items, {
148
                        display_item_id => $display_item->display_item_id,
149
                        itemnumber      => $itemnumber,
150
                        barcode         => $barcode,
151
                        biblionumber    => $item->biblionumber,
152
                    };
153
154
                    $report->{total_success}++;
155
                    $self->step;
156
                }
157
            }
158
        );
159
    } catch {
160
        warn $_;
161
        push @messages, {
162
            type  => 'error',
163
            code  => 'unknown',
164
            error => $_,
165
        };
166
        die "Something terrible has happened!" if ( $_ =~ /Rollback failed/ );
167
    };
168
169
    $report->{display_id}   = $display_id;
170
    $report->{added_items}  = \@added_items;
171
    $report->{failed_items} = \@failed_items;
172
173
    my $data = $self->decoded_data;
174
    $data->{messages} = \@messages;
175
    $data->{report}   = $report;
176
177
    $self->finish($data);
178
}
179
180
=head3 enqueue
181
182
Enqueue the job.
183
184
=cut
185
186
sub enqueue {
187
    my ( $self, $args ) = @_;
188
189
    return unless exists $args->{barcodes} && exists $args->{display_id};
190
191
    my @barcodes    = @{ $args->{barcodes} };
192
    my $display_id  = $args->{display_id};
193
    my $date_remove = $args->{date_remove};
194
195
    $self->SUPER::enqueue(
196
        {
197
            job_size => scalar @barcodes,
198
            job_args => {
199
                display_id  => $display_id,
200
                barcodes    => \@barcodes,
201
                date_remove => $date_remove,
202
            },
203
            job_queue => 'long_tasks',
204
        }
205
    );
206
}
207
208
1;
(-)a/Koha/BackgroundJob/BatchDeleteDisplayItems.pm (+193 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchDeleteDisplayItems;
2
3
# Copyright 2025-2026 Open Fifth Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <https://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Try::Tiny qw( catch try );
22
23
use Koha::DisplayItem;
24
use Koha::DisplayItems;
25
use Koha::Items;
26
27
use base 'Koha::BackgroundJob';
28
29
=head1 NAME
30
31
Koha::BackgroundJob::BatchDeleteDisplayItems - Background job to remove multiple items from displays
32
33
=head1 API
34
35
=head2 Class methods
36
37
=head3 job_type
38
39
Return the job type 'batch_delete_display_items'.
40
41
=cut
42
43
sub job_type {
44
    return 'batch_delete_display_items';
45
}
46
47
=head3 process
48
49
Process the batch removal of items from displays
50
51
=cut
52
53
sub process {
54
    my ( $self, $args ) = @_;
55
56
    if ( $self->status eq 'cancelled' ) {
57
        return;
58
    }
59
60
    $self->start;
61
62
    my @barcodes   = @{ $args->{barcodes} };
63
    my $display_id = $args->{display_id};      # Optional: if specified, only remove from this display
64
65
    my $report = {
66
        total_records => scalar @barcodes,
67
        total_success => 0,
68
        total_errors  => 0,
69
    };
70
71
    my @messages;
72
    my @deleted_items;
73
    my @failed_items;
74
75
    try {
76
        my $schema = Koha::Database->new->schema;
77
        $schema->txn_do(
78
            sub {
79
                for my $barcode ( sort { $a <=> $b } @barcodes ) {
80
81
                    last if $self->get_from_storage->status eq 'cancelled';
82
83
                    my $item = Koha::Items->find( { barcode => $barcode } );
84
                    unless ($item) {
85
                        push @failed_items, {
86
                            barcode => $barcode,
87
                            error   => 'Item with barcode not found'
88
                        };
89
                        $report->{total_errors}++;
90
                        next;
91
                    }
92
                    my $itemnumber = $item->itemnumber;
93
94
                    # Build search criteria
95
                    my $search_criteria = { itemnumber => $itemnumber };
96
                    $search_criteria->{display_id} = $display_id
97
                        if $display_id;
98
99
                    my $display_items = Koha::DisplayItems->search($search_criteria);
100
101
                    unless ( $display_items->count ) {
102
                        push @failed_items, {
103
                            itemnumber => $itemnumber,
104
                            barcode    => $barcode,
105
                            error      => $display_id
106
                            ? 'Item not found in specified display'
107
                            : 'Item not found in any display'
108
                        };
109
                        $report->{total_errors}++;
110
                        next;
111
                    }
112
113
                    my $deleted_count = 0;
114
                    my @deleted_from_displays;
115
116
                    while ( my $display_item = $display_items->next ) {
117
                        push @deleted_from_displays, {
118
                            display_id      => $display_item->display_id,
119
                            display_item_id => $display_item->display_item_id,
120
                        };
121
                        $display_item->delete;
122
                        $deleted_count++;
123
                    }
124
125
                    if ( $deleted_count > 0 ) {
126
                        push @deleted_items, {
127
                            itemnumber            => $itemnumber,
128
                            barcode               => $barcode,
129
                            displays_removed_from => \@deleted_from_displays,
130
                            count                 => $deleted_count,
131
                        };
132
                        $report->{total_success}++;
133
                    } else {
134
                        push @failed_items, {
135
                            itemnumber => $itemnumber,
136
                            barcode    => $barcode,
137
                            error      => 'No display items could be deleted'
138
                        };
139
                        $report->{total_errors}++;
140
                    }
141
142
                    $self->step;
143
                }
144
            }
145
        );
146
    } catch {
147
        warn $_;
148
        push @messages, {
149
            type  => 'error',
150
            code  => 'unknown',
151
            error => $_,
152
        };
153
        die "Something terrible has happened!" if ( $_ =~ /Rollback failed/ );
154
    };
155
156
    $report->{display_id}    = $display_id;
157
    $report->{deleted_items} = \@deleted_items;
158
    $report->{failed_items}  = \@failed_items;
159
160
    my $data = $self->decoded_data;
161
    $data->{messages} = \@messages;
162
    $data->{report}   = $report;
163
164
    $self->finish($data);
165
}
166
167
=head3 enqueue
168
169
Enqueue the job.
170
171
=cut
172
173
sub enqueue {
174
    my ( $self, $args ) = @_;
175
176
    return unless exists $args->{barcodes};
177
178
    my @barcodes   = @{ $args->{barcodes} };
179
    my $display_id = $args->{display_id};      # Optional
180
181
    $self->SUPER::enqueue(
182
        {
183
            job_size => scalar @barcodes,
184
            job_args => {
185
                barcodes   => \@barcodes,
186
                display_id => $display_id,
187
            },
188
            job_queue => 'long_tasks',
189
        }
190
    );
191
}
192
193
1;
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_add_display_items.inc (+76 lines)
Line 0 Link Here
1
[% USE Koha %]
2
3
[% BLOCK report %]
4
    [% SET report = job.report %]
5
    [% IF report %]
6
        [% IF job.status == 'finished' %]
7
            [% IF report.total_errors > 0 %]
8
                <div class="alert alert-alert"> Processed [% report.total_success | html %] of [% report.total_records | html %] with [% report.total_errors | html %] errors</div>
9
            [% ELSE %]
10
                <div class="alert alert-success"> Processed [% report.total_success | html %] of [% report.total_records | html %]</div>
11
            [% END %]
12
        [% ELSIF job.status == 'cancelled' %]
13
            <span> The job has been cancelled before it finished.</span>
14
        [% END %]
15
    [% END %]
16
[% END %]
17
18
[% BLOCK detail %]
19
    [% SET messages = job.messages %]
20
    [% IF messages && (messages.size > 0) %]
21
        <h4>Messages</h4>
22
        <table>
23
            <tr>
24
                <th>Type</th>
25
                <th>Code</th>
26
                <th>Error</th>
27
            </tr>
28
            [% FOR message IN messages %]
29
                <tr>
30
                    <td>[% message.type | html %]</td>
31
                    <td>[% message.code | html %]</td>
32
                    <td>[% message.error | html %]</td>
33
                </tr>
34
            [% END %]
35
        </table>
36
    [% END %]
37
    [% SET report = job.report %]
38
    [% IF report %]
39
        [% IF (report.added_items.size > 0) %]
40
            <h4>Added items</h4>
41
            <table>
42
                <tr>
43
                    <th>Item barcode</th>
44
                    <th>Display number</th>
45
                </tr>
46
                [% FOR added_item IN report.added_items %]
47
                    <tr>
48
                        <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% added_item.itemnumber | url %]">[% added_item.barcode | html %]</a></td>
49
                        <td><a href="/cgi-bin/koha/display/displays/[% report.display_id | url %]">[% report.display_id | html %]</a></td>
50
                    </tr>
51
                [% END %]
52
            </table>
53
        [% END %]
54
        [% IF (report.failed_items.size > 0) %]
55
            <h4>Failed items</h4>
56
            <table>
57
                <tr>
58
                    <th>Item barcode</th>
59
                    <th>Error message</th>
60
                </tr>
61
                [% FOR failed_item IN report.failed_items %]
62
                    <tr>
63
                        <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% failed_item.itemnumber | url %]">[% failed_item.barcode | html %]</a></td>
64
                        <td>[% failed_item.error | html %]</td>
65
                    </tr>
66
                [% END %]
67
            </table>
68
        [% END %]
69
    [% END %]
70
    [% IF job.status == 'cancelled' %]
71
        <span> The job has been cancelled before it finished.</span>
72
    [% END %]
73
[% END %]
74
75
[% BLOCK js %]
76
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_delete_display_items.inc (+76 lines)
Line 0 Link Here
1
[% USE Koha %]
2
3
[% BLOCK report %]
4
    [% SET report = job.report %]
5
    [% IF report %]
6
        [% IF job.status == 'finished' %]
7
            [% IF report.total_errors > 0 %]
8
                <div class="alert alert-alert"> Processed [% report.total_success | html %] of [% report.total_records | html %] with [% report.total_errors | html %] errors</div>
9
            [% ELSE %]
10
                <div class="alert alert-success"> Processed [% report.total_success | html %] of [% report.total_records | html %]</div>
11
            [% END %]
12
        [% ELSIF job.status == 'cancelled' %]
13
            <span> The job has been cancelled before it finished.</span>
14
        [% END %]
15
    [% END %]
16
[% END %]
17
18
[% BLOCK detail %]
19
    [% SET messages = job.messages %]
20
    [% IF messages && (messages.size > 0) %]
21
        <h4>Messages</h4>
22
        <table>
23
            <tr>
24
                <th>Type</th>
25
                <th>Code</th>
26
                <th>Error</th>
27
            </tr>
28
            [% FOR message IN messages %]
29
                <tr>
30
                    <td>[% message.type | html %]</td>
31
                    <td>[% message.code | html %]</td>
32
                    <td>[% message.error | html %]</td>
33
                </tr>
34
            [% END %]
35
        </table>
36
    [% END %]
37
    [% SET report = job.report %]
38
    [% IF report %]
39
        [% IF (report.deleted_items.size > 0) %]
40
            <h4>Deleted items</h4>
41
            <table>
42
                <tr>
43
                    <th>Item barcode</th>
44
                    <th>Display number</th>
45
                </tr>
46
                [% FOR deleted_item IN report.deleted_items %]
47
                    <tr>
48
                        <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% deleted_item.itemnumber | url %]">[% deleted_item.barcode | html %]</a></td>
49
                        <td><a href="/cgi-bin/koha/display/displays/[% report.display_id | url %]">[% report.display_id | html %]</a></td>
50
                    </tr>
51
                [% END %]
52
            </table>
53
        [% END %]
54
        [% IF (report.failed_items.size > 0) %]
55
            <h4>Failed items</h4>
56
            <table>
57
                <tr>
58
                    <th>Item barcode</th>
59
                    <th>Error message</th>
60
                </tr>
61
                [% FOR failed_item IN report.failed_items %]
62
                    <tr>
63
                        <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% failed_item.itemnumber | url %]">[% failed_item.barcode | html %]</a></td>
64
                        <td>[% failed_item.error | html %]</td>
65
                    </tr>
66
                [% END %]
67
            </table>
68
        [% END %]
69
    [% END %]
70
    [% IF job.status == 'cancelled' %]
71
        <span> The job has been cancelled before it finished.</span>
72
    [% END %]
73
[% END %]
74
75
[% BLOCK js %]
76
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt (-1 / +8 lines)
Lines 182-187 Link Here
182
                '_id': 'batch_item_record_modification',
182
                '_id': 'batch_item_record_modification',
183
                '_str': _("Batch item record modification").escapeHtml()
183
                '_str': _("Batch item record modification").escapeHtml()
184
            },
184
            },
185
            {
186
                '_id': 'batch_add_display_items',
187
                '_str': _("Batch add display items").escapeHtml()
188
            },
189
            {
190
                '_id': 'batch_delete_display_items',
191
                '_str': _("Batch delete display items").escapeHtml()
192
            },
185
            {
193
            {
186
                '_id': 'batch_item_record_deletion',
194
                '_id': 'batch_item_record_deletion',
187
                '_str': _("Batch item record deletion").escapeHtml()
195
                '_str': _("Batch item record deletion").escapeHtml()
188
- 

Return to bug 14962