From 58023a4a0a83eb4babfdcdb11b7ef33c304725ab Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 30 Oct 2025 14:56:34 +0000 Subject: [PATCH 13/16] 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. Sponsored-by: OpenFifth Signed-off-by: Andrew Fuerste Henry Signed-off-by: Martin Renvoize --- 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 44ab1435fc..67df976745 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 1a4ebc5f59..ecc35c75b0 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 cb350c6e28..25b071f20b 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 53b2df6a5c..66cf5f32a7 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 %] -
    -
  • The patron has a debt of [% balance | $Price %] and renewals are restricted at [% Koha.Preference('FineNoRenewals') | $Price %].
  • -
- [% 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') %] +
    +
  • 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') %]
[% 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 d3f3c38e51..0bb1f74139 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js +++ b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js @@ -424,7 +424,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 += "" + @@ -478,7 +478,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" ) { @@ -507,13 +507,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 += "" + @@ -1010,6 +1009,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() @@ -1029,6 +1029,7 @@ $(document).ready(function () { patron_id: borrowernumber, library_id: branchcode, override_limit: override_limit, + override_debt: override_debt, }; if (UnseenRenewals) { @@ -1420,16 +1421,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 2b478eebb7..3f8f3368a2 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 86c8b1548a..faff4c822a 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 7d28d2b7c1..9d30932e6f 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.34.1