Bugzilla – Attachment 186374 Details for
Bug 19814
Batch Check-in function
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 19814: Add batch return functionality for RFID workflows
Bug-19814-Add-batch-return-functionality-for-RFID-.patch (text/plain), 16.35 KB, created by
Martin Renvoize (ashimema)
on 2025-09-11 13:35:20 UTC
(
hide
)
Description:
Bug 19814: Add batch return functionality for RFID workflows
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2025-09-11 13:35:20 UTC
Size:
16.35 KB
patch
obsolete
>From 24445cd6e7efd5a2fc532089427f6e98ea15ef78 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Thu, 11 Sep 2025 14:24:18 +0100 >Subject: [PATCH] Bug 19814: Add batch return functionality for RFID workflows >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >This proof-of-concept adds batch return capability to the circulation >returns page, designed primarily for RFID pad workflows while maintaining >backward compatibility for single scan returns. > >Key Features: >- Automatic RFID batch detection and processing with debouncing >- Manual batch mode toggle in checkin settings >- Single textarea element that adapts from single-line to multi-line >- Batch results display with per-item status and messages > >Technical Implementation: >- Backend auto-detects batch mode via newline presence in barcode input >- Debouncing (500ms) prevents premature form submission during scanning for multi-line input >- Textarea styling dynamically switches between single-line and multi-line modes >- Batch processing reuses existing AddReturn() logic for consistency >- All existing circulation rules, messages, and workflows preserved > >RFID Workflow: >1. RFID pad scans multiple items รข deposits newline-delimited barcodes >2. Interface auto-expands to show all scanned items >3. Automatic submission after 0.5s delay >4. Batch results display with status for each item > >Manual Workflow: >1. Enable "batch mode" in circulation settings >2. Input field expands to textarea for multiple barcodes >3. Submit to see comprehensive batch results > >This is a proof-of-concept seeking user feedback before >full development and refinement. >--- > circ/returns.pl | 101 ++++++++++- > .../prog/en/modules/circ/returns.tt | 157 +++++++++++++++++- > 2 files changed, 255 insertions(+), 3 deletions(-) > >diff --git a/circ/returns.pl b/circ/returns.pl >index 29b2d2f7bf7..77eafd22e54 100755 >--- a/circ/returns.pl >+++ b/circ/returns.pl >@@ -211,7 +211,16 @@ my $borrower; > my $returned = 0; > my $messages; > my $issue; >-my $barcode = $query->param('barcode'); >+my $barcode_input = $query->param('barcode'); >+my $batch_barcodes = $query->param('batch_barcodes'); >+ >+# Check if barcode input contains multiple lines (batch mode) >+my $is_batch_mode = $barcode_input && $barcode_input =~ /\n/; >+if ($is_batch_mode) { >+ $batch_barcodes = $barcode_input; >+ $barcode_input = undef; # Clear single barcode when in batch mode >+} >+my $barcode = $barcode_input; > my $exemptfine = $query->param('exemptfine'); > if ( $exemptfine > && !C4::Auth::haspermission( C4::Context->userenv->{'id'}, { 'updatecharges' => 'writeoff' } ) ) >@@ -291,6 +300,96 @@ if ( $transit && $op eq 'cud-transfer' ) { > > # actually return book and prepare item table..... > my $returnbranch; >+ >+# Handle batch returns >+my @batch_results; >+if ( $batch_barcodes && $op eq 'cud-checkin' ) { >+ my @barcodes = split /\s*\n\s*/, $batch_barcodes; >+ @barcodes = grep { $_ && $_ !~ /^\s*$/ } @barcodes; # Remove empty lines >+ >+ foreach my $batch_barcode (@barcodes) { >+ $batch_barcode = barcodedecode($batch_barcode) if $batch_barcode; >+ next unless $batch_barcode; >+ >+ my $batch_item = Koha::Items->find( { barcode => $batch_barcode } ); >+ my %batch_result = ( >+ barcode => $batch_barcode, >+ success => 0, >+ messages => {}, >+ borrower => undef, >+ item => $batch_item >+ ); >+ >+ if ($batch_item) { >+ my $batch_itemnumber = $batch_item->itemnumber; >+ >+ # Check if we should display a checkin message >+ my $itemtype = Koha::ItemTypes->find( $batch_item->effective_itemtype ); >+ if ( $itemtype && $itemtype->checkinmsg ) { >+ $batch_result{checkinmsg} = $itemtype->checkinmsg; >+ $batch_result{checkinmsgtype} = $itemtype->checkinmsgtype; >+ } >+ >+ # Process the return date >+ my $return_date = $dropboxmode ? $dropboxdate : dt_from_string($return_date_override); >+ >+ # Check for multi-part confirmation requirement >+ my $needs_confirm = >+ C4::Context->preference("CircConfirmItemParts") >+ && $batch_item->materials >+ && !$query->param('multiple_confirm'); >+ >+ # Check for bundle confirmation requirement >+ my $bundle_confirm = $batch_item->is_bundle >+ && !$query->param('confirm_items_bundle_return'); >+ >+ # Process waiting holds cancellation requests >+ if ($batch_item) { >+ my $waiting_holds_to_be_cancelled = $batch_item->holds->waiting->filter_by_has_cancellation_requests; >+ while ( my $hold = $waiting_holds_to_be_cancelled->next ) { >+ $hold->cancel; >+ } >+ } >+ >+ # Do the actual return unless confirmation needed >+ unless ( $needs_confirm || $bundle_confirm ) { >+ my ( $batch_returned, $batch_messages, $batch_issue, $batch_borrower ) = >+ AddReturn( $batch_barcode, $userenv_branch, $exemptfine, $return_date ); >+ >+ $batch_result{success} = $batch_returned; >+ $batch_result{messages} = $batch_messages; >+ $batch_result{issue} = $batch_issue; >+ $batch_result{borrower} = $batch_borrower; >+ >+ if ($batch_returned) { >+ unshift @checkins, { >+ barcode => $batch_barcode, >+ duedate => $batch_issue ? $batch_issue->date_due : undef, >+ borrowernumber => $batch_issue ? $batch_issue->borrowernumber : undef, >+ not_returned => 0, >+ }; >+ } elsif ( C4::Context->preference('ShowAllCheckins') && !$batch_messages->{'BadBarcode'} ) { >+ unshift @checkins, { >+ barcode => $batch_barcode, >+ duedate => $batch_issue ? $batch_issue->date_due : undef, >+ borrowernumber => $batch_issue ? $batch_issue->borrowernumber : undef, >+ not_returned => 1, >+ }; >+ } >+ } else { >+ $batch_result{needs_confirm} = $needs_confirm; >+ $batch_result{bundle_confirm} = $bundle_confirm; >+ } >+ } else { >+ $batch_result{messages}{'BadBarcode'} = $batch_barcode; >+ } >+ >+ push @batch_results, \%batch_result; >+ } >+ >+ $template->param( batch_results => \@batch_results ); >+} >+ > if ( $barcode && ( $op eq 'cud-checkin' || $op eq 'cud-affect_reserve' ) ) { > $barcode = barcodedecode($barcode) if $barcode; > my $item = Koha::Items->find( { barcode => $barcode } ); >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >index fec7616ac69..ba9762c8efd 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt >@@ -1155,9 +1155,16 @@ > <div class="col-sm-6"> > <div class="form-control-group"> > [% IF ( exemptfine || dropboxmode ) %] >- <input name="barcode" id="barcode" size="14" placeholder="Enter item barcode" class="barcode focus input-warning" type="text" /> >+ <textarea >+ name="barcode" >+ id="barcode" >+ placeholder="Enter item barcode" >+ class="barcode focus input-warning" >+ rows="1" >+ style="width: 240px; height: 2.2em; resize: none; overflow: hidden; white-space: nowrap;" >+ ></textarea> > [% ELSE %] >- <input name="barcode" id="barcode" size="14" placeholder="Enter item barcode" class="barcode focus" type="text" /> >+ <textarea name="barcode" id="barcode" placeholder="Enter item barcode" class="barcode focus" rows="1" style="width: 240px; height: 2.2em; resize: none; overflow: hidden; white-space: nowrap;"></textarea> > [% END %] > <input type="hidden" name="op" value="cud-checkin" /> > >@@ -1245,6 +1252,11 @@ > <label for="dropboxcheck">Book drop mode</label> > </div> > >+ <div id="batch-mode-setting" class="circ-setting"> >+ <input type="checkbox" id="batch-mode-toggle" name="batch_mode" value="batch_mode" /> >+ <label for="batch-mode-toggle">Enable batch mode for multiple returns</label> >+ </div> >+ > [% IF Koha.Preference('ExpireReservesMaxPickUpDelayCharge') %] > <div class="forgive-manual-hold-fees circ-setting"> > [% IF ( forgivemanualholdsexpire ) %] >@@ -1263,6 +1275,73 @@ > </form> > <!-- /#checkin-form --> > >+ [% IF ( batch_results.size ) %] >+ <div class="page-section"> >+ <h2>Batch return results</h2> >+ <table id="batch-results-table" class="table table-striped"> >+ <thead> >+ <tr> >+ <th>Barcode</th> >+ <th>Status</th> >+ <th>Title</th> >+ <th>Patron</th> >+ <th>Messages</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH result IN batch_results %] >+ <tr class="[% IF result.success %]success[% ELSE %]danger[% END %]"> >+ <td>[% result.barcode | html %]</td> >+ <td> >+ [% IF result.success %] >+ <span class="label label-success">Returned</span> >+ [% ELSIF result.needs_confirm %] >+ <span class="label label-warning">Needs confirmation</span> >+ [% ELSIF result.bundle_confirm %] >+ <span class="label label-warning">Bundle confirmation required</span> >+ [% ELSIF result.messages.BadBarcode %] >+ <span class="label label-danger">Invalid barcode</span> >+ [% ELSE %] >+ <span class="label label-danger">Failed</span> >+ [% END %] >+ </td> >+ <td> >+ [% IF result.item %] >+ <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% result.item.biblio.biblionumber | uri %]"> [% result.item.biblio.title | html %] </a> >+ [% END %] >+ </td> >+ <td> >+ [% IF result.borrower %] >+ <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% result.borrower.borrowernumber | uri %]"> [% result.borrower.firstname | html %] [% result.borrower.surname | html %] </a> >+ [% END %] >+ </td> >+ <td> >+ [% IF result.checkinmsg %] >+ <div class="alert alert-[% result.checkinmsgtype || 'info' %]">[% result.checkinmsg | html %]</div> >+ [% END %] >+ [% FOREACH code IN result.messages.keys %] >+ [% IF code == 'WasTransfered' %] >+ <div class="alert alert-info">Item transferred to [% result.messages.TransferTo %]</div> >+ [% ELSIF code == 'NeedsTransfer' %] >+ <div class="alert alert-warning">Item needs transfer</div> >+ [% ELSIF code == 'NotIssued' %] >+ <div class="alert alert-warning">Item was not checked out</div> >+ [% ELSIF code == 'WasLost' %] >+ <div class="alert alert-info">Item was lost, now found</div> >+ [% ELSIF code == 'withdrawn' %] >+ <div class="alert alert-warning">Item is withdrawn</div> >+ [% ELSIF code == 'Debarred' %] >+ <div class="alert alert-danger">Patron is restricted</div> >+ [% END %] >+ [% END %] >+ </td> >+ </tr> >+ [% END %] >+ </tbody> >+ </table> >+ </div> >+ [% END %] >+ > [% IF ( checkins.size ) %] > <div class="page-section"> > <h2>Checked-in items</h2> >@@ -1736,6 +1815,80 @@ > ], > }); > >+ // Debounce variables for RFID batch input >+ let debounceTimer; >+ let isReceivingRFIDInput = false; >+ >+ // Batch mode toggle functionality >+ $('#batch-mode-toggle').on('change', function() { >+ const $barcodeTextarea = $('#barcode'); >+ >+ if ($(this).is(':checked')) { >+ // Switch to multi-line batch mode >+ $barcodeTextarea.attr({ >+ 'placeholder': 'Enter barcodes, one per line', >+ 'rows': 8 >+ }).css({ >+ 'height': 'auto', >+ 'overflow': 'auto', >+ 'white-space': 'pre-wrap', >+ 'resize': 'vertical' >+ }); >+ } else { >+ // Switch back to single-line mode >+ $barcodeTextarea.attr({ >+ 'placeholder': 'Enter item barcode', >+ 'rows': 1 >+ }).css({ >+ 'height': '2.2em', >+ 'overflow': 'hidden', >+ 'white-space': 'nowrap', >+ 'resize': 'none' >+ }); >+ } >+ $barcodeTextarea.focus(); >+ }); >+ >+ // Handle RFID input with debouncing >+ $('#barcode').on('input paste', function(e) { >+ const currentValue = $(this).val(); >+ >+ // Clear any existing timer >+ clearTimeout(debounceTimer); >+ >+ // Check if this looks like RFID batch input (contains newlines) >+ if (currentValue.includes('\n')) { >+ isReceivingRFIDInput = true; >+ >+ // Auto-enable batch mode if not already enabled >+ if (!$('#batch-mode-toggle').is(':checked')) { >+ $('#batch-mode-toggle').prop('checked', true).trigger('change'); >+ } >+ >+ // Set a debounce timer to submit after RFID scanning finishes >+ debounceTimer = setTimeout(function() { >+ isReceivingRFIDInput = false; >+ $('#checkin-form').submit(); >+ }, 500); // 500ms delay after last input >+ } >+ }); >+ >+ // Prevent immediate form submission on Enter if receiving RFID input >+ $('#barcode').on('keydown', function(e) { >+ if (e.which === 13 && isReceivingRFIDInput) { >+ e.preventDefault(); >+ return false; >+ } >+ }); >+ >+ // Prevent form submission during RFID input >+ $('#checkin-form').on('submit', function(e) { >+ if (isReceivingRFIDInput) { >+ e.preventDefault(); >+ return false; >+ } >+ }); >+ > [% IF ( !(Koha.Preference('TransfersBlockCirc')) && Koha.Preference('AutomaticConfirmTransfer') ) %] > $("#wrong-transfer-modal").on('hidden.bs.modal',function(){ > $("#wrongtransferform").submit(); >-- >2.51.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 19814
: 186374 |
186375