Bug 3492: Add hold fees to circulation rules system

This commit integrates hold fee configuration into the main circulation
rules system, providing granular control over hold fees based on patron
category, item type, and branch combinations.

Changes:
- Add hold_fee as a new circulation rule type with full scope support
- Remove deprecated reservefee column mapping from Koha::Patron::Category
- Integrate hold fee input into the circulation rules admin interface
- Add hold fee column to the main circulation rules matrix
- Enable proper rule hierarchy resolution for hold fees

Hold fees can now be configured at multiple levels with automatic
fallback handling following standard circulation rule patterns.

Sponsored-by: OpenFifth <https://openfifth.co.uk>
Signed-off-by: Jackie Usher <jackie.usher@westsussex.gov.uk>
Signed-off-by: Tomás Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2025-09-08 18:47:01 +01:00 committed by Lucas Gass
parent f5bc5be08d
commit 35c7f0a7c4
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
4 changed files with 19 additions and 2 deletions

View File

@ -86,6 +86,10 @@ our $RULE_KINDS = {
scope => [ 'branchcode', 'categorycode' ],
is_monetary => 1,
},
hold_fee => {
scope => [ 'branchcode', 'categorycode', 'itemtype' ],
is_monetary => 1,
},
open_article_requests_limit => {
scope => [ 'branchcode', 'categorycode' ],
},

View File

@ -311,7 +311,6 @@ sub to_api_mapping {
dateofbirthrequired => 'lower_age_limit',
enrolmentfee => 'enrolment_fee',
overduenoticerequired => 'overdue_notice_required',
reservefee => 'reserve_fee',
hidelostitems => 'hide_lost_items',
category_type => 'category_type',
BlockExpiredPatronOpacActions => 'block_expired_patron_opac_actions',

View File

@ -121,6 +121,7 @@ if ( $op eq 'cud-delete' ) {
recall_shelf_time => undef,
decreaseloanholds => undef,
holds_pickup_period => undef,
hold_fee => undef,
}
}
);
@ -275,6 +276,7 @@ elsif ( $op eq 'cud-add' ) {
my $recall_overdue_fine = $input->param('recall_overdue_fine');
my $recall_shelf_time = $input->param('recall_shelf_time');
my $holds_pickup_period = strip_non_numeric( scalar $input->param('holds_pickup_period') );
my $hold_fee = $input->param('hold_fee') || q{};
my $rules = {
maxissueqty => $maxissueqty,
@ -318,6 +320,7 @@ elsif ( $op eq 'cud-add' ) {
recall_overdue_fine => $recall_overdue_fine,
recall_shelf_time => $recall_shelf_time,
holds_pickup_period => $holds_pickup_period,
hold_fee => $hold_fee,
};
Koha::CirculationRules->set_rules(

View File

@ -156,6 +156,7 @@
<th>On shelf holds allowed</th>
<th>OPAC item level holds</th>
<th>Holds pickup period (day)</th>
<th>Hold fee</th>
[% IF Koha.Preference('ArticleRequests') %]
<th>Article requests</th>
[% END %]
@ -218,8 +219,9 @@
[% SET recall_overdue_fine = all_rules.$c.$i.recall_overdue_fine %]
[% SET recall_shelf_time = all_rules.$c.$i.recall_shelf_time %]
[% SET holds_pickup_period = all_rules.$c.$i.holds_pickup_period %]
[% SET hold_fee = all_rules.$c.$i.hold_fee %]
[% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || expire_reserves_charge || finedays || maxsuspensiondays || suspension_chargeperiod || renewalsallowed || unseenrenewalsallowed || renewalperiod || norenewalbefore || noautorenewalbefore || auto_renew || no_auto_renewal_after || no_auto_renewal_after_hard_limit || reservesallowed || holds_per_day || holds_per_record || onshelfholds || opacitemholds || article_requests || rentaldiscount || decreaseloanholds || recalls_allowed || recalls_per_record || on_shelf_recalls || recall_due_date_interval || recall_overdue_fine || recall_shelf_time || holds_pickup_period %]
[% SET show_rule = note || maxissueqty || maxonsiteissueqty || issuelength || daysmode || lengthunit || hardduedate || hardduedatecompare || fine || chargeperiod || chargeperiod_charge_at || firstremind || overduefinescap || cap_fine_to_replacement_price || expire_reserves_charge || finedays || maxsuspensiondays || suspension_chargeperiod || renewalsallowed || unseenrenewalsallowed || renewalperiod || norenewalbefore || noautorenewalbefore || auto_renew || no_auto_renewal_after || no_auto_renewal_after_hard_limit || reservesallowed || holds_per_day || holds_per_record || onshelfholds || opacitemholds || article_requests || rentaldiscount || decreaseloanholds || recalls_allowed || recalls_per_record || on_shelf_recalls || recall_due_date_interval || recall_overdue_fine || recall_shelf_time || holds_pickup_period || hold_fee %]
[% IF show_rule %]
[% SET row_count = row_count + 1 %]
<tr row_countd="row_[% row_count | html %]">
@ -412,6 +414,13 @@
[% holds_pickup_period | html %]
[% END %]
</td>
<td>
[% IF hold_fee.defined && hold_fee != '' %]
[% hold_fee | $Price %]
[% ELSE %]
<span>0.00</span>
[% END %]
</td>
[% IF Koha.Preference('ArticleRequests') %]
<td data-code="[% article_requests | html %]">
[% IF article_requests == 'no' %]
@ -565,6 +574,7 @@
</select>
</td>
<td><input type="text" name="holds_pickup_period" id="holds_pickup_period" size="2" /></td>
<td><input type="text" name="hold_fee" id="hold_fee" size="5" inputmode="decimal" pattern="^\d+(\.\d{2})?$" /></td>
[% IF Koha.Preference('ArticleRequests') %]
<td>
<select id="article_requests" name="article_requests">
@ -637,6 +647,7 @@
<th>On shelf holds allowed</th>
<th>OPAC item level holds</th>
<th>Holds pickup period (day)</th>
<th>Hold fee</th>
[% IF Koha.Preference('ArticleRequests') %]
<th>Article requests</th>
[% END %]