Bug 9762: (Alternative) Log circulation overrides

An alternative approach to the original submission for bug 9762. Instead
of recording a distinct action log line for a set of override messages
in addition to the standard issue record, this patch updates the
existing issue log to include any confirmation or impossible responses
from the preceeding CanBookBeIssued call.

We continue to record these as a JSON structure as the prior patch did,
but instead of human messages we use the predefined codes as returned
from the existing CanBookBeIssued response.

To test:

After each step, a single action log message should be displayed under
`Tools > Log viewer`. The message itself will now consist of a JSON
string with an array of 'confirmations' and 'forced' strings
representing the overriden feedback from CanBookBeIssued.

Circulation -- Checking out

1) Check out an item that is age-restricted (AgeRestrictionOverride syspref)
2) Check out an item that has a “not for loan” status (AllowNotForLoanOverride
   syspref)
3) Check out an item that has a “lost” status (IssueLostItem syspref)
4) Check out an item to a patron who has reached the checkout limit
   (AllowTooManyOverride syspref)
5) Check out an item to a patron who is not allowed to borrow this item type
6) Check out an item to a patron who has unpaid fines (AllFinesNeedOverride
   and/or AllowFineOverride + noissuescharge sysprefs)
7) Check out an item on hold for someone else
8) Check out an item on hold ready for pickup by someone else
9) Check out an item already checked out to someone else
   (AutoReturnCheckedOutItems syspref)
10) Check out to a patron who has restrictions

Circulation -- Checking in

11) Ignore a hold upon check in
12) Ignore a hold and transfer upon check in

Circulation – Renewing

13) Renew an item on hold for someone else
    (AllowRenewalOnHoldOverride syspref)
14) Renew an item that has reached the maximum number of renewals
    (AllowRenewalLimitOverride syspref)

Holds

15) Place a hold for a patron who is not allowed to place this item
    type on hold (AllowHoldPolicyOverride syspref)
16) Place a hold for a patron who has reached the maximum number of
    holds
17) Place an item-level hold when rules dictate that this is not
    allowed

Sponsored-by: OpenFifth <https://openfifth.co.uk/>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2025-04-23 14:17:28 +01:00 committed by Lucas Gass
parent 86f4eb93aa
commit 5350a9276e
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
9 changed files with 170 additions and 14 deletions

View File

@ -1674,6 +1674,8 @@ sub AddIssue {
my $auto_renew = $params && $params->{auto_renew};
my $cancel_recall = $params && $params->{cancel_recall};
my $recall_id = $params && $params->{recall_id};
my $confirmations = $params->{confirmations};
my $forced = $params->{forced};
my $dbh = C4::Context->dbh;
my $barcodecheck = CheckValidBarcode($barcode);
@ -2011,11 +2013,27 @@ sub AddIssue {
}
);
}
logaction(
"CIRCULATION", "ISSUE",
$patron->borrowernumber,
$item_object->itemnumber,
) if C4::Context->preference("IssueLog");
# Log the checkout
if ( C4::Context->preference('IssueLog') ) {
my $info = $item_object->itemnumber;
if ( defined($confirmations) || defined($forced) ) {
$info = to_json(
{
issue => $issue->issue_id,
itemnumber => $item_object->itemnumber,
confirmations => $confirmations,
forced => $forced
},
{ pretty => 1, canonical => 1 }
);
}
logaction(
"CIRCULATION", "ISSUE",
$patron->borrowernumber,
$info,
);
}
Koha::Plugins->call(
'after_circ_action',

View File

@ -174,7 +174,16 @@ sub do_checkout {
$recall_id = $messages->{RECALLED};
}
}
my $issue = AddIssue( $patron, $barcode, $overridden_duedate, 0, undef, undef, { recall_id => $recall_id } );
my $issue = AddIssue(
$patron, $barcode,
$overridden_duedate,
0, undef, undef,
{
recall_id => $recall_id,
confirmations => [ grep { /^[A-Z_]+$/ } keys %{$needsconfirmation} ],
forced => [ keys %{$issuingimpossible} ]
}
);
$self->{due} = $self->duedatefromissue( $issue, $itemnumber );
}

View File

@ -252,7 +252,15 @@ sub mark_as_delivered {
C4::Circulation::CanBookBeIssued( $patron, $hold->item->barcode );
unless ( keys %$issuingimpossible ) {
my $issue = C4::Circulation::AddIssue( $patron, $hold->item->barcode );
my $issue = C4::Circulation::AddIssue(
$patron,
$hold->item->barcode,
undef, undef, undef, undef,
{
confirmations => [ grep { /^[A-Z_]+$/ } keys %{$needsconfirmation} ],
forced => [ keys %{$issuingimpossible} ]
}
);
if ($issue) {
Koha::CurbsidePickupIssue->new(
{

View File

@ -299,7 +299,15 @@ sub add {
}
# Call 'AddIssue'
my $checkout = AddIssue( $patron, $item->barcode );
my $checkout = AddIssue(
$patron,
$item->barcode,
undef, undef, undef, undef,
{
confirmations => [ grep { /^[A-Z_]+$/ } keys %{$confirmation} ],
forced => [ keys %{$impossible} ]
}
);
if ($checkout) {
$c->res->headers->location( $c->req->url->to_string . '/' . $checkout->id );
return $c->render(

View File

@ -552,9 +552,13 @@ if ( @$barcodes && $op eq 'cud-checkout' ) {
$cancelreserve,
undef, undef,
{
onsite_checkout => $onsite_checkout, auto_renew => $session->param('auto_renew'),
switch_onsite_checkout => $switch_onsite_checkout, cancel_recall => $cancel_recall,
onsite_checkout => $onsite_checkout,
auto_renew => $session->param('auto_renew'),
switch_onsite_checkout => $switch_onsite_checkout,
cancel_recall => $cancel_recall,
recall_id => $recall_id,
confirmations => [ grep { /^[A-Z_]+$/ } keys %{$needsconfirmation} ],
forced => [ keys %{$issuingimpossible} ]
}
);
$template_params->{issue} = $issue;

View File

@ -176,3 +176,62 @@
<span>[% log_interface | html %]</span>
[% END %]
[% END %]
[% BLOCK translate_log_override %]
[% SWITCH override %]
[% CASE 'INVALID_DATE' %]
<span>Invalid date confirmed</span>
[% CASE 'DEBT_GUARANTEES' %]
<span>Guarantees had debts</span>
[% CASE 'DEBT_GUARANTORS' %]
<span>Guarantors had debts</span>
[% CASE 'DEBT' %]
<span>Patron had debt</span>
[% CASE 'USERBLOCKEDOVERDUE' %]
<span>Patron had overdues</span>
[% CASE 'ADDITIONAL_MATERIALS' %]
<span>Additional materials were confirmed</span>
[% CASE 'RENEW_ISSUE' %]
<span>Renewal</span>
[% CASE 'ISSUED_TO_ANOTHER' %]
<span>Item was issued to another patron prior to this checkout</span>
[% CASE 'PATRON_CANT' %]
<span>Max renewals reached</span>
[% CASE 'TOO_MANY' %]
<span>Max renewals reached</span>
[% CASE 'PREVISSUE' %]
<span>Patron was notified of previous issues</span>
[% CASE 'NOT_FOR_LOAN' %]
<span>Item was not for loan</span>
[% CASE 'NOT_FOR_LOAN_FORCING' %]
<span>Item was not for loan</span>
[% CASE 'ITEM_LOST' %]
<span>Item was reported lost</span>
[% CASE 'BORRNOTSAMEBRANCH' %]
<span>Patron does not belong to this library</span>
[% CASE 'RENTALCHARGE' %]
<span>Item had a rental charge</span>
[% CASE 'RECALLED' %]
<span>Item was recalled</span>
[% CASE 'RESERVE_WAITING' %]
<span>Item had a reserve waiting</span>
[% CASE 'RESERVED' %]
<span>Item was reserved</span>
[% CASE 'TRANSFERRED' %]
<span>Item was transferred</span>
[% CASE 'PROCESSING' %]
<span>Item was in processing</span>
[% CASE 'BOOKED_EARLY' %]
<span>Item was booked and checked out early</span>
[% CASE 'BOOKED_TO_ANOTHER' %]
<span>Item was booked to another patron</span>
[% CASE 'AGE_RESTRICTION' %]
<span>Item has an eage restriction</span>
[% CASE 'HIGHHOLDS' %]
<span>Item had high holds</span>
[% CASE 'BIBLIO_ALREADY_ISSUED' %]
<span>Biblio was already issued</span>
[% CASE %]
<span>[% override | html %]</span>
[% END %]
[% END %]

View File

@ -333,6 +333,22 @@
title="Display detail for this item"
>Item [% loopro.barcode | html %]</a
>
[% ELSIF ( loopro.module == 'CIRCULATION' && loopro.json_found ) %]
<div class="loginfo" id="loginfo[% loopro.action_id | html %]">
<div>
<a
href="/cgi-bin/koha/catalogue/moredetail.pl?itemnumber=[% loopro.json.itemnumber | uri %]&amp;biblionumber=[% loopro.json.biblionumber | uri %]&amp;bi=[% loopro.json.biblioitemnumber | uri %]#item[% loopro.json.itemnumber | uri %]"
title="Display detail for this item"
>Item [% loopro.json.barcode | html %]</a
>
</div>
[% FOREACH force IN loopro.json.forces %]
<div class="forced"> [% PROCESS translare_log_override override=force %] </div>
[% END %]
[% FOREACH confirmation IN loopro.json.confirmations %]
<div class="confirmed"> [% PROCESS translate_log_override override=confirmation %] </div>
[% END %]
</div>
[% ELSIF loopro.module == "SYSTEMPREFERENCE" || loopro.module == "REPORTS" || loopro.module == "NEWS" %]
<div class="loginfo" id="loginfo[% loopro.action_id | html %]">[% loopro.info | trim | html %]</div>
<div class="compare_info" id="compare_info[% loopro.action_id | html %]">

View File

@ -281,7 +281,13 @@ if ( $patron && $op eq "cud-returnbook" && $allowselfcheckreturns ) {
)->count;
}
my $new_issue = AddIssue( $patron, $barcode );
my $new_issue = AddIssue(
$patron, $barcode, undef, undef, undef, undef,
{
confirmations => [ grep { /^[A-Z_]+$/ } keys %{$needconfirm} ],
forced => [ keys %{$impossible} ]
}
);
$template->param( issued => 1, new_issue => $new_issue );
push @newissueslist, $barcode unless ( grep /^$barcode$/, @newissueslist );

View File

@ -22,6 +22,7 @@ use Modern::Perl;
use C4::Auth qw( get_template_and_user );
use CGI qw ( -utf8 );
use JSON;
use Text::CSV::Encoded;
use C4::Context;
use C4::Output qw( output_html_with_http_headers );
@ -152,12 +153,11 @@ if ($do_it) {
$result->{'biblioitemnumber'} = q{};
$result->{'barcode'} = q{};
if ( substr( $log->info, 0, 4 ) eq 'item' || $log->module eq "CIRCULATION" ) {
if ( substr( $log->info, 0, 4 ) eq 'item' ) {
# get item information so we can create a working link
my $itemnumber = $log->object;
$itemnumber = $log->info if ( $log->module eq "CIRCULATION" );
my $item = Koha::Items->find($itemnumber);
my $item = Koha::Items->find($itemnumber);
if ($item) {
$result->{'object_found'} = 1;
$result->{'biblionumber'} = $item->biblionumber;
@ -166,6 +166,34 @@ if ($do_it) {
}
}
if ( $log->module eq "CIRCULATION" ) {
my $info = $log->info;
my $decoded;
my $is_json = eval {
$decoded = decode_json($info);
1;
};
if ( $is_json && ref($decoded) ) {
$result->{'json_found'} = 1;
my $item = Koha::Items->find( $decoded->{itemnumber} );
if ($item) {
$decoded->{biblionumber} = $item->biblionumber;
$decoded->{biblioitemnumber} = $item->biblionumber;
$decoded->{barcode} = $item->barcode;
}
$result->{'json'} = $decoded;
} else {
my $item = Koha::Items->find($info);
if ($item) {
$result->{'object_found'} = 1;
$result->{'biblionumber'} = $item->biblionumber;
$result->{'biblioitemnumber'} = $item->biblionumber;
$result->{'barcode'} = $item->barcode;
}
}
}
#always add firstname and surname for librarian/user
if ( $log->user ) {
my $patron = Koha::Patrons->find( $log->user );