Bugzilla – Attachment 192447 Details for
Bug 26355
Allow patrons to self-renew through the OPAC
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 26355: Update opac-user and opac-memberentry to support the renewal workflow
Bug-26355-Update-opac-user-and-opac-memberentry-to.patch (text/plain), 23.76 KB, created by
Matt Blenkinsop
on 2026-02-04 10:57:13 UTC
(
hide
)
Description:
Bug 26355: Update opac-user and opac-memberentry to support the renewal workflow
Filename:
MIME Type:
Creator:
Matt Blenkinsop
Created:
2026-02-04 10:57:13 UTC
Size:
23.76 KB
patch
obsolete
>From 5dc09da263cd3c3a790b4c2dd392399de1810572 Mon Sep 17 00:00:00 2001 >From: Matt Blenkinsop <matt.blenkinsop@openfifth.co.uk> >Date: Wed, 21 Jan 2026 09:46:08 +0000 >Subject: [PATCH] Bug 26355: Update opac-user and opac-memberentry to support > the renewal workflow >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >This is part of a sponsored piece of development from one of our customers. There was a specification of requirements but I was conscious of the ideas already discussed on this bug and wanted to try and develop a solution that would balance both sets of requirements. The concept works as follows: >- Self-renewal can be enabled and controlled at a category level > - The window within which it is available is customisable (both pre and post-expiry) > - Defaults to the NotifyBorrowerDeparture syspref > - The amount in fines that block self-renewal is customisable > - Defaults to the category level charges and then the noissescharge preference > - The message to display if renewal fails is customisable >- Libraries can also add additional verification steps. This was a requirement for our customer and to enable it Iâve opted to use Patron attribute types because these are already configurable by category and library. This means you can add additional verification questions such as âDo you still live within X miles of the library?â >- The self-renewal process will also respect the OPACPatronDetails and AutoApprovePatronProfileSettings sysprefs. If OPACPatronDetails is enabled, the renewal will redirect the patron to the OPAC details form and ask them to confirm their details are still correct. Any changes will be logged as patron modifications and approved in line with the auto approval syspref >- Renewal happens regardless of whether OPACPatronDetails is enabled, only the details changes are logged as modification requests >- Notifications are sent to the patron in line with their messaging preferences > >Test plan: >1. Apply all patches and reset_all >2. In the intranet, navigate to patron categories and edit the âPatronâ category >3. Scroll to the bottom, there should be a section called âAccount expiry and self-renewalâ >4. Set âAllow self-renewalâ to Yes >5. Set Self-renewal availability period to be 10 days >6. Set Self-renewal availability post-expiryâ to also be 10 days and save >7. In system preferences, set the OPACPatronDetails preference to âDonât allowâ >8. Choose a patron that has the âPatronâ type category >9. Edit that patron so that you can log into the OPAC and set their expiry date to be yesterdayâs date >10. Log into the OPAC as that patron (it may be easiest to do this in incognito as we will be swapping between the intranet and OPAC throughout the test plan and it will remove the need to keep logging out and in) >11. You should see a message that says âYou are eligible for self-renewal. Please click here to renew your account.â >12. Click that link - you will be presented with a modal that says âAre you sure you want to renew your account?â >13. Click âCloseâ - we donât want to complete renewal yet as there is more functionality available to test >14. Navigate to the patron category again, set âSelf-renewal availability post-expiryâ to be 0 >15. Refresh the OPAC, the self-renewal link should not be there as we are now outside the patron self-renewal window >16. Undo that change in the patron category, the link should reappear when refreshing the OPAC >17. Now set the patronâs expiry date to be tomorrow >18. In the category, set âSelf-renewal availability periodâ to 0 >19. Refresh the OPAC, it should still show the link as it is defaulting to NotifyBorrowerDeparture >20. Set NotifyBorrowerDeparture to 0 >21. Refresh the OPAC, the link should vanish >22. In the category, restore âSelf-renewal availability periodâ to 10 days >23. Refresh the OPAC, the link should reappear >24. In the intranet, set the patron to be debarred using a manual restriction >25. In the OPAC refresh the page - self-renewal should no longer be available >26. Remove the restriction - the renewal link will be visible again >27. Set a fine on the patron record of 6 USD >28. Refresh the OPAC, the self-renewal link will be gone because we are over the global ânoissueschargeâ preference value of 5 >29. In the category, set âCheckout charge limitâ to 7 >30. Refresh the OPAC, the link will be visible because we are now under the category charge >31. Add an additional fine of 2 to the patron account >32. Refresh the OPAC, the self-renewal link is gone >33. In the category, set âSelf-renewal charge limitâ to be 10 >34. Refresh the OPAC, the link will be visible again >35. In the category, set âSelf-renewal failure messageâ to be whatever you want >36. In the OPAC, refresh the page and then click into the self-renewal process >37. This time, click âNoâ - you should see the failure message you have just set >38. Click âCloseâ and refresh the page to reset this message >39. In the Intranet, navigate to Admin > Patron attribute types >40. Click to create a new attribute >41. The code isnât important here but we do need a description - this can be anything for the plan, for example âDo you still live locally to this library?â >42. Tick the new checkbox - Self-renewal verification check >43. Create a second attribute, this time set the description to something like âWe donât want to see this one!â. Do NOT click the self-renewal checkbox this time >44. In the OPAC, refresh the page and then click the renewal link again >45. You should be met with the modal, this time asking the verification question we just set up in the patron attributes >46. Click âYesâ - you should see the âAre you sure you want to renew your account?â message again >47. The second attribute that we set up without the self-renewal checkbox selected should not appear >48. This time when prompted with the âAre you sureâ message, click âYesâ >49. The page should refresh and display a confirmation with the new expiry date. >50. In the intranet, view the patronâs record - the date should have been updated >51. In the Modification log tab, there should be a log entry for âMembership renewedâ >52. Change the patronâs expiry date again, this time to be yesterdayâs date >53. In the OPAC, click on âPersonal detailâ, the new expiry date should show there >54. Now set the OPACPatronDetails syspref to be âAllowâ >55. Click on âSummaryâ again, the self-renewal link should be there again >56. Click into the self-renewal process and click âYesâ on your verification step >57. This time you should be met with a message that says âConfirm your account detailsâ >58. Click âContinueâ - you will be redirected to the OPAC details form >59. There should be a message at the top that says âPlease verify your details to proceed with your self-renewal.â >60. Update a field somewhere on the patron form >61. Scroll to the bottom - the button should say âSubmit renewal requestâ >62. Click this button >63. You will be shown the renewal confirmation message >64. In the intranet, on the homepage you should now see a modification request >65. Check the request, it should match the fields you changed - donât approve or deny for now >66. Set âAutoApprovePatronProfileSettingsâ in sysprefs to âEnableâ >67. Reset your patronâs expiry date to todayâs date >68. In the OPAC, click into Personal details and then back into Summary again (we need to do this to remove the success parameter from the URL) >69. The renewal link will be visible again - go through the process again >70. On the form, you should see an alert that you have already sent a modification request >71. This time, change a different field to the previous attempt >72. Submit the form >73. In the intranet there should be no modification this time and the change should be visible in the patron record as it has been automatically approved and handled as per the syspref >74. prove t/db_dependent/Koha/Patron.t >75. prove t/db_dependent/api/v1/patrons_self_renewal.t >76. yarn cypress run --spec t/cypress/integration/Islands/SelfRenewal.ts >77. Sign off! > >Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/> >Sponsored-by: Royal Borough of Kensington and Chelsea <https://www.rbkc.gov.uk/> >Sponsored-by: LibraryOn > >Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk> >--- > .../bootstrap/en/modules/opac-memberentry.tt | 60 ++++++++++++++++--- > .../bootstrap/en/modules/opac-user.tt | 41 ++++++++++++- > opac/opac-memberentry.pl | 23 ++----- > opac/opac-user.pl | 37 ++++++++---- > 4 files changed, 121 insertions(+), 40 deletions(-) > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >index 8a7563c7756..97fbbbe316f 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-memberentry.tt >@@ -92,6 +92,9 @@ > [% IF nochanges %] > <div class="alert alert-error">No changes were made.</div> > [% END %] >+ [% IF self_renewal %] >+ <div class="alert alert-info">Please verify your details to proceed with your self-renewal.</div> >+ [% END %] > [% END %] > > [% IF empty_mandatory_fields %] >@@ -241,7 +244,7 @@ > > > [% END %] > >- <form method="post" action="/cgi-bin/koha/opac-memberentry.pl" id="memberentry-form" autocomplete="off"> >+ <form method="post" [% IF (self_renewal) %]action="/api/v1/public/patrons/self_renewal" id="self-renewal-form"[% ELSE %]action="/cgi-bin/koha/opac-memberentry.pl" id="memberentry-form"[% END %] autocomplete="off"> > [% INCLUDE 'csrf-token.inc' %] > > [% FOREACH field = ['streetnumber' 'streettype' 'cardnumber' 'branchcode' 'categorycode' 'title' 'surname' 'firstname' 'preferred_name' 'middle_name' 'dateofbirth' 'initials' 'pronouns' 'othernames' 'address' 'address2' 'city' 'state' 'zipcode' 'country' 'phone' 'phonepro' 'mobile' 'email' 'emailpro' 'fax' 'B_streettype' 'B_streetnumber' 'B_address' 'B_address2' 'B_city' 'B_state' 'B_zipcode' 'B_country' 'B_phone' 'B_email' 'contactnote' 'altcontactsurname' 'altcontactfirstname' 'altcontactaddress1' 'altcontactaddress2' 'altcontactaddress3' 'altcontactstate' 'altcontactzipcode' 'altcontactcountry' 'altcontactphone' 'password' 'lang' ] %] >@@ -1362,7 +1365,11 @@ > [% IF OPACPatronDetails %] > <fieldset class="action"> > <input type="hidden" name="op" value="cud-update" /> >- <input type="submit" class="btn btn-primary" value="Submit update request" /> >+ [% IF (self_renewal) %] >+ <input type="submit" class="btn btn-primary" value="Submit renewal request" /> >+ [% ELSE %] >+ <input type="submit" class="btn btn-primary" value="Submit update request" /> >+ [% END %] > </fieldset> > [% END %] > [% ELSE %] >@@ -1391,16 +1398,23 @@ > [% INCLUDE 'calendar.inc' %] > <script> > $(document).ready(function() { >+ let formName >+ [% IF (self_renewal) %] >+ formName = 'self-renewal-form' >+ [% ELSE %] >+ formName = 'memberentry-form' >+ [% END %] >+ > [% IF op == 'edit' && !OPACPatronDetails %] >- $("#memberentry-form :input").attr('readonly', true); >+ $("#" + formName + " :input").attr('readonly', true); > $("#borrower_branchcode").attr('disabled',true); > $("#borrower_title").attr('disabled',true); >- $('#memberentry-form :radio').attr('disabled',true); >+ $('#' + formName + ' :radio').attr('disabled',true); > $('span.required').remove(); > $('label.required').removeClass('required'); > [% END %] > >- $("#memberentry-form").validate({ >+ $("#" + formName).validate({ > rules: { > borrower_email: { > email: true >@@ -1430,8 +1444,40 @@ > return false; > } > else { >- form.beenSubmitted = true; >- form.submit(); >+ if(formName === 'self-renewal-form') { >+ const formValues = $(form).serializeArray() >+ const renewalBody = formValues.reduce((body, curr, i) => { >+ if(curr.name.includes("borrower")) { >+ const fieldName = curr.name.replace("borrower_", "") >+ body[fieldName] = curr.value >+ } >+ if(curr.name === "patron_attribute_code") { >+ const matchingValue = formValues[i + 1].value >+ body.extended_attributes.push({code: curr.value, attribute: matchingValue}) >+ } >+ return body >+ }, {extended_attributes: []}) >+ const options = { >+ url: $(form).attr('action'), >+ method: $(form).attr('method').toUpperCase(), >+ contentType: 'application/json', >+ data: JSON.stringify(renewalBody) >+ }; >+ $.ajax(options) >+ .then(function(result) { >+ let newLocation = "/cgi-bin/koha/opac-user.pl?self_renewal_success=" + result.expiry_date >+ if(result.confirmation_sent) { >+ newLocation += "&confirmation_sent=1" >+ } >+ document.location = newLocation >+ }) >+ .fail(function(err) { >+ document.location = "/cgi-bin/koha/opac-user.pl?self_renewal_success=0"; >+ }); >+ } else { >+ form.beenSubmitted = true; >+ form.submit(); >+ } > } > } > }); >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >index 5c8edacfe22..2096477123c 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt >@@ -1,4 +1,5 @@ > [% USE raw %] >+[% USE To %] > [% USE Asset %] > [% USE Koha %] > [% USE KohaDates %] >@@ -119,18 +120,41 @@ > </div> > [% END %] > >- [% IF ( borrower.warndeparture ) %] >+ [% IF ( borrower.warndeparture) %] > <div class="alert alert-warning" id="warndeparture"> >- <strong>Please note:</strong><span> Your library card will expire on <span id="warndeparture_date">[% borrower.warndeparture | $KohaDates %]</span>. Please contact the library for more information.</span> >+ <strong>Please note:</strong >+ ><span> >+ Your library card will expire on <span id="warndeparture_date">[% borrower.warndeparture | $KohaDates %]</span>. >+ [% IF ( !self_renewal_available) %]Please contact the library for more information.[% END %]</span >+ > > [% IF ( borrower.returnbeforeexpiry ) %]<span id="warndeparture_returnbeforeexpiry"> Also note that you must return all checked out items before your library card expires.</span>[% END %] > </div> > [% END %] >- > [% IF ( borrower.warnexpired ) %] > <div class="alert alert-warning" id="warnexpired"> > <strong>Please note: </strong><span>Your account has expired as of [% borrower.warnexpired | $KohaDates %]. Please contact the library if you wish to renew your account.</span> > </div> > [% END %] >+ [% IF ( self_renewal_available && !self_renewal_alert ) %] >+ <div class="alert alert-info" id="self_renewal_available"> >+ [% IF ( self_renewal_available ) %] >+ <span> You are eligible for self-renewal. <a href="#patronSelfRenewal" data-bs-toggle="modal">Please click here to renew your account.</a></span> >+ [% END %] >+ </div> >+ [% END %] >+ [% IF ( self_renewal_success ) %] >+ <div class="alert alert-success" id="self_renewal_success"> >+ <span>Your account has been successfully renewed until [% self_renewal_success | $KohaDates %].</span> >+ [% IF ( self_renewal_confirmation_sent ) %] >+ <span>You should receive a confirmation shortly.</span> >+ [% END %] >+ </div> >+ [% END %] >+ [% IF ( self_renewal_alert ) %] >+ <div class="alert alert-warning" id="self_renewal_alert"> >+ <span>[% self_renewal_alert | html %]</span> >+ </div> >+ [% END %] > > [% IF ( RENEW_ERROR ) %] > <div class="alert alert-warning"> >@@ -1003,6 +1027,17 @@ > </main> > <!-- /#main --> > >+[% IF ( self_renewal_available ) %] >+ <patron-self-renewal></patron-self-renewal> >+[% END %] >+ >+[% SET islands = Asset.js("js/vue/dist/islands.esm.js").match('(src="([^"]+)")').1 %] >+<script src="[% islands | $raw %]" type="module"> >+</script> >+<script type="module"> >+ import { hydrate } from "[% islands | $raw %]"; >+ hydrate(); >+</script> > [%# hold suspend modal form %] > <div id="suspendHoldModal" class="modal" tabindex="-1" role="dialog" aria-labelledby="suspendModalLabel" aria-hidden="true" data-focus="false"> > <div class="modal-dialog"> >diff --git a/opac/opac-memberentry.pl b/opac/opac-memberentry.pl >index ce530d4bb15..0e4c4441971 100755 >--- a/opac/opac-memberentry.pl >+++ b/opac/opac-memberentry.pl >@@ -354,22 +354,8 @@ if ( $op eq 'cud-create' ) { > $borrower_changes{borrowernumber} = $borrowernumber; > $borrower_changes{extended_attributes} = to_json($extended_attributes_changes); > >- Koha::Patron::Modifications->search( { borrowernumber => $borrowernumber } )->delete; >- >- $borrower_changes{verification_token} = q{}; # prevent warn Missing value for PK column >- my $m = Koha::Patron::Modification->new( \%borrower_changes )->store(); >- >- #Automatically approve patron profile changes if set in syspref >- >- if ( C4::Context->preference('AutoApprovePatronProfileSettings') ) { >- >- # Need to get the object from database, otherwise it is not complete enough to allow deletion >- # when approval has been performed. >- my $tmp_m = Koha::Patron::Modifications->find( { borrowernumber => $borrowernumber } ); >- $tmp_m->approve() if $tmp_m; >- } >- > my $patron = Koha::Patrons->find($borrowernumber); >+ $patron->request_modification( \%borrower_changes ); > $template->param( borrower => $patron->unblessed ); > } else { > my $patron = Koha::Patrons->find($borrowernumber); >@@ -385,9 +371,12 @@ if ( $op eq 'cud-create' ) { > my $patron = Koha::Patrons->find($borrowernumber); > my $borrower = $patron->unblessed; > >+ my $self_renewal = $cgi->param('self_renewal') || 0; >+ > $template->param( >- borrower => $borrower, >- hidden => GetHiddenFields( $mandatory, 'edit' ), >+ borrower => $borrower, >+ hidden => GetHiddenFields( $mandatory, 'edit' ), >+ self_renewal => $self_renewal > ); > > if ( C4::Context->preference('OPACpatronimages') ) { >diff --git a/opac/opac-user.pl b/opac/opac-user.pl >index e0671b33aa5..5aa491879cb 100755 >--- a/opac/opac-user.pl >+++ b/opac/opac-user.pl >@@ -419,20 +419,31 @@ if ( C4::Context->preference("ArticleRequests") ) { > ); > } > >+my $self_renewal_available = $patron->is_eligible_for_self_renewal(); >+my $self_renewal_alert; >+my $self_renewal_success = $query->param('self_renewal_success'); >+$self_renewal_alert = $patron->category->self_renewal_failure_message >+ if defined $self_renewal_success && !$self_renewal_success; >+my $self_renewal_confirmation_sent = $query->param('confirmation_sent'); >+ > $template->param( >- patron_messages => $patron_messages, >- opacnote => $borr->{opacnote}, >- patronupdate => $patronupdate, >- OpacRenewalAllowed => C4::Context->preference("OpacRenewalAllowed"), >- userview => 1, >- tab => scalar $query->param('tab') || 0, >- SuspendHoldsOpac => C4::Context->preference('SuspendHoldsOpac'), >- AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'), >- OpacHoldNotes => C4::Context->preference('OpacHoldNotes'), >- failed_holds => scalar $query->param('failed_holds'), >- opac_user_holds => scalar $query->param('opac-user-holds') || 0, >- opac_user_overdues => scalar $query->param('opac-user-overdues') || 0, >- opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0, >+ patron_messages => $patron_messages, >+ opacnote => $borr->{opacnote}, >+ patronupdate => $patronupdate, >+ OpacRenewalAllowed => C4::Context->preference("OpacRenewalAllowed"), >+ userview => 1, >+ tab => scalar $query->param('tab') || 0, >+ SuspendHoldsOpac => C4::Context->preference('SuspendHoldsOpac'), >+ AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'), >+ OpacHoldNotes => C4::Context->preference('OpacHoldNotes'), >+ failed_holds => scalar $query->param('failed_holds'), >+ opac_user_holds => scalar $query->param('opac-user-holds') || 0, >+ opac_user_overdues => scalar $query->param('opac-user-overdues') || 0, >+ opac_user_article_requests => scalar $query->param('opac-user-article-requests') || 0, >+ self_renewal_available => $self_renewal_available, >+ self_renewal_success => $self_renewal_success, >+ self_renewal_alert => $self_renewal_alert, >+ self_renewal_confirmation_sent => $self_renewal_confirmation_sent, > ); > > # if not an empty string this indicates to return >-- >2.39.5
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 26355
:
191781
|
191782
|
191783
|
191784
|
191785
|
191786
|
191787
|
191788
|
191789
|
192440
|
192441
|
192442
|
192443
|
192444
|
192445
|
192446
| 192447 |
192448