Bugzilla – Attachment 193723 Details for
Bug 14962
Temp Shelving Location / On Display Module
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 14962: On Display code for BackgroundJobs
ed72756.patch (text/plain), 21.56 KB, created by
Martin Renvoize (ashimema)
on 2026-02-24 13:35:09 UTC
(
hide
)
Description:
Bug 14962: On Display code for BackgroundJobs
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-02-24 13:35:09 UTC
Size:
21.56 KB
patch
obsolete
>From ed727566db5ddff167fa484c5f0342997a3ef028 Mon Sep 17 00:00:00 2001 >From: Jake Deery <jake.deery@openfifth.co.uk> >Date: Mon, 1 Dec 2025 13:09:20 +0000 >Subject: [PATCH] Bug 14962: On Display code for BackgroundJobs > >This patch adds code required to make BackgroundJobs accept and process batch addition and removal of items to and from displays. >Please see the test files commit for instructions on how to test this bundle of patches. > >Sponsored-by: ByWater Solutions >Signed-of-by: Martin Renvoize <martin.renvoize@openfifth.co.uk> >--- > Koha/BackgroundJob.pm | 2 + > Koha/BackgroundJob/BatchAddDisplayItems.pm | 208 ++++++++++++++++++ > Koha/BackgroundJob/BatchDeleteDisplayItems.pm | 193 ++++++++++++++++ > .../batch_add_display_items.inc | 76 +++++++ > .../batch_delete_display_items.inc | 76 +++++++ > .../prog/en/modules/admin/background_jobs.tt | 8 + > 6 files changed, 563 insertions(+) > create mode 100644 Koha/BackgroundJob/BatchAddDisplayItems.pm > create mode 100644 Koha/BackgroundJob/BatchDeleteDisplayItems.pm > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_add_display_items.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_delete_display_items.inc > >diff --git a/Koha/BackgroundJob.pm b/Koha/BackgroundJob.pm >index 0ff204281d6..b4f46904649 100644 >--- a/Koha/BackgroundJob.pm >+++ b/Koha/BackgroundJob.pm >@@ -449,6 +449,8 @@ sub core_types_to_classes { > batch_biblio_record_modification => 'Koha::BackgroundJob::BatchUpdateBiblio', > batch_item_record_deletion => 'Koha::BackgroundJob::BatchDeleteItem', > batch_item_record_modification => 'Koha::BackgroundJob::BatchUpdateItem', >+ batch_add_display_items => 'Koha::BackgroundJob::BatchAddDisplayItems', >+ batch_delete_display_items => 'Koha::BackgroundJob::BatchDeleteDisplayItems', > erm_sushi_harvester => 'Koha::BackgroundJob::ErmSushiHarvester', > batch_hold_cancel => 'Koha::BackgroundJob::BatchCancelHold', > create_eholdings_from_biblios => 'Koha::BackgroundJob::CreateEHoldingsFromBiblios', >diff --git a/Koha/BackgroundJob/BatchAddDisplayItems.pm b/Koha/BackgroundJob/BatchAddDisplayItems.pm >new file mode 100644 >index 00000000000..065defcd1ba >--- /dev/null >+++ b/Koha/BackgroundJob/BatchAddDisplayItems.pm >@@ -0,0 +1,208 @@ >+package Koha::BackgroundJob::BatchAddDisplayItems; >+ >+# Copyright 2025-2026 Open Fifth Ltd >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use Try::Tiny qw( catch try ); >+use C4::Context; >+ >+use Koha::DateUtils qw(dt_from_string); >+use Koha::DisplayItem; >+use Koha::DisplayItems; >+use Koha::Items; >+use Koha::Displays; >+ >+use base 'Koha::BackgroundJob'; >+ >+=head1 NAME >+ >+Koha::BackgroundJob::BatchAddDisplayItems - Background job to add multiple items to a display >+ >+=head1 API >+ >+=head2 Class methods >+ >+=head3 job_type >+ >+Return the job type 'batch_add_display_items'. >+ >+=cut >+ >+sub job_type { >+ return 'batch_add_display_items'; >+} >+ >+=head3 process >+ >+Process the batch addition of items to a display >+ >+=cut >+ >+sub process { >+ my ( $self, $args ) = @_; >+ >+ if ( $self->status eq 'cancelled' ) { >+ return; >+ } >+ >+ $self->start; >+ >+ my $display_id = $args->{display_id}; >+ my @barcodes = @{ $args->{barcodes} }; >+ my $date_remove = $args->{date_remove}; >+ >+ my $report = { >+ total_records => scalar @barcodes, >+ total_success => 0, >+ total_errors => 0, >+ }; >+ >+ my @messages; >+ my @added_items; >+ my @failed_items; >+ >+ # Validate display exists >+ my $display = Koha::Displays->find($display_id); >+ unless ($display) { >+ push @messages, { >+ type => 'error', >+ code => 'display_not_found', >+ display_id => $display_id, >+ }; >+ $self->finish( { messages => \@messages, report => $report } ); >+ return; >+ } >+ >+ # Calculate date_remove from display_days if not provided >+ unless ($date_remove) { >+ if ( $display->display_days ) { >+ my $dt = dt_from_string(); >+ $dt->add( days => $display->display_days ); >+ $date_remove = $dt->ymd; >+ } >+ } >+ >+ try { >+ my $schema = Koha::Database->new->schema; >+ $schema->txn_do( >+ sub { >+ for my $barcode ( sort { $a <=> $b } @barcodes ) { >+ >+ last if $self->get_from_storage->status eq 'cancelled'; >+ >+ my $item = Koha::Items->find( { barcode => $barcode } ); >+ unless ($item) { >+ push @failed_items, { >+ barcode => $barcode, >+ error => 'Item with barcode not found' >+ }; >+ $report->{total_errors}++; >+ next; >+ } >+ my $itemnumber = $item->itemnumber; >+ >+ # Check if item is already in this display >+ my $existing = Koha::DisplayItems->search( >+ { >+ display_id => $display_id, >+ itemnumber => $itemnumber, >+ } >+ )->next; >+ >+ if ($existing) { >+ push @failed_items, { >+ itemnumber => $itemnumber, >+ barcode => $barcode, >+ error => 'Item already in display' >+ }; >+ $report->{total_errors}++; >+ next; >+ } >+ >+ # Create display item >+ my $display_item = Koha::DisplayItem->new( >+ { >+ display_id => $display_id, >+ itemnumber => $itemnumber, >+ biblionumber => $item->biblionumber, >+ date_remove => $date_remove, >+ } >+ )->store; >+ >+ push @added_items, { >+ display_item_id => $display_item->display_item_id, >+ itemnumber => $itemnumber, >+ barcode => $barcode, >+ biblionumber => $item->biblionumber, >+ }; >+ >+ $report->{total_success}++; >+ $self->step; >+ } >+ } >+ ); >+ } catch { >+ warn $_; >+ push @messages, { >+ type => 'error', >+ code => 'unknown', >+ error => $_, >+ }; >+ die "Something terrible has happened!" if ( $_ =~ /Rollback failed/ ); >+ }; >+ >+ $report->{display_id} = $display_id; >+ $report->{added_items} = \@added_items; >+ $report->{failed_items} = \@failed_items; >+ >+ my $data = $self->decoded_data; >+ $data->{messages} = \@messages; >+ $data->{report} = $report; >+ >+ $self->finish($data); >+} >+ >+=head3 enqueue >+ >+Enqueue the job. >+ >+=cut >+ >+sub enqueue { >+ my ( $self, $args ) = @_; >+ >+ return unless exists $args->{barcodes} && exists $args->{display_id}; >+ >+ my @barcodes = @{ $args->{barcodes} }; >+ my $display_id = $args->{display_id}; >+ my $date_remove = $args->{date_remove}; >+ >+ $self->SUPER::enqueue( >+ { >+ job_size => scalar @barcodes, >+ job_args => { >+ display_id => $display_id, >+ barcodes => \@barcodes, >+ date_remove => $date_remove, >+ }, >+ job_queue => 'long_tasks', >+ } >+ ); >+} >+ >+1; >diff --git a/Koha/BackgroundJob/BatchDeleteDisplayItems.pm b/Koha/BackgroundJob/BatchDeleteDisplayItems.pm >new file mode 100644 >index 00000000000..c24c29c1f83 >--- /dev/null >+++ b/Koha/BackgroundJob/BatchDeleteDisplayItems.pm >@@ -0,0 +1,193 @@ >+package Koha::BackgroundJob::BatchDeleteDisplayItems; >+ >+# Copyright 2025-2026 Open Fifth Ltd >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use Try::Tiny qw( catch try ); >+ >+use Koha::DisplayItem; >+use Koha::DisplayItems; >+use Koha::Items; >+ >+use base 'Koha::BackgroundJob'; >+ >+=head1 NAME >+ >+Koha::BackgroundJob::BatchDeleteDisplayItems - Background job to remove multiple items from displays >+ >+=head1 API >+ >+=head2 Class methods >+ >+=head3 job_type >+ >+Return the job type 'batch_delete_display_items'. >+ >+=cut >+ >+sub job_type { >+ return 'batch_delete_display_items'; >+} >+ >+=head3 process >+ >+Process the batch removal of items from displays >+ >+=cut >+ >+sub process { >+ my ( $self, $args ) = @_; >+ >+ if ( $self->status eq 'cancelled' ) { >+ return; >+ } >+ >+ $self->start; >+ >+ my @barcodes = @{ $args->{barcodes} }; >+ my $display_id = $args->{display_id}; # Optional: if specified, only remove from this display >+ >+ my $report = { >+ total_records => scalar @barcodes, >+ total_success => 0, >+ total_errors => 0, >+ }; >+ >+ my @messages; >+ my @deleted_items; >+ my @failed_items; >+ >+ try { >+ my $schema = Koha::Database->new->schema; >+ $schema->txn_do( >+ sub { >+ for my $barcode ( sort { $a <=> $b } @barcodes ) { >+ >+ last if $self->get_from_storage->status eq 'cancelled'; >+ >+ my $item = Koha::Items->find( { barcode => $barcode } ); >+ unless ($item) { >+ push @failed_items, { >+ barcode => $barcode, >+ error => 'Item with barcode not found' >+ }; >+ $report->{total_errors}++; >+ next; >+ } >+ my $itemnumber = $item->itemnumber; >+ >+ # Build search criteria >+ my $search_criteria = { itemnumber => $itemnumber }; >+ $search_criteria->{display_id} = $display_id >+ if $display_id; >+ >+ my $display_items = Koha::DisplayItems->search($search_criteria); >+ >+ unless ( $display_items->count ) { >+ push @failed_items, { >+ itemnumber => $itemnumber, >+ barcode => $barcode, >+ error => $display_id >+ ? 'Item not found in specified display' >+ : 'Item not found in any display' >+ }; >+ $report->{total_errors}++; >+ next; >+ } >+ >+ my $deleted_count = 0; >+ my @deleted_from_displays; >+ >+ while ( my $display_item = $display_items->next ) { >+ push @deleted_from_displays, { >+ display_id => $display_item->display_id, >+ display_item_id => $display_item->display_item_id, >+ }; >+ $display_item->delete; >+ $deleted_count++; >+ } >+ >+ if ( $deleted_count > 0 ) { >+ push @deleted_items, { >+ itemnumber => $itemnumber, >+ barcode => $barcode, >+ displays_removed_from => \@deleted_from_displays, >+ count => $deleted_count, >+ }; >+ $report->{total_success}++; >+ } else { >+ push @failed_items, { >+ itemnumber => $itemnumber, >+ barcode => $barcode, >+ error => 'No display items could be deleted' >+ }; >+ $report->{total_errors}++; >+ } >+ >+ $self->step; >+ } >+ } >+ ); >+ } catch { >+ warn $_; >+ push @messages, { >+ type => 'error', >+ code => 'unknown', >+ error => $_, >+ }; >+ die "Something terrible has happened!" if ( $_ =~ /Rollback failed/ ); >+ }; >+ >+ $report->{display_id} = $display_id; >+ $report->{deleted_items} = \@deleted_items; >+ $report->{failed_items} = \@failed_items; >+ >+ my $data = $self->decoded_data; >+ $data->{messages} = \@messages; >+ $data->{report} = $report; >+ >+ $self->finish($data); >+} >+ >+=head3 enqueue >+ >+Enqueue the job. >+ >+=cut >+ >+sub enqueue { >+ my ( $self, $args ) = @_; >+ >+ return unless exists $args->{barcodes}; >+ >+ my @barcodes = @{ $args->{barcodes} }; >+ my $display_id = $args->{display_id}; # Optional >+ >+ $self->SUPER::enqueue( >+ { >+ job_size => scalar @barcodes, >+ job_args => { >+ barcodes => \@barcodes, >+ display_id => $display_id, >+ }, >+ job_queue => 'long_tasks', >+ } >+ ); >+} >+ >+1; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_add_display_items.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_add_display_items.inc >new file mode 100644 >index 00000000000..d921cd19d3e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_add_display_items.inc >@@ -0,0 +1,76 @@ >+[% USE Koha %] >+ >+[% BLOCK report %] >+ [% SET report = job.report %] >+ [% IF report %] >+ [% IF job.status == 'finished' %] >+ [% IF report.total_errors > 0 %] >+ <div class="alert alert-alert"> Processed [% report.total_success | html %] of [% report.total_records | html %] with [% report.total_errors | html %] errors</div> >+ [% ELSE %] >+ <div class="alert alert-success"> Processed [% report.total_success | html %] of [% report.total_records | html %]</div> >+ [% END %] >+ [% ELSIF job.status == 'cancelled' %] >+ <span> The job has been cancelled before it finished.</span> >+ [% END %] >+ [% END %] >+[% END %] >+ >+[% BLOCK detail %] >+ [% SET messages = job.messages %] >+ [% IF messages && (messages.size > 0) %] >+ <h4>Messages</h4> >+ <table> >+ <tr> >+ <th>Type</th> >+ <th>Code</th> >+ <th>Error</th> >+ </tr> >+ [% FOR message IN messages %] >+ <tr> >+ <td>[% message.type | html %]</td> >+ <td>[% message.code | html %]</td> >+ <td>[% message.error | html %]</td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% SET report = job.report %] >+ [% IF report %] >+ [% IF (report.added_items.size > 0) %] >+ <h4>Added items</h4> >+ <table> >+ <tr> >+ <th>Item barcode</th> >+ <th>Display number</th> >+ </tr> >+ [% FOR added_item IN report.added_items %] >+ <tr> >+ <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% added_item.itemnumber | url %]">[% added_item.barcode | html %]</a></td> >+ <td><a href="/cgi-bin/koha/display/displays/[% report.display_id | url %]">[% report.display_id | html %]</a></td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% IF (report.failed_items.size > 0) %] >+ <h4>Failed items</h4> >+ <table> >+ <tr> >+ <th>Item barcode</th> >+ <th>Error message</th> >+ </tr> >+ [% FOR failed_item IN report.failed_items %] >+ <tr> >+ <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% failed_item.itemnumber | url %]">[% failed_item.barcode | html %]</a></td> >+ <td>[% failed_item.error | html %]</td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% END %] >+ [% IF job.status == 'cancelled' %] >+ <span> The job has been cancelled before it finished.</span> >+ [% END %] >+[% END %] >+ >+[% BLOCK js %] >+[% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_delete_display_items.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_delete_display_items.inc >new file mode 100644 >index 00000000000..064900baaec >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_delete_display_items.inc >@@ -0,0 +1,76 @@ >+[% USE Koha %] >+ >+[% BLOCK report %] >+ [% SET report = job.report %] >+ [% IF report %] >+ [% IF job.status == 'finished' %] >+ [% IF report.total_errors > 0 %] >+ <div class="alert alert-alert"> Processed [% report.total_success | html %] of [% report.total_records | html %] with [% report.total_errors | html %] errors</div> >+ [% ELSE %] >+ <div class="alert alert-success"> Processed [% report.total_success | html %] of [% report.total_records | html %]</div> >+ [% END %] >+ [% ELSIF job.status == 'cancelled' %] >+ <span> The job has been cancelled before it finished.</span> >+ [% END %] >+ [% END %] >+[% END %] >+ >+[% BLOCK detail %] >+ [% SET messages = job.messages %] >+ [% IF messages && (messages.size > 0) %] >+ <h4>Messages</h4> >+ <table> >+ <tr> >+ <th>Type</th> >+ <th>Code</th> >+ <th>Error</th> >+ </tr> >+ [% FOR message IN messages %] >+ <tr> >+ <td>[% message.type | html %]</td> >+ <td>[% message.code | html %]</td> >+ <td>[% message.error | html %]</td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% SET report = job.report %] >+ [% IF report %] >+ [% IF (report.deleted_items.size > 0) %] >+ <h4>Deleted items</h4> >+ <table> >+ <tr> >+ <th>Item barcode</th> >+ <th>Display number</th> >+ </tr> >+ [% FOR deleted_item IN report.deleted_items %] >+ <tr> >+ <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% deleted_item.itemnumber | url %]">[% deleted_item.barcode | html %]</a></td> >+ <td><a href="/cgi-bin/koha/display/displays/[% report.display_id | url %]">[% report.display_id | html %]</a></td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% IF (report.failed_items.size > 0) %] >+ <h4>Failed items</h4> >+ <table> >+ <tr> >+ <th>Item barcode</th> >+ <th>Error message</th> >+ </tr> >+ [% FOR failed_item IN report.failed_items %] >+ <tr> >+ <td><a href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% failed_item.itemnumber | url %]">[% failed_item.barcode | html %]</a></td> >+ <td>[% failed_item.error | html %]</td> >+ </tr> >+ [% END %] >+ </table> >+ [% END %] >+ [% END %] >+ [% IF job.status == 'cancelled' %] >+ <span> The job has been cancelled before it finished.</span> >+ [% END %] >+[% END %] >+ >+[% BLOCK js %] >+[% END %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >index eccc21bb977..8a0578e8930 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >@@ -182,6 +182,14 @@ > '_id': 'batch_item_record_modification', > '_str': _("Batch item record modification").escapeHtml() > }, >+ { >+ '_id': 'batch_add_display_items', >+ '_str': _("Batch add display items").escapeHtml() >+ }, >+ { >+ '_id': 'batch_delete_display_items', >+ '_str': _("Batch delete display items").escapeHtml() >+ }, > { > '_id': 'batch_item_record_deletion', > '_str': _("Batch item record deletion").escapeHtml() >-- >2.53.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 14962
:
190216
|
190217
|
190218
|
190219
|
190220
|
190221
|
190222
|
190223
|
190224
|
190225
|
190226
|
190227
|
190228
|
190229
|
190230
|
190231
|
190232
|
190571
|
190572
|
193719
|
193720
|
193721
|
193722
|
193723
|
193724
|
193725
|
193726
|
193727
|
193728
|
193729
|
193730
|
193731
|
193732
|
193733
|
193734
|
193735
|
193736
|
193737
|
193738
|
193739
|
193740
|
193741
|
193742
|
193743
|
193744
|
193745
|
193746
|
193747
|
193748
|
193749
|
193750
|
193751
|
193752
|
193753
|
193754
|
193755
|
194285
|
194286
|
194287
|
194288
|
194289
|
194290
|
194291
|
194292
|
194293
|
194294
|
194295
|
194296
|
194297
|
194298
|
194299
|
194300
|
194301
|
194302
|
194303
|
194304
|
194305
|
194306
|
194307
|
194308
|
194309
|
194310
|
194311
|
194312
|
194313
|
194314
|
194315
|
194316
|
194317
|
194318
|
194320
|
194321
|
194322