From fa2b01021a3389b9cbe2b6d165a3a77995803450 Mon Sep 17 00:00:00 2001
From: Thibaud Guillot
Date: Fri, 23 Feb 2024 10:14:40 +0100
Subject: [PATCH] Bug 8137: Add 'global checkout limits' table
The aim here is to avoid the accumulation of checkouts possibilities through the rules of the various branches, in order to have a global quota.
Test plan:
1) Apply this patch
2) Run updatedatabase script and rebuild schema, restart_all..
3) On circulation rules page you will find a new table "Global checkout
limits".
4) If you enter values, they will overwrite the existing values,
creating limits for all branches combined.
5) Test it by set a "current checkouts allowed" to 2 on "standard rules section"
and set a "global" maxissueqty to 1.
Normally 1 is the new limit for now.
6) You can also add a "current checkouts allowed" on other branches and
see that you can't accumulate checkouts (you can also delete "global"
values and see that if you have 3 on a branch and 2 on other, you can
perform 5 checkouts in total.
7) Note that on specific branch circulation rules page the table cannot
be modified, but just shown as a reminder. If main matrix is empty on
specific branch the table is hidden by default (cause if none values
it's referred to standard rules).
Sponsored-by: BibLibre
Signed-off-by: Lisette Scheer
---
Koha/CirculationRules.pm | 29 ++-
Koha/Schema/Result/CirculationRule.pm | 11 +-
Koha/Template/Plugin/CirculationRules.pm | 2 +
admin/smart-rules.pl | 40 +++-
.../Bug_8137-add-column-has_priority.pl | 15 ++
installer/data/mysql/kohastructure.sql | 1 +
.../prog/en/modules/admin/smart-rules.tt | 193 +++++++++++++++++-
7 files changed, 276 insertions(+), 15 deletions(-)
create mode 100755 installer/data/mysql/atomicupdate/Bug_8137-add-column-has_priority.pl
diff --git a/Koha/CirculationRules.pm b/Koha/CirculationRules.pm
index b0b2bd00c2b..d3c5a40caed 100644
--- a/Koha/CirculationRules.pm
+++ b/Koha/CirculationRules.pm
@@ -227,7 +227,9 @@ our $RULE_KINDS = {
bookings_trail_period => {
scope => [ 'branchcode', 'itemtype' ],
},
-
+ has_priority => {
+ scope => [ 'branchcode', 'categorycode' ],
+ },
# Not included (deprecated?):
# * accountsent
# * reservecharge
@@ -260,6 +262,7 @@ sub get_effective_rule {
$params->{categorycode} //= undef;
$params->{branchcode} //= undef;
$params->{itemtype} //= undef;
+ $params->{has_priority} //= undef;
my $rule_name = $params->{rule_name};
my $categorycode = $params->{categorycode};
@@ -290,7 +293,17 @@ sub get_effective_rule {
}
)->single;
- return $rule;
+ $search_params->{has_priority} = 1;
+
+ my $priority_rule = $self->search(
+ $search_params,
+ {
+ order_by => $order_by,
+ rows => 1,
+ }
+ )->single;
+
+ return defined $priority_rule ? $priority_rule : $rule;
}
=head3 get_effective_rule_value
@@ -318,10 +331,11 @@ sub get_effective_rule_value {
my $categorycode = $params->{categorycode};
my $itemtype = $params->{itemtype};
my $branchcode = $params->{branchcode};
+ my $has_priority = $params->{has_priority};
my $memory_cache = Koha::Cache::Memory::Lite->get_instance;
- my $cache_key = sprintf "CircRules:%s:%s:%s:%s", $rule_name // q{},
- $categorycode // q{}, $branchcode // q{}, $itemtype // q{};
+ my $cache_key = sprintf "CircRules:%s:%s:%s:%s:%s", $rule_name // q{},
+ $categorycode // q{}, $branchcode // q{}, $itemtype // q{} ,$has_priority // q{};
my $cached = $memory_cache->get_from_cache($cache_key);
return $cached if $cached;
@@ -344,6 +358,7 @@ sub get_effective_rules {
my $categorycode = $params->{categorycode};
my $itemtype = $params->{itemtype};
my $branchcode = $params->{branchcode};
+ my $has_priority = $params->{has_priority};
my $r;
foreach my $rule (@$rules) {
@@ -353,6 +368,7 @@ sub get_effective_rules {
categorycode => $categorycode,
itemtype => $itemtype,
branchcode => $branchcode,
+ has_priority => $has_priority,
}
);
@@ -394,6 +410,8 @@ sub set_rule {
my $itemtype = $params->{itemtype};
my $rule_name = $params->{rule_name};
my $rule_value = $params->{rule_value};
+ my $has_priority = $params->{has_priority};
+
my $can_be_blank = defined $kind_info->{can_be_blank} ? $kind_info->{can_be_blank} : 1;
$rule_value = undef if defined $rule_value && $rule_value eq "" && !$can_be_blank;
my $is_monetary = defined $kind_info->{is_monetary} ? $kind_info->{is_monetary} : 0;
@@ -409,6 +427,7 @@ sub set_rule {
branchcode => $branchcode,
categorycode => $categorycode,
itemtype => $itemtype,
+ has_priority => $has_priority
}
)->next();
@@ -429,6 +448,7 @@ sub set_rule {
itemtype => $itemtype,
rule_name => $rule_name,
rule_value => $rule_value,
+ has_priority => $has_priority,
}
);
$rule->store();
@@ -458,6 +478,7 @@ sub set_rules {
$set_params{branchcode} = $params->{branchcode} if exists $params->{branchcode};
$set_params{categorycode} = $params->{categorycode} if exists $params->{categorycode};
$set_params{itemtype} = $params->{itemtype} if exists $params->{itemtype};
+ $set_params{has_priority} = $params->{has_priority} if exists $params->{has_priority};
my $rules = $params->{rules};
my $rule_objects = [];
diff --git a/Koha/Schema/Result/CirculationRule.pm b/Koha/Schema/Result/CirculationRule.pm
index 50dc93c5ad5..9cce1ba39ef 100644
--- a/Koha/Schema/Result/CirculationRule.pm
+++ b/Koha/Schema/Result/CirculationRule.pm
@@ -62,6 +62,11 @@ __PACKAGE__->table("circulation_rules");
is_nullable: 0
size: 32
+=head2 has_priority
+
+ data_type: 'tinyint'
+ is_nullable: 1
+
=cut
__PACKAGE__->add_columns(
@@ -77,6 +82,8 @@ __PACKAGE__->add_columns(
{ data_type => "varchar", is_nullable => 0, size => 32 },
"rule_value",
{ data_type => "varchar", is_nullable => 0, size => 32 },
+ "has_priority",
+ { data_type => "tinyint", is_nullable => 1 },
);
=head1 PRIMARY KEY
@@ -177,8 +184,8 @@ __PACKAGE__->belongs_to(
);
-# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-10-05 14:29:17
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:QHMqvrtX0ohJe70PHUYZ0Q
+# Created by DBIx::Class::Schema::Loader v0.07051 @ 2024-12-20 12:51:01
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:P5S9o9p6aJ38EOkLQQOW0Q
=head2 koha_objects_class
diff --git a/Koha/Template/Plugin/CirculationRules.pm b/Koha/Template/Plugin/CirculationRules.pm
index d19c27b543b..1e1de6ba22a 100644
--- a/Koha/Template/Plugin/CirculationRules.pm
+++ b/Koha/Template/Plugin/CirculationRules.pm
@@ -76,6 +76,7 @@ sub Search {
$branchcode = undef if $branchcode eq q{} or $branchcode eq q{*};
$categorycode = undef if $categorycode eq q{} or $categorycode eq q{*};
$itemtype = undef if $itemtype eq q{} or $itemtype eq q{*};
+ my $has_priority = $params->{has_priority} // undef;
my $rule = Koha::CirculationRules->search(
{
@@ -83,6 +84,7 @@ sub Search {
categorycode => $categorycode,
itemtype => $itemtype,
rule_name => $rule_name,
+ has_priority => $has_priority
}
)->next;
diff --git a/admin/smart-rules.pl b/admin/smart-rules.pl
index 7f5448f511c..37eeae44f66 100755
--- a/admin/smart-rules.pl
+++ b/admin/smart-rules.pl
@@ -755,6 +755,44 @@ elsif ( $op eq 'cud-add' ) {
rules => { waiting_hold_cancellation => undef },
}
);
+} elsif ( $op eq 'cud-set-global-checkout-limits' ) {
+
+ my $categorycode = $input->param('categorycode');
+ my $itemtype = $input->param('itemtype');
+ my $maxissueqty = scalar $input->param('maxissueqty');
+ my $maxonsiteissueqty = scalar $input->param('maxonsiteissueqty');
+
+ my %checkout_rules;
+ $checkout_rules{'maxissueqty'} = strip_non_numeric($maxissueqty);
+ $checkout_rules{'maxonsiteissueqty'} = strip_non_numeric($maxonsiteissueqty);
+
+ while (my ($rule_name, $rule_value) = each(%checkout_rules)) {
+ if(defined $rule_value && $rule_value ne '') {
+ Koha::CirculationRules->set_rule(
+ { categorycode => ( $categorycode eq '*' ) ? undef : $categorycode,
+ itemtype => ( $itemtype eq '*' ) ? undef : $itemtype,
+ branchcode => undef,
+ has_priority => 1,
+ rule_name => $rule_name,
+ rule_value => $rule_value
+ });
+ }
+ }
+} elsif ( $op eq 'cud-delete-global-checkout-limits' ) {
+ my $categorycode = $input->param('categorycode');
+ my $itemtype = $input->param('itemtype');
+
+ Koha::CirculationRules->set_rules(
+ { categorycode => ( $categorycode eq '*' ) ? undef : $categorycode,
+ itemtype => ( $itemtype eq '*' ) ? undef : $itemtype,
+ branchcode => undef,
+ has_priority => 1,
+ rules => {
+ maxissueqty => undef,
+ maxonsiteissueqty => undef,
+ },
+ }
+ );
}
my $refundLostItemFeeRule =
@@ -783,7 +821,7 @@ my @used_itemtypes =
Koha::CirculationRules->search( { branchcode => $humanbranch }, { columns => ['itemtype'], distinct => 1, } )
->get_column('itemtype');
-my $all_rules = Koha::CirculationRules->search( { branchcode => $humanbranch } );
+my $all_rules = Koha::CirculationRules->search({ branchcode => $humanbranch, has_priority => undef });
my $definedbranch = $all_rules->count ? 1 : 0;
my $rules = {};
diff --git a/installer/data/mysql/atomicupdate/Bug_8137-add-column-has_priority.pl b/installer/data/mysql/atomicupdate/Bug_8137-add-column-has_priority.pl
new file mode 100755
index 00000000000..20742a43de1
--- /dev/null
+++ b/installer/data/mysql/atomicupdate/Bug_8137-add-column-has_priority.pl
@@ -0,0 +1,15 @@
+use Modern::Perl;
+
+return {
+ bug_number => "8137",
+ description => "Add column has_priority",
+ up => sub {
+ my ($args) = @_;
+ my ( $dbh, $out ) = @$args{qw(dbh out)};
+
+ if ( !column_exists( 'circulation_rules', 'has_priority' ) ) {
+ $dbh->do('ALTER TABLE circulation_rules ADD IF NOT EXISTS has_priority TINYINT(1) DEFAULT NULL');
+ say $out "Added column 'circulation_rules.has_priority'";
+ }
+ },
+};
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index ce39d6a933f..4367412a686 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -1881,6 +1881,7 @@ CREATE TABLE `circulation_rules` (
`itemtype` varchar(10) DEFAULT NULL,
`rule_name` varchar(32) NOT NULL,
`rule_value` varchar(32) NOT NULL,
+ `has_priority` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `branchcode` (`branchcode`,`categorycode`,`itemtype`,`rule_name`),
KEY `circ_rules_ibfk_2` (`categorycode`),
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
index a1b9d79a41e..be7150f36f4 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
@@ -83,6 +83,7 @@
[% Koha.Preference('CircControl') | html %] and the HomeOrHoldingBranch system preference which is set to
[% Koha.Preference('HomeOrHoldingBranch') | html %].
+ The “Global checkout limits” section is designed to create a limit for all librairies, thus preventing checkouts accumulation.
@@ -890,6 +891,108 @@
[% END %]
+
+
Global checkout limits
+
Specify checkout limits for all librairies.
+
+
+
[% IF humanbranch %]
Waiting hold cancellation policy for [% Branches.GetName( humanbranch ) | html %]
@@ -1459,7 +1562,7 @@
});
});
- function clear_edit() {
+ function clear_edit_form(editRowSelector, tableSelector) {
var cancel = confirm(_("Are you sure you want to cancel your changes?"));
if (!cancel) return;
$("#default-circulation-rules tr").removeClass("highlighted-row");
@@ -1472,18 +1575,66 @@
$(this).val("");
$(this).prop("disabled", false);
}
- if (type == "checkbox") {
+ if (type === "checkbox") {
$(this).prop("checked", false);
}
});
- $(edit_row).find("select").prop("disabled", false);
- $(edit_row).find("select option:selected").removeAttr("selected");
- $(edit_row).find("select option:first-child").attr("selected", "selected");
- $(edit_row).find("td:last input[name='clear']").remove();
+ edit_row.find("select").prop('disabled', false);
+ edit_row.find("select option:selected").prop('selected', false);
+ edit_row.find("select option:first-child").prop("selected", true);
+ edit_row.find("td:last input[name='clear']").remove();
}
var MSG_CONFIRM_DELETE = _("Are you sure you want to delete this rule? This cannot be undone.");
-
+ $(".editrule-global-checkout-limits").click(function(){
+ if ( $("#global_checkout_limits_edit_row").find("input[type='text']").filter(function(){return this.value.length > 0 }).length > 0 ) {
+ var edit = confirm(_("Are you sure you want to edit another rule?"));
+ if (!edit) return false;
+ }
+ $('#global-checkout-limits-rules td').removeClass('highlighted-row');
+ $(this).parent().parent().find("td").each(function (i) {
+ $(this).addClass('highlighted-row');
+ itm_code = $(this).data('code');
+ itm_text = $(this).text();
+ itm_text = itm_text.replace(/^\s*|\s*$/g,'');
+ var current_column = $("#global_checkout_limits_edit_row td:eq("+i+")");
+ var current_input_id = $(current_column).children('input').first().attr('id');
+ $(current_column).find("input[type='text']").val(itm_text);
+ // select the corresponding option
+ $(current_column).find("select option").each(function(){
+ // Reset selection status for all options
+ $(this).prop('selected', false);
+ // Declare opt here to ensure it is scoped correctly within the loop
+ let opt = $(this).val();
+ // Select the matching option
+ if (opt == itm_code) {
+ $(this).prop('selected', true);
+ }
+ });
+ // After setting the correct option, update the select to reflect the change
+ $(current_column).find('select').trigger('change');
+ if ( i == 0 || i == 1 ) {
+ // Disable the 2 first columns, we cannot update them.
+ var val = $(current_column).find("select option:selected").val();
+ var name = "categorycode";
+ if ( i == 1 ) {
+ name="itemtype";
+ }
+ // Remove potential previous input added
+ $(current_column).find("input").remove();
+ $(current_column).append("");
+ } else if ( current_input_id === "global_checkout_limits_maxissueqty" || current_input_id === "global_checkout_limits_maxonsiteissueqty" ) {
+ // If the value is not an integer for
+ // - "Global current checkouts allowed"
+ // - "Global current on-site checkouts allowed"
+ // The value will be "Not defined"
+ if( !((parseFloat(itm_text) == parseInt(itm_text)) && !isNaN(itm_text)) ) {
+ $(current_column).find("input[type='text']").val("");
+ }
+ }
+ });
+ return false;
+ });
$(document).ready(function () {
$('[data-bs-toggle="popover"]').popover();
@@ -1632,8 +1783,12 @@
});
$(".clear_edit").on("click", function (e) {
e.preventDefault();
- clear_edit();
+ clear_edit_form("#edit_row", "#default-circulation-rules");
});
+ $(".clear_edit_global_checkouts_limit").on("click",function(e){
+ e.preventDefault();
+ clear_edit_form("#global_checkout_limits_edit_row", "#global-checkout-limits-rules");
+ });
$("#set-article-requests-daily-limit").on("submit", function () {
if (!$("input[name='open_article_requests_limit'").val().length) {
@@ -1724,6 +1879,28 @@
f.find("[name='branch']").val($(this).data("branch"));
return f.submit();
});
+
+ $(".delete-global-checkout-limits").on("click", function(e){
+ e.preventDefault();
+ if ( !confirmDelete(MSG_CONFIRM_DELETE) ) {
+ return false;
+ }
+ let f = $("#delete_form");
+ f.find("[name='op']").val('cud-delete-global-checkout-limits');
+ f.find("[name='itemtype']").val($(this).data('itemtype'));
+ f.find("[name='categorycode']").val($(this).data('categorycode'));
+ f.find("[name='branch']").val($(this).data('branch'));
+ return f.submit();
+ });
+
+ if($("#branch").val() != '*') {
+ var rowCount = $("#default-circulation-rules tbody tr").length;
+ if(rowCount == 1){
+ $("#global_checkout_limits").remove();
+ } else {
+ $("#global_checkout_limits .restricted-access").remove();
+ }
+ }
});
[% END %]
--
2.39.5