From 92d485821faa998c4d3815785bbcfc9f10666bd4 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 30 Oct 2025 14:56:34 +0000 Subject: [PATCH] Bug 23415: Add debt override support to circulation checkout page This patch implements fine override functionality for the checkouts table on the circulation page (circ/circulation.pl), completing the debt override feature for all renewal workflows. Changes: - Added override_debt checkbox to checkouts-table.inc when AllowFineOverrideRenewing preference is enabled - Updated /svc/renew to accept and process override_debt parameter, matching the pattern used for override_limit - Enhanced circulation-api-client.js to pass override_debt parameter through the renewal API call - Implemented specific CSS classes for renewal error types (renewals-allowed-too_many, renewals-allowed-too_much_owing, etc.) to enable selective checkbox control - Modified override checkbox handlers to selectively enable only relevant renewal checkboxes based on the specific blocking error: * override_limit enables checkboxes for 'too_many' and 'on_reserve' * override_debt enables checkboxes for 'too_much_owing' and 'auto_too_much_owing' - Added AllowFineOverrideRenewing JavaScript variable to circulation.tt This provides clear visual feedback to staff about which renewals will be affected by each override option, improving usability and reducing confusion when multiple items have different blocking reasons. --- circ/renew.pl | 4 +-- .../prog/en/includes/checkouts-table.inc | 10 ++++++ .../prog/en/modules/circ/circulation.tt | 1 + .../prog/en/modules/circ/renew.tt | 20 ++++++++---- koha-tmpl/intranet-tmpl/prog/js/checkouts.js | 31 +++++++++++++------ .../prog/js/fetch/circulation-api-client.js | 5 +-- svc/renew | 9 ++++++ .../Circulation/FineNoRenewals_spec.ts | 19 ++++++------ 8 files changed, 70 insertions(+), 29 deletions(-) diff --git a/circ/renew.pl b/circ/renew.pl index 44ab1435fca..67df976745a 100755 --- a/circ/renew.pl +++ b/circ/renew.pl @@ -53,7 +53,7 @@ my $hard_due_date = $cgi->param('hard_due_date'); my ( $item, $checkout, $patron ); my $error = q{}; -my ( $soonest_renew_date, $latest_auto_renew_date, $balance ); +my ( $soonest_renew_date, $latest_auto_renew_date ); if ( $op eq 'cud-renew' && $barcode ) { $barcode = barcodedecode($barcode) if $barcode; @@ -100,8 +100,6 @@ if ( $op eq 'cud-renew' && $barcode ) { $can_renew = 1; $error = undef; } else { - $balance = $patron->account->balance; - $template->param( balance => $balance ); $can_renew = 0; } } diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc index 316b28889b3..adb0545d483 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc @@ -64,6 +64,10 @@ [% END %] + [% IF Koha.Preference( 'AllowFineOverrideRenewing' ) %] + + + [% END %] [% END %] [% IF ( CAN_user_circulate_circulate_remaining_permissions ) %] [% IF Koha.Preference( 'UnseenRenewals' ) %] @@ -75,6 +79,12 @@ [% END %] + [% IF patron.account.non_issues_charges >= Koha.Preference('FineNoRenewals') %] +

Note: Patron has [% patron.account.non_issues_charges | $Price %] of non-issuing charges outstanding, renewals are restricted at + [% Koha.Preference('FineNoRenewals') | $Price %]

+ [% END %] [% IF Koha.Preference('ExportCircHistory') %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt index 041e3e8b436..02c1a4941fd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -1096,6 +1096,7 @@ var exports_enabled = [% Koha.Preference('ExportCircHistory') ? 1 : 0 | html %]; var AllowRenewalLimitOverride = [% (CAN_user_circulate_override_renewals && Koha.Preference('AllowRenewalLimitOverride') )? 1: 0 | html %]; var AllowRenewalOnHoldOverride = [% (CAN_user_circulate_override_renewals && Koha.Preference('AllowRenewalOnHoldOverride') )? 1: 0 | html %]; + var AllowFineOverrideRenewing = [% (CAN_user_circulate_override_renewals && Koha.Preference('AllowFineOverrideRenewing') )? 1: 0 | html %]; var AllowCirculate = [% (CAN_user_circulate_circulate_remaining_permissions)? 1 : 0 | html %]; var script = "circulation"; var relatives_borrowernumbers = new Array(); diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt index 53b2df6a5ca..66cf5f32a7c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/renew.tt @@ -129,11 +129,14 @@ [% ELSIF error == "auto_renew" or error == "auto_too_much_owing" %]

[% INCLUDE 'biblio-title.inc' biblio=item.biblio link = 1 %] ( [% item.barcode | html %] ) has been scheduled for automatic renewal.

[% IF error == "auto_too_much_owing" %] - [% IF balance %] - - [% END %] + [% SET patron_account = borrower.account %] + [% SET total_balance = patron_account.balance %] + [% SET non_issues_charges = patron_account.non_issues_charges %] + [% SET fine_no_renewals = Koha.Preference('FineNoRenewals') %] + [% IF Koha.Preference('AllowFineOverrideRenewing') %]
[% INCLUDE 'csrf-token.inc' %] @@ -156,8 +159,13 @@ [% END %] [% END %] [% ELSIF error == "too_much_owing" %] + [% SET patron_account = borrower.account %] + [% SET total_balance = patron_account.balance %] + [% SET non_issues_charges = patron_account.non_issues_charges %] + [% SET fine_no_renewals = Koha.Preference('FineNoRenewals') %]
    -
  • The patron has a debt of [% balance | $Price %] and renewals are restricted at [% Koha.Preference('FineNoRenewals') | $Price %].
  • +
  • The patron has a total debt of [% total_balance | $Price %], of which [% non_issues_charges | $Price %] are non-issuing charges.
  • +
  • Renewals are blocked when non-issuing charges exceed [% fine_no_renewals | $Price %].
[% IF Koha.Preference('AllowFineOverrideRenewing') %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js index 107541a2c99..1cab6faa393 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js +++ b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js @@ -393,7 +393,7 @@ function LoadIssuesTable() { ""; span_style = "display: none"; - span_class = "renewals-allowed"; + span_class = "renewals-allowed-too_many"; } else if (oObj.can_renew_error == "too_unseen") { msg += "" + @@ -447,7 +447,7 @@ function LoadIssuesTable() { ""; span_style = "display: none"; - span_class = "renewals-allowed"; + span_class = "renewals-allowed-auto_too_much_owing"; } else if ( oObj.can_renew_error == "auto_account_expired" ) { @@ -476,13 +476,12 @@ function LoadIssuesTable() { msg += "" + __( - "Cannot renew, the patron has a debt of " + - parseFloat(oObj.fine).format_price() + "Cannot renew - patron has debts blocking renewal" ) + ""; span_style = "display: none"; - span_class = "renewals-allowed"; + span_class = "renewals-allowed-too_much_owing"; } else { msg += "" + @@ -968,6 +967,7 @@ $(document).ready(function () { function renew(item_id) { var override_limit = $("#override_limit").is(":checked") ? 1 : 0; + var override_debt = $("#override_debt").is(":checked") ? 1 : 0; $(this) .parent() @@ -987,6 +987,7 @@ $(document).ready(function () { patron_id: borrowernumber, library_id: branchcode, override_limit: override_limit, + override_debt: override_debt, }; if (UnseenRenewals) { @@ -1379,16 +1380,28 @@ $(document).ready(function () { .click(function () { if (this.checked) { if (AllowRenewalLimitOverride) { - $(".renewals-allowed").show(); - $(".renewals-disabled").hide(); + $(".renewals-allowed-too_many").show(); } if (AllowRenewalOnHoldOverride) { $(".renewals-allowed-on_reserve").show(); } } else { - $(".renewals-allowed").hide(); + $(".renewals-allowed-too_many").hide(); $(".renewals-allowed-on_reserve").hide(); - $(".renewals-disabled").show(); + } + }) + .prop("checked", false); + } + + if (AllowFineOverrideRenewing) { + $("#override_debt") + .click(function () { + if (this.checked) { + $(".renewals-allowed-too_much_owing").show(); + $(".renewals-allowed-auto_too_much_owing").show(); + } else { + $(".renewals-allowed-too_much_owing").hide(); + $(".renewals-allowed-auto_too_much_owing").hide(); } }) .prop("checked", false); diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-api-client.js index 2b478eebb7f..3f8f3368a29 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-api-client.js +++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/circulation-api-client.js @@ -31,11 +31,12 @@ export class CirculationAPIClient { this.httpClient.post({ endpoint: "renew", body: - "itemnumber=%s&borrowernumber=%s&branchcode=%s&override_limit=%s".format( + "itemnumber=%s&borrowernumber=%s&branchcode=%s&override_limit=%s&override_debt=%s".format( checkout.item_id, checkout.patron_id, checkout.library_id, - checkout.override_limit + checkout.override_limit, + checkout.override_debt || 0 ) + (checkout.seen !== undefined ? "&seen=%s".format(checkout.seen) diff --git a/svc/renew b/svc/renew index 86c8b1548ae..faff4c822a9 100755 --- a/svc/renew +++ b/svc/renew @@ -46,10 +46,12 @@ print $input->header( -type => 'application/json', -charset => 'UTF-8' ); my $itemnumber = $input->param('itemnumber'); my $borrowernumber = $input->param('borrowernumber'); my $override_limit = $input->param('override_limit'); +my $override_debt = $input->param('override_debt'); my $branchcode = $input->param('branchcode') || C4::Context->userenv->{'branch'}; my $seen = $input->param('seen'); my $date_due; + if ( $input->param('date_due') ) { $date_due = dt_from_string( scalar $input->param('date_due') ); } @@ -79,6 +81,13 @@ if ( $data->{error} && $data->{error} eq 'too_many' && C4::Context->preference(' push @{$confirmations}, 'RENEWAL_LIMIT' if $override_limit; } +if ( $data->{error} && ( $data->{error} eq 'too_much_owing' or $data->{error} eq 'auto_too_much_owing' ) ) { + if ( $override_debt && C4::Context->preference('AllowFineOverrideRenewing') ) { + $data->{renew_okay} = 1; + $data->{error} = undef; + } +} + if ( $data->{renew_okay} || ( $seen && $data->{error} eq 'too_unseen' ) ) { try { $date_due = AddRenewal( diff --git a/t/cypress/integration/Circulation/FineNoRenewals_spec.ts b/t/cypress/integration/Circulation/FineNoRenewals_spec.ts index 7d28d2b7c15..9d30932e6f3 100644 --- a/t/cypress/integration/Circulation/FineNoRenewals_spec.ts +++ b/t/cypress/integration/Circulation/FineNoRenewals_spec.ts @@ -59,9 +59,11 @@ describe("Circulation - FineNoRenewals and AllowFineOverrideRenewing", () => { // Should see error message about patron debt cy.get(".dialog.alert").should("be.visible"); - cy.get(".dialog.alert li").should( + cy.get(".dialog.alert").should("contain", "total debt of"); + cy.get(".dialog.alert").should("contain", "non-issuing charges"); + cy.get(".dialog.alert").should( "contain", - `The patron has a debt of` + "Renewals are blocked when non-issuing charges exceed" ); }); @@ -80,11 +82,8 @@ describe("Circulation - FineNoRenewals and AllowFineOverrideRenewing", () => { // Should see error message about patron debt cy.get(".dialog.alert").should("be.visible"); - - cy.get(".dialog.alert li").should( - "contain", - `The patron has a debt of` - ); + cy.get(".dialog.alert").should("contain", "total debt of"); + cy.get(".dialog.alert").should("contain", "non-issuing charges"); // Should see override button cy.get('.dialog.alert form button[type="submit"].approve').should( @@ -111,9 +110,11 @@ describe("Circulation - FineNoRenewals and AllowFineOverrideRenewing", () => { // Should see error message about patron debt cy.get(".dialog.alert").should("be.visible"); - cy.get(".dialog.alert li").should( + cy.get(".dialog.alert").should("contain", "total debt of"); + cy.get(".dialog.alert").should("contain", "non-issuing charges"); + cy.get(".dialog.alert").should( "contain", - `The patron has a debt of` + "Renewals are blocked when non-issuing charges exceed" ); // Should NOT see override button -- 2.51.1