Browse Source

Bug 18936: Convert issuingrules fields to circulation_rules

Signed-off-by: Minna Kivinen <minna.kivinen@hamk.fi>
Signed-off-by: Joonas Kylmälä <joonas.kylmala@helsinki.fi>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
20.05.x
Kyle Hall 6 years ago
committed by Martin Renvoize
parent
commit
58fda20e85
Signed by: martin.renvoize GPG Key ID: 422B469130441A0F
  1. 358
      C4/Circulation.pm
  2. 35
      C4/Overdues.pm
  3. 45
      C4/Reserves.pm
  4. 14
      Koha/Biblio.pm
  5. 43
      Koha/CirculationRules.pm
  6. 68
      Koha/IssuingRule.pm
  7. 15
      Koha/Item.pm
  8. 327
      Koha/Schema/Result/Issuingrule.pm
  9. 115
      admin/smart-rules.pl
  10. 45
      installer/data/mysql/atomicupdate/bug_18936.perl
  11. 330
      koha-tmpl/intranet-tmpl/prog/en/modules/admin/smart-rules.tt
  12. 49
      t/db_dependent/ArticleRequests.t
  13. 346
      t/db_dependent/Circulation.t
  14. 23
      t/db_dependent/Circulation/CalcDateDue.t
  15. 34
      t/db_dependent/Circulation/CalcFine.t
  16. 314
      t/db_dependent/Circulation/GetHardDueDate.t
  17. 31
      t/db_dependent/Circulation/IssuingRules/maxsuspensiondays.t
  18. 21
      t/db_dependent/Circulation/Returns.t
  19. 21
      t/db_dependent/Circulation/SwitchOnSiteCheckouts.t
  20. 14
      t/db_dependent/Circulation/TooMany.t
  21. 16
      t/db_dependent/Circulation/issue.t
  22. 16
      t/db_dependent/DecreaseLoanHighHolds.t
  23. 35
      t/db_dependent/Fines.t
  24. 87
      t/db_dependent/Holds.t
  25. 22
      t/db_dependent/Holds/DisallowHoldIfItemsAvailable.t
  26. 87
      t/db_dependent/Koha/IssuingRules.t
  27. 9
      t/db_dependent/Koha/Objects.t
  28. 44
      t/db_dependent/Reserves.t
  29. 105
      t/db_dependent/Reserves/MultiplePerRecord.t
  30. 4
      t/db_dependent/TestBuilder.t
  31. 16
      t/db_dependent/api/v1/holds.t

358
C4/Circulation.pm

@ -44,7 +44,6 @@ use Koha::Biblioitems;
use Koha::DateUtils;
use Koha::Calendar;
use Koha::Checkouts;
use Koha::IssuingRules;
use Koha::Items;
use Koha::Patrons;
use Koha::Patron::Debarments;
@ -423,18 +422,20 @@ sub TooMany {
# specific rule
if (C4::Context->preference('item-level_itypes')) {
$count_query .= " WHERE items.itype NOT IN (
SELECT itemtype FROM issuingrules
SELECT itemtype FROM circulation_rules
WHERE branchcode = ?
AND (categorycode = ? OR categorycode = ?)
AND itemtype <> '*'
AND rule_name = 'maxissueqty'
) ";
} else {
$count_query .= " JOIN biblioitems USING (biblionumber)
} else {
$count_query .= " JOIN biblioitems USING (biblionumber)
WHERE biblioitems.itemtype NOT IN (
SELECT itemtype FROM issuingrules
SELECT itemtype FROM circulation_rules
WHERE branchcode = ?
AND (categorycode = ? OR categorycode = ?)
AND itemtype <> '*'
AND rule_name = 'maxissueqty'
) ";
}
push @bind_params, $maxissueqty_rule->branchcode;
@ -1391,14 +1392,16 @@ sub AddIssue {
# If automatic renewal wasn't selected while issuing, set the value according to the issuing rule.
unless ($auto_renew) {
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
{ categorycode => $borrower->{categorycode},
my $rule = Koha::CirculationRules->get_effective_rule(
{
categorycode => $borrower->{categorycode},
itemtype => $item_object->effective_itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rule_name => 'auto_renew'
}
);
$auto_renew = $issuing_rule->auto_renew if $issuing_rule;
$auto_renew = $rule->rule_value if $rule;
}
# Record in the database the fact that the book was issued.
@ -1539,67 +1542,77 @@ Get loan length for an itemtype, a borrower type and a branch
=cut
sub GetLoanLength {
my ( $borrowertype, $itemtype, $branchcode ) = @_;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare(qq{
SELECT issuelength, lengthunit, renewalperiod
FROM issuingrules
WHERE categorycode=?
AND itemtype=?
AND branchcode=?
AND issuelength IS NOT NULL
});
my ( $categorycode, $itemtype, $branchcode ) = @_;
# Set search precedences
my @params = (
{
categorycode => $categorycode,
itemtype => $itemtype,
branchcode => $branchcode,
},
{
categorycode => $categorycode,
itemtype => '*',
branchcode => $branchcode,
},
{
categorycode => '*',
itemtype => $itemtype,
branchcode => $branchcode,
},
{
categorycode => '*',
itemtype => '*',
branchcode => $branchcode,
},
{
categorycode => $categorycode,
itemtype => $itemtype,
branchcode => '*',
},
{
categorycode => $categorycode,
itemtype => '*',
branchcode => '*',
},
{
categorycode => '*',
itemtype => $itemtype,
branchcode => '*',
},
{
categorycode => '*',
itemtype => '*',
branchcode => '*',
},
);
# try to find issuelength & return the 1st available.
# check with borrowertype, itemtype and branchcode, then without one of those parameters
$sth->execute( $borrowertype, $itemtype, $branchcode );
my $loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( $borrowertype, '*', $branchcode );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( '*', $itemtype, $branchcode );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( '*', '*', $branchcode );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( $borrowertype, $itemtype, '*' );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( $borrowertype, '*', '*' );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( '*', $itemtype, '*' );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
$sth->execute( '*', '*', '*' );
$loanlength = $sth->fetchrow_hashref;
return $loanlength
if defined($loanlength) && defined $loanlength->{issuelength};
# if no rule is set => 0 day (hardcoded)
return {
issuelength => 0,
# Initialize default values
my $rules = {
issuelength => 0,
renewalperiod => 0,
lengthunit => 'days',
lengthunit => 'days',
};
# Search for rules!
foreach my $rule_name (qw( issuelength renewalperiod lengthunit )) {
foreach my $params (@params) {
my $rule = Koha::CirculationRules->search(
{
rule_name => $rule_name,
%$params,
}
)->next();
if ($rule) {
$rules->{$rule_name} = $rule->rule_value;
last;
}
}
}
return $rules;
}
@ -1614,19 +1627,21 @@ Get the Hard Due Date and it's comparison for an itemtype, a borrower type and a
sub GetHardDueDate {
my ( $borrowertype, $itemtype, $branchcode ) = @_;
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
{ categorycode => $borrowertype,
my $rules = Koha::CirculationRules->get_effective_rules(
{
categorycode => $borrowertype,
itemtype => $itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rules => [ 'hardduedate', 'hardduedatecompare' ],
}
);
if ( defined( $issuing_rule ) ) {
if ( $issuing_rule->hardduedate ) {
return (dt_from_string($issuing_rule->hardduedate, 'iso'),$issuing_rule->hardduedatecompare);
} else {
return (undef, undef);
if ( defined( $rules->{hardduedate} ) ) {
if ( $rules->{hardduedate} ) {
return ( dt_from_string( $rules->{hardduedate}, 'iso' ), $rules->{hardduedatecompare} );
}
else {
return ( undef, undef );
}
}
}
@ -2245,14 +2260,20 @@ sub _calculate_new_debar_dt {
my $branchcode = _GetCircControlBranch( $item, $borrower );
my $circcontrol = C4::Context->preference('CircControl');
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
my $issuing_rule = Koha::CirculationRules->get_effective_rules(
{ categorycode => $borrower->{categorycode},
itemtype => $item->{itype},
branchcode => $branchcode
branchcode => $branchcode,
rules => [
'finedays',
'lengthunit',
'firstremind',
'maxsuspensiondays',
]
}
);
my $finedays = $issuing_rule ? $issuing_rule->finedays : undef;
my $unit = $issuing_rule ? $issuing_rule->lengthunit : undef;
my $finedays = $issuing_rule ? $issuing_rule->{finedays} : undef;
my $unit = $issuing_rule ? $issuing_rule->{lengthunit} : undef;
my $chargeable_units = C4::Overdues::get_chargeable_units($unit, $dt_due, $return_date, $branchcode);
return unless $finedays;
@ -2263,7 +2284,7 @@ sub _calculate_new_debar_dt {
# grace period is measured in the same units as the loan
my $grace =
DateTime::Duration->new( $unit => $issuing_rule->firstremind );
DateTime::Duration->new( $unit => $issuing_rule->{firstremind} );
my $deltadays = DateTime::Duration->new(
days => $chargeable_units
@ -2272,16 +2293,16 @@ sub _calculate_new_debar_dt {
if ( $deltadays->subtract($grace)->is_positive() ) {
my $suspension_days = $deltadays * $finedays;
if ( $issuing_rule->suspension_chargeperiod > 1 ) {
if ( $issuing_rule->{suspension_chargeperiod} > 1 ) {
# No need to / 1 and do not consider / 0
$suspension_days = DateTime::Duration->new(
days => floor( $suspension_days->in_units('days') / $issuing_rule->suspension_chargeperiod )
days => floor( $suspension_days->in_units('days') / $issuing_rule->{suspension_chargeperiod} )
);
}
# If the max suspension days is < than the suspension days
# the suspension days is limited to this maximum period.
my $max_sd = $issuing_rule->maxsuspensiondays;
my $max_sd = $issuing_rule->{maxsuspensiondays};
if ( defined $max_sd ) {
$max_sd = DateTime::Duration->new( days => $max_sd );
$suspension_days = $max_sd
@ -2749,15 +2770,23 @@ sub CanBookBeRenewed {
return ( 1, undef ) if $override_limit;
my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed );
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
{ categorycode => $patron->categorycode,
my $issuing_rule = Koha::CirculationRules->get_effective_rules(
{
categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rules => [
'renewalsallowed',
'no_auto_renewal_after',
'no_auto_renewal_after_hard_limit',
'lengthunit',
'norenewalbefore',
]
}
);
return ( 0, "too_many" )
if not $issuing_rule or $issuing_rule->renewalsallowed <= $issue->renewals;
if not $issuing_rule->{renewalsallowed} or $issuing_rule->{renewalsallowed} <= $issue->renewals;
my $overduesblockrenewing = C4::Context->preference('OverduesBlockRenewing');
my $restrictionblockrenewing = C4::Context->preference('RestrictionBlockRenewing');
@ -2777,23 +2806,23 @@ sub CanBookBeRenewed {
return ( 0, 'auto_account_expired' );
}
if ( defined $issuing_rule->no_auto_renewal_after
and $issuing_rule->no_auto_renewal_after ne "" ) {
if ( defined $issuing_rule->{no_auto_renewal_after}
and $issuing_rule->{no_auto_renewal_after} ne "" ) {
# Get issue_date and add no_auto_renewal_after
# If this is greater than today, it's too late for renewal.
my $maximum_renewal_date = dt_from_string($issue->issuedate, 'sql');
$maximum_renewal_date->add(
$issuing_rule->lengthunit => $issuing_rule->no_auto_renewal_after
$issuing_rule->{lengthunit} => $issuing_rule->{no_auto_renewal_after}
);
my $now = dt_from_string;
if ( $now >= $maximum_renewal_date ) {
return ( 0, "auto_too_late" );
}
}
if ( defined $issuing_rule->no_auto_renewal_after_hard_limit
and $issuing_rule->no_auto_renewal_after_hard_limit ne "" ) {
if ( defined $issuing_rule->{no_auto_renewal_after_hard_limit}
and $issuing_rule->{no_auto_renewal_after_hard_limit} ne "" ) {
# If no_auto_renewal_after_hard_limit is >= today, it's also too late for renewal
if ( dt_from_string >= dt_from_string( $issuing_rule->no_auto_renewal_after_hard_limit ) ) {
if ( dt_from_string >= dt_from_string( $issuing_rule->{no_auto_renewal_after_hard_limit} ) ) {
return ( 0, "auto_too_late" );
}
}
@ -2810,17 +2839,17 @@ sub CanBookBeRenewed {
}
}
if ( defined $issuing_rule->norenewalbefore
and $issuing_rule->norenewalbefore ne "" )
if ( defined $issuing_rule->{norenewalbefore}
and $issuing_rule->{norenewalbefore} ne "" )
{
# Calculate soonest renewal by subtracting 'No renewal before' from due date
my $soonestrenewal = dt_from_string( $issue->date_due, 'sql' )->subtract(
$issuing_rule->lengthunit => $issuing_rule->norenewalbefore );
$issuing_rule->{lengthunit} => $issuing_rule->{norenewalbefore} );
# Depending on syspref reset the exact time, only check the date
if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date'
and $issuing_rule->lengthunit eq 'days' )
and $issuing_rule->{lengthunit} eq 'days' )
{
$soonestrenewal->truncate( to => 'day' );
}
@ -3042,14 +3071,16 @@ sub GetRenewCount {
# $item and $borrower should be calculated
my $branchcode = _GetCircControlBranch($item->unblessed, $patron->unblessed);
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
{ categorycode => $patron->categorycode,
my $rule = Koha::CirculationRules->get_effective_rule(
{
categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rule_name => 'renewalsallowed',
}
);
$renewsallowed = $issuing_rule ? $issuing_rule->renewalsallowed : 0;
$renewsallowed = $rule ? $rule->rule_value : 0;
$renewsleft = $renewsallowed - $renewcount;
if($renewsleft < 0){ $renewsleft = 0; }
return ( $renewcount, $renewsallowed, $renewsleft );
@ -3087,25 +3118,29 @@ sub GetSoonestRenewDate {
or return;
my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed );
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
my $issuing_rule = Koha::CirculationRules->get_effective_rules(
{ categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rules => [
'norenewalbefore',
'lengthunit',
]
}
);
my $now = dt_from_string;
return $now unless $issuing_rule;
if ( defined $issuing_rule->norenewalbefore
and $issuing_rule->norenewalbefore ne "" )
if ( defined $issuing_rule->{norenewalbefore}
and $issuing_rule->{norenewalbefore} ne "" )
{
my $soonestrenewal =
dt_from_string( $itemissue->date_due )->subtract(
$issuing_rule->lengthunit => $issuing_rule->norenewalbefore );
$issuing_rule->{lengthunit} => $issuing_rule->{norenewalbefore} );
if ( C4::Context->preference('NoRenewalBeforePrecision') eq 'date'
and $issuing_rule->lengthunit eq 'days' )
and $issuing_rule->{lengthunit} eq 'days' )
{
$soonestrenewal->truncate( to => 'day' );
}
@ -3146,30 +3181,36 @@ sub GetLatestAutoRenewDate {
or return;
my $branchcode = _GetCircControlBranch( $item->unblessed, $patron->unblessed );
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule(
{ categorycode => $patron->categorycode,
my $circulation_rules = Koha::CirculationRules->get_effective_rules(
{
categorycode => $patron->categorycode,
itemtype => $item->effective_itemtype,
branchcode => $branchcode
branchcode => $branchcode,
rules => [
'no_auto_renewal_after',
'no_auto_renewal_after_hard_limit',
'lengthunit',
]
}
);
return unless $issuing_rule;
return unless $circulation_rules;
return
if ( not $issuing_rule->no_auto_renewal_after
or $issuing_rule->no_auto_renewal_after eq '' )
and ( not $issuing_rule->no_auto_renewal_after_hard_limit
or $issuing_rule->no_auto_renewal_after_hard_limit eq '' );
if ( not $circulation_rules->{no_auto_renewal_after}
or $circulation_rules->{no_auto_renewal_after} eq '' )
and ( not $circulation_rules->{no_auto_renewal_after_hard_limit}
or $circulation_rules->{no_auto_renewal_after_hard_limit} eq '' );
my $maximum_renewal_date;
if ( $issuing_rule->no_auto_renewal_after ) {
if ( $circulation_rules->{no_auto_renewal_after} ) {
$maximum_renewal_date = dt_from_string($itemissue->issuedate);
$maximum_renewal_date->add(
$issuing_rule->lengthunit => $issuing_rule->no_auto_renewal_after
$circulation_rules->{lengthunit} => $circulation_rules->{no_auto_renewal_after}
);
}
if ( $issuing_rule->no_auto_renewal_after_hard_limit ) {
my $dt = dt_from_string( $issuing_rule->no_auto_renewal_after_hard_limit );
if ( $circulation_rules->{no_auto_renewal_after_hard_limit} ) {
my $dt = dt_from_string( $circulation_rules->{no_auto_renewal_after_hard_limit} );
$maximum_renewal_date = $dt if not $maximum_renewal_date or $maximum_renewal_date > $dt;
}
return $maximum_renewal_date;
@ -3216,19 +3257,10 @@ sub GetIssuingCharges {
$item_type = $item_data->{itemtype};
$charge = $item_data->{rentalcharge};
my $branch = C4::Context::mybranch();
my $discount_query = q|SELECT rentaldiscount,
issuingrules.itemtype, issuingrules.branchcode
FROM borrowers
LEFT JOIN issuingrules ON borrowers.categorycode = issuingrules.categorycode
WHERE borrowers.borrowernumber = ?
AND (issuingrules.itemtype = ? OR issuingrules.itemtype = '*')
AND (issuingrules.branchcode = ? OR issuingrules.branchcode = '*')|;
my $discount_sth = $dbh->prepare($discount_query);
$discount_sth->execute( $borrowernumber, $item_type, $branch );
my $discount_rules = $discount_sth->fetchall_arrayref({});
if (@{$discount_rules}) {
my $patron = Koha::Patrons->find( $borrowernumber );
my $discount = _get_discount_from_rule($patron->categorycode, $branch, $item_type);
if ($discount) {
# We may have multiple rules so get the most specific
my $discount = _get_discount_from_rule($discount_rules, $branch, $item_type);
$charge = ( $charge * ( 100 - $discount ) ) / 100;
}
if ($charge) {
@ -3241,37 +3273,43 @@ sub GetIssuingCharges {
# Select most appropriate discount rule from those returned
sub _get_discount_from_rule {
my ($rules_ref, $branch, $itemtype) = @_;
my $discount;
my ($categorycode, $branchcode, $itemtype) = @_;
if (@{$rules_ref} == 1) { # only 1 applicable rule use it
$discount = $rules_ref->[0]->{rentaldiscount};
return (defined $discount) ? $discount : 0;
}
# could have up to 4 does one match $branch and $itemtype
my @d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq $itemtype } @{$rules_ref};
if (@d) {
$discount = $d[0]->{rentaldiscount};
return (defined $discount) ? $discount : 0;
}
# do we have item type + all branches
@d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq $itemtype } @{$rules_ref};
if (@d) {
$discount = $d[0]->{rentaldiscount};
return (defined $discount) ? $discount : 0;
}
# do we all item types + this branch
@d = grep { $_->{branchcode} eq $branch && $_->{itemtype} eq q{*} } @{$rules_ref};
if (@d) {
$discount = $d[0]->{rentaldiscount};
return (defined $discount) ? $discount : 0;
}
# so all and all (surely we wont get here)
@d = grep { $_->{branchcode} eq q{*} && $_->{itemtype} eq q{*} } @{$rules_ref};
if (@d) {
$discount = $d[0]->{rentaldiscount};
return (defined $discount) ? $discount : 0;
# Set search precedences
my @params = (
{
branchcode => $branchcode,
itemtype => $itemtype,
categorycode => $categorycode,
},
{
branchcode => '*',
categorycode => $categorycode,
itemtype => $itemtype,
},
{
branchcode => $branchcode,
categorycode => $categorycode,
itemtype => '*',
},
{
branchcode => '*',
categorycode => $categorycode,
itemtype => '*',
},
);
foreach my $params (@params) {
my $rule = Koha::CirculationRules->search(
{
rule_name => 'rentaldiscount',
%$params,
}
)->next();
return $rule->rule_value if $rule;
}
# none of the above
return 0;
}

35
C4/Overdues.pm

@ -35,7 +35,6 @@ use C4::Debug;
use Koha::DateUtils;
use Koha::Account::Lines;
use Koha::Account::Offsets;
use Koha::IssuingRules;
use Koha::Libraries;
use vars qw(@ISA @EXPORT);
@ -238,27 +237,43 @@ sub CalcFine {
my $start_date = $due_dt->clone();
# get issuingrules (fines part will be used)
my $itemtype = $item->{itemtype} || $item->{itype};
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $bortype, itemtype => $itemtype, branchcode => $branchcode });
my $issuing_rule = Koha::CirculationRules->get_effective_rules(
{
categorycode => $bortype,
itemtype => $itemtype,
branchcode => $branchcode,
rules => [
'lengthunit',
'firstremind',
'chargeperiod',
'chargeperiod_charge_at',
'fine',
'overduefinescap',
'cap_fine_to_replacement_price',
'chargename',
]
}
);
$itemtype = Koha::ItemTypes->find($itemtype);
return unless $issuing_rule; # If not rule exist, there is no fine
my $fine_unit = $issuing_rule->lengthunit || 'days';
my $fine_unit = $issuing_rule->{lengthunit} || 'days';
my $chargeable_units = get_chargeable_units($fine_unit, $start_date, $end_date, $branchcode);
my $units_minus_grace = $chargeable_units - ($issuing_rule->firstremind || 0);
my $units_minus_grace = $chargeable_units - ($issuing_rule->{firstremind} || 0);
my $amount = 0;
if ( $issuing_rule->chargeperiod && ( $units_minus_grace > 0 ) ) {
if ( $issuing_rule->{chargeperiod} && ( $units_minus_grace > 0 ) ) {
my $units = C4::Context->preference('FinesIncludeGracePeriod') ? $chargeable_units : $units_minus_grace;
my $charge_periods = $units / $issuing_rule->chargeperiod;
my $charge_periods = $units / $issuing_rule->{chargeperiod};
# If chargeperiod_charge_at = 1, we charge a fine at the start of each charge period
# if chargeperiod_charge_at = 0, we charge at the end of each charge period
$charge_periods = $issuing_rule->chargeperiod_charge_at == 1 ? ceil($charge_periods) : floor($charge_periods);
$amount = $charge_periods * $issuing_rule->fine;
$charge_periods = $issuing_rule->{chargeperiod_charge_at} == 1 ? ceil($charge_periods) : floor($charge_periods);
$amount = $charge_periods * $issuing_rule->{fine};
} # else { # a zero (or null) chargeperiod or negative units_minus_grace value means no charge. }
$amount = $issuing_rule->overduefinescap if $issuing_rule->overduefinescap && $amount > $issuing_rule->overduefinescap;
$amount = $issuing_rule->{overduefinescap} if $issuing_rule->{overduefinescap} && $amount > $issuing_rule->{overduefinescap};
# This must be moved to Koha::Item (see also similar code in C4::Accounts::chargelostitem
$item->{replacementprice} ||= $itemtype->defaultreplacecost
@ -266,7 +281,7 @@ sub CalcFine {
&& ( ! defined $item->{replacementprice} || $item->{replacementprice} == 0 )
&& C4::Context->preference("useDefaultReplacementCost");
$amount = $item->{replacementprice} if ( $issuing_rule->cap_fine_to_replacement_price && $item->{replacementprice} && $amount > $item->{replacementprice} );
$amount = $item->{replacementprice} if ( $issuing_rule->{cap_fine_to_replacement_price} && $item->{replacementprice} && $amount > $item->{replacementprice} );
$debug and warn sprintf("CalcFine returning (%s, %s, %s)", $amount, $units_minus_grace, $chargeable_units);
return ($amount, $units_minus_grace, $chargeable_units);

45
C4/Reserves.pm

@ -40,7 +40,6 @@ use Koha::Database;
use Koha::DateUtils;
use Koha::Hold;
use Koha::Holds;
use Koha::IssuingRules;
use Koha::ItemTypes;
use Koha::Items;
use Koha::Libraries;
@ -2206,24 +2205,40 @@ patron category, itemtype, and library.
sub GetHoldRule {
my ( $categorycode, $itemtype, $branchcode ) = @_;
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare(
q{
SELECT categorycode, itemtype, branchcode, reservesallowed, holds_per_record, holds_per_day
FROM issuingrules
WHERE (categorycode in (?,'*') )
AND (itemtype IN (?,'*'))
AND (branchcode IN (?,'*'))
ORDER BY categorycode DESC,
itemtype DESC,
branchcode DESC
my $reservesallowed = Koha::CirculationRules->get_effective_rule(
{
itemtype => $itemtype,
categorycode => $categorycode,
branchcode => $branchcode,
rule_name => 'reservesallowed',
order_by => {
-desc => [ 'categorycode', 'itemtype', 'branchcode' ]
}
}
);
return unless $reservesallowed;;
$sth->execute( $categorycode, $itemtype, $branchcode );
my $rules;
$rules->{reservesallowed} = $reservesallowed->rule_value;
$rules->{itemtype} = $reservesallowed->itemtype;
$rules->{categorycode} = $reservesallowed->categorycode;
$rules->{branchcode} = $reservesallowed->branchcode;
my $holds_per_x_rules = Koha::CirculationRules->get_effective_rules(
{
itemtype => $itemtype,
categorycode => $categorycode,
branchcode => $branchcode,
rules => ['holds_per_record', 'holds_per_day'],
order_by => {
-desc => [ 'categorycode', 'itemtype', 'branchcode' ]
}
}
);
$rules->{holds_per_record} = $holds_per_x_rules->{holds_per_record};
$rules->{holds_per_day} = $holds_per_x_rules->{holds_per_day};
return $sth->fetchrow_hashref();
return $rules;
}
=head1 AUTHOR

14
Koha/Biblio.pm

@ -37,7 +37,7 @@ use Koha::ArticleRequest::Status;
use Koha::ArticleRequests;
use Koha::Biblio::Metadatas;
use Koha::Biblioitems;
use Koha::IssuingRules;
use Koha::CirculationRules;
use Koha::Item::Transfer::Limits;
use Koha::Items;
use Koha::Libraries;
@ -299,10 +299,16 @@ sub article_request_type_for_bib {
my $borrowertype = $borrower->categorycode;
my $itemtype = $self->itemtype();
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $borrowertype, itemtype => $itemtype });
my $rule = Koha::CirculationRules->get_effective_rule(
{
rule_name => 'article_requests',
categorycode => $borrowertype,
itemtype => $itemtype,
}
);
return q{} unless $issuing_rule;
return $issuing_rule->article_requests || q{}
return q{} unless $rule;
return $rule->rule_value || q{}
}
=head3 article_request_type_for_items

43
Koha/CirculationRules.pm

@ -1,7 +1,6 @@
package Koha::CirculationRules;
# Copyright Vaara-kirjastot 2015
# Copyright Koha Development Team 2016
# Copyright ByWater Solutions 2017
#
# This file is part of Koha.
#
@ -42,6 +41,10 @@ Koha::CirculationRules - Koha CirculationRule Object set class
sub get_effective_rule {
my ( $self, $params ) = @_;
$params->{categorycode} = '*' if exists($params->{categorycode}) && !defined($params->{categorycode});
$params->{branchcode} = '*' if exists($params->{branchcode}) && !defined($params->{branchcode});
$params->{itemtype} = '*' if exists($params->{itemtype}) && !defined($params->{itemtype});
my $rule_name = $params->{rule_name};
my $categorycode = $params->{categorycode};
my $itemtype = $params->{itemtype};
@ -55,6 +58,9 @@ sub get_effective_rule {
$v = undef if $v and $v eq '*';
}
my $order_by = $params->{order_by}
// { -desc => [ 'branchcode', 'categorycode', 'itemtype' ] };
my $search_params;
$search_params->{rule_name} = $rule_name;
@ -65,9 +71,7 @@ sub get_effective_rule {
my $rule = $self->search(
$search_params,
{
order_by => {
-desc => [ 'branchcode', 'categorycode', 'itemtype' ]
},
order_by => $order_by,
rows => 1,
}
)->single;
@ -75,6 +79,35 @@ sub get_effective_rule {
return $rule;
}
=head3 get_effective_rule
=cut
sub get_effective_rules {
my ( $self, $params ) = @_;
my $rules = $params->{rules};
my $categorycode = $params->{categorycode};
my $itemtype = $params->{itemtype};
my $branchcode = $params->{branchcode};
my $r;
foreach my $rule (@$rules) {
my $effective_rule = $self->get_effective_rule(
{
rule_name => $rule,
categorycode => $categorycode,
itemtype => $itemtype,
branchcode => $branchcode,
}
);
$r->{$rule} = $effective_rule->rule_value if $effective_rule;
}
return $r;
}
=head3 set_rule
=cut

68
Koha/IssuingRule.pm

@ -1,68 +0,0 @@
package Koha::IssuingRule;
# Copyright Vaara-kirjastot 2015
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use Koha::Database;
use base qw(Koha::Object);
=head1 NAME
Koha::Hold - Koha Hold object class
=head1 API
=head2 Class Methods
=cut
=head3 delete
=cut
sub delete {
my ($self) = @_;
my $branchcode = $self->branchcode eq '*' ? undef : $self->branchcode;
my $categorycode = $self->categorycode eq '*' ? undef : $self->categorycode;
my $itemtype = $self->itemtype eq '*' ? undef : $self->itemtype;
Koha::CirculationRules->search({
branchcode => $branchcode,
itemtype => $itemtype,
categorycode => $categorycode,
rule_name => [qw(
maxissueqty
maxonsiteissueqty
max_holds
)],
})->delete;
$self->SUPER::delete;
}
=head3 type
=cut
sub _type {
return 'Issuingrule';
}
1;

15
Koha/Item.pm

@ -29,7 +29,7 @@ use C4::Context;
use C4::Circulation;
use C4::Reserves;
use Koha::Checkouts;
use Koha::IssuingRules;
use Koha::CirculationRules;
use Koha::Item::Transfer::Limits;
use Koha::Item::Transfers;
use Koha::Patrons;
@ -369,10 +369,17 @@ sub article_request_type {
: undef;
my $borrowertype = $borrower->categorycode;
my $itemtype = $self->effective_itemtype();
my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $borrowertype, itemtype => $itemtype, branchcode => $branchcode });
my $rule = Koha::CirculationRules->get_effective_rule(
{
rule_name => 'article_requests',
categorycode => $borrowertype,
itemtype => $itemtype,
branchcode => $branchcode
}
);
return q{} unless $issuing_rule;
return $issuing_rule->article_requests || q{}
return q{} unless $rule;
return $rule->rule_value || q{}
}
=head3 current_holds

327
Koha/Schema/Result/Issuingrule.pm

@ -1,327 +0,0 @@
use utf8;
package Koha::Schema::Result::Issuingrule;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
Koha::Schema::Result::Issuingrule
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 TABLE: C<issuingrules>
=cut
__PACKAGE__->table("issuingrules");
=head1 ACCESSORS
=head2 categorycode
data_type: 'varchar'
default_value: (empty string)
is_nullable: 0
size: 10
=head2 itemtype
data_type: 'varchar'
default_value: (empty string)
is_nullable: 0
size: 10
=head2 restrictedtype
data_type: 'tinyint'
is_nullable: 1
=head2 rentaldiscount
data_type: 'decimal'
is_nullable: 1
size: [28,6]
=head2 reservecharge
data_type: 'decimal'
is_nullable: 1
size: [28,6]
=head2 fine
data_type: 'decimal'
is_nullable: 1
size: [28,6]
=head2 finedays
data_type: 'integer'
is_nullable: 1
=head2 maxsuspensiondays
data_type: 'integer'
is_nullable: 1
=head2 suspension_chargeperiod
data_type: 'integer'
default_value: 1
is_nullable: 1
=head2 firstremind
data_type: 'integer'
is_nullable: 1
=head2 chargeperiod
data_type: 'integer'
is_nullable: 1
=head2 chargeperiod_charge_at
data_type: 'tinyint'
default_value: 0
is_nullable: 0
=head2 accountsent
data_type: 'integer'
is_nullable: 1
=head2 issuelength
data_type: 'integer'
is_nullable: 1
=head2 lengthunit
data_type: 'varchar'
default_value: 'days'
is_nullable: 1
size: 10
=head2 hardduedate
data_type: 'date'
datetime_undef_if_invalid: 1
is_nullable: 1
=head2 hardduedatecompare
data_type: 'tinyint'
default_value: 0
is_nullable: 0
=head2 renewalsallowed
data_type: 'smallint'
default_value: 0
is_nullable: 0
=head2 renewalperiod
data_type: 'integer'
is_nullable: 1
=head2 norenewalbefore
data_type: 'integer'
is_nullable: 1
=head2 auto_renew
data_type: 'tinyint'
default_value: 0
is_nullable: 1
=head2 no_auto_renewal_after
data_type: 'integer'
is_nullable: 1
=head2 no_auto_renewal_after_hard_limit
data_type: 'date'
datetime_undef_if_invalid: 1
is_nullable: 1
=head2 reservesallowed
data_type: 'smallint'
default_value: 0
is_nullable: 0
=head2 holds_per_record
data_type: 'smallint'
default_value: 1
is_nullable: 0
=head2 holds_per_day
data_type: 'smallint'
is_nullable: 1
=head2 branchcode
data_type: 'varchar'
default_value: (empty string)
is_nullable: 0
size: 10
=head2 overduefinescap
data_type: 'decimal'
is_nullable: 1
size: [28,6]
=head2 cap_fine_to_replacement_price
data_type: 'tinyint'
default_value: 0
is_nullable: 0
=head2 onshelfholds
data_type: 'tinyint'
default_value: 0
is_nullable: 0
=head2 opacitemholds
data_type: 'char'
default_value: 'N'
is_nullable: 0
size: 1
=head2 article_requests
data_type: 'enum'
default_value: 'no'
extra: {list => ["no","yes","bib_only","item_only"]}
is_nullable: 0
=head2 note
data_type: 'varchar'
is_nullable: 1
size: 100
=cut
__PACKAGE__->add_columns(
"categorycode",
{ data_type => "varchar", default_value => "", is_nullable => 0, size => 10 },
"itemtype",
{ data_type => "varchar", default_value => "", is_nullable => 0, size => 10 },
"restrictedtype",
{ data_type => "tinyint", is_nullable => 1 },
"rentaldiscount",
{ data_type => "decimal", is_nullable => 1, size => [28, 6] },
"reservecharge",
{ data_type => "decimal", is_nullable => 1, size => [28, 6] },
"fine",
{ data_type => "decimal", is_nullable => 1, size => [28, 6] },
"finedays",
{ data_type => "integer", is_nullable => 1 },
"maxsuspensiondays",
{ data_type => "integer", is_nullable => 1 },
"suspension_chargeperiod",
{ data_type => "integer", default_value => 1, is_nullable => 1 },
"firstremind",
{ data_type => "integer", is_nullable => 1 },
"chargeperiod",
{ data_type => "integer", is_nullable => 1 },
"chargeperiod_charge_at",
{ data_type => "tinyint", default_value => 0, is_nullable => 0 },
"accountsent",
{ data_type => "integer", is_nullable => 1 },
"issuelength",
{ data_type => "integer", is_nullable => 1 },
"lengthunit",
{
data_type => "varchar",
default_value => "days",
is_nullable => 1,
size => 10,
},
"hardduedate",
{ data_type => "date", datetime_undef_if_invalid => 1, is_nullable => 1 },
"hardduedatecompare",
{ data_type => "tinyint", default_value => 0, is_nullable => 0 },
"renewalsallowed",
{ data_type => "smallint", default_value => 0, is_nullable => 0 },
"renewalperiod",
{ data_type => "integer", is_nullable => 1 },
"norenewalbefore",
{ data_type => "integer", is_nullable => 1 },
"auto_renew",
{ data_type => "tinyint", default_value => 0, is_nullable => 1 },
"no_auto_renewal_after",
{ data_type => "integer", is_nullable => 1 },
"no_auto_renewal_after_hard_limit",
{ data_type => "date", datetime_undef_if_invalid => 1, is_nullable => 1 },
"reservesallowed",
{ data_type => "smallint", default_value => 0, is_nullable => 0 },
"holds_per_record",
{ data_type => "smallint", default_value => 1, is_nullable => 0 },
"holds_per_day",
{ data_type => "smallint", is_nullable => 1 },
"branchcode",
{ data_type => "varchar", default_value => "", is_nullable => 0, size => 10 },
"overduefinescap",
{ data_type => "decimal", is_nullable => 1, size => [28, 6] },
"cap_fine_to_replacement_price",
{ data_type => "tinyint", default_value => 0, is_nullable => 0 },
"onshelfholds",
{ data_type => "tinyint", default_value => 0, is_nullable => 0 },
"opacitemholds",
{ data_type => "char", default_value => "N", is_nullable => 0, size => 1 },
"article_requests",
{
data_type => "enum",
default_value => "no",
extra => { list => ["no", "yes", "bib_only", "item_only"] },
is_nullable => 0,
},
"note",
{ data_type => "varchar", is_nullable => 1, size => 100 },
);
=head1 PRIMARY KEY
=over 4
=item * L</branchcode>
=item * L</categorycode>
=item * L</itemtype>
=back
=cut
__PACKAGE__->set_primary_key("branchcode", "categorycode", "itemtype");
# Created by DBIx::Class::Schema::Loader v0.07046 @ 2019-03-05 20:49:11
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6bPX0BRWWQZrWFun3GP86Q
sub koha_object_class {
'Koha::IssuingRule';
}
sub koha_objects_class {
'Koha::IssuingRules';
}
1;

115
admin/smart-rules.pl

@ -26,8 +26,6 @@ use C4::Koha;
use C4::Debug;
use Koha::DateUtils;
use Koha::Database;
use Koha::IssuingRule;
use Koha::IssuingRules;
use Koha::Logger;
use Koha::RefundLostItemFeeRules;
use Koha::Libraries;
@ -81,12 +79,41 @@ if ($op eq 'delete') {
my $categorycode = $input->param('categorycode');
$debug and warn "deleting $1 $2 $branch";
Koha::IssuingRules->find({
branchcode => $branch,
categorycode => $categorycode,
itemtype => $itemtype
})->delete;
Koha::CirculationRules->set_rules(
{
categorycode => $categorycode,
branchcode => $branch,
itemtype => $itemtype,
rules => {
restrictedtype => undef,
rentaldiscount => undef,
fine => undef,
finedays => undef,
maxsuspensiondays => undef,
firstremind => undef,
chargeperiod => undef,
chargeperiod_charge_at => undef,
accountsent => undef,
issuelength => undef,
lengthunit => undef,
hardduedate => undef,
hardduedatecompare => undef,
renewalsallowed => undef,
renewalperiod => undef,
norenewalbefore => undef,
auto_renew => undef,
no_auto_renewal_after => undef,
no_auto_renewal_after_hard_limit => undef,
reservesallowed => undef,
holds_per_record => undef,
overduefinescap => undef,
cap_fine_to_replacement_price => undef,
onshelfholds => undef,
opacitemholds => undef,
article_requests => undef,
}
}
);
}
elsif ($op eq 'delete-branch-cat') {
my $categorycode = $input->param('categorycode');
@ -264,10 +291,9 @@ elsif ($op eq 'add') {
my $note = $input->param('note');
$debug and warn "Adding $br, $bor, $itemtype, $fine, $maxissueqty, $maxonsiteissueqty, $cap_fine_to_replacement_price";
my $params = {
branchcode => $br,
categorycode => $bor,
itemtype => $itemtype,
my $rules = {
maxissueqty => $maxissueqty,
maxonsiteissueqty => $maxonsiteissueqty,
fine => $fine,
finedays => $finedays,
maxsuspensiondays => $maxsuspensiondays,
@ -297,21 +323,12 @@ elsif ($op eq 'add') {
note => $note,
};
my $issuingrule = Koha::IssuingRules->find({categorycode => $bor, itemtype => $itemtype, branchcode => $br});
if ($issuingrule) {
$issuingrule->set($params)->store();
} else {
Koha::IssuingRule->new()->set($params)->store();
}
Koha::CirculationRules->set_rules(
{
categorycode => $bor,
itemtype => $itemtype,
branchcode => $br,
rules => {
maxissueqty => $maxissueqty,
maxonsiteissueqty => $maxonsiteissueqty,
}
rules => $rules,
}
);
@ -548,62 +565,16 @@ $template->param(
my $patron_categories = Koha::Patron::Categories->search({}, { order_by => ['description'] });
my @row_loop;
my $itemtypes = Koha::ItemTypes->search_with_localization;
my $sth2 = $dbh->prepare("
SELECT issuingrules.*,
itemtypes.description AS humanitemtype,
categories.description AS humancategorycode,
COALESCE( localization.translation, itemtypes.description ) AS translated_description
FROM issuingrules
LEFT JOIN itemtypes
ON (itemtypes.itemtype = issuingrules.itemtype)
LEFT JOIN categories
ON (categories.categorycode = issuingrules.categorycode)
LEFT JOIN localization ON issuingrules.itemtype = localization.code
AND localization.entity = 'itemtypes'
AND localization.lang = ?
WHERE issuingrules.branchcode = ?
");
$sth2->execute($language, $branch);
while (my $row = $sth2->fetchrow_hashref) {
$row->{'current_branch'} ||= $row->{'branchcode'};
$row->{humanitemtype} ||= $row->{itemtype};
$row->{default_translated_description} = 1 if $row->{humanitemtype} eq '*';
$row->{'humancategorycode'} ||= $row->{'categorycode'};
$row->{'default_humancategorycode'} = 1 if $row->{'humancategorycode'} eq '*';
$row->{'fine'} = sprintf('%.2f', $row->{'fine'});
if ($row->{'hardduedate'} && $row->{'hardduedate'} ne '0000-00-00') {
my $harddue_dt = eval { dt_from_string( $row->{'hardduedate'} ) };
$row->{'hardduedate'} = eval { output_pref( { dt => $harddue_dt, dateonly => 1 } ) } if ( $harddue_dt );
$row->{'hardduedatebefore'} = 1 if ($row->{'hardduedatecompare'} == -1);
$row->{'hardduedateexact'} = 1 if ($row->{'hardduedatecompare'} == 0);
$row->{'hardduedateafter'} = 1 if ($row->{'hardduedatecompare'} == 1);
} else {
$row->{'hardduedate'} = 0;
}
if ($row->{no_auto_renewal_after_hard_limit}) {
my $dt = eval { dt_from_string( $row->{no_auto_renewal_after_hard_limit} ) };
$row->{no_auto_renewal_after_hard_limit} = eval { output_pref( { dt => $dt, dateonly => 1 } ) } if $dt;
}
push @row_loop, $row;
}
my @sorted_row_loop = sort by_category_and_itemtype @row_loop;
$template->param(show_branch_cat_rule_form => 1);
$template->param(
patron_categories => $patron_categories,
itemtypeloop => $itemtypes,
rules => \@sorted_row_loop,
humanbranch => ($branch ne '*' ? $branch : ''),
current_branch => $branch,
definedbranch => scalar(@sorted_row_loop)>0
);
itemtypeloop => $itemtypes,
humanbranch => ( $branch ne '*' ? $branch : '' ),
current_branch => $branch,
);
output_html_with_http_headers $input, $cookie, $template->output;