2 # ---------------------------------------------------------------
3 # Copyright (C) 2022 King County Library System
4 # Author: Bill Erickson <berickxx@gmail.com>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 # ---------------------------------------------------------------
20 use OpenSRF::AppSession;
21 use OpenSRF::Utils::SettingsClient;
22 use OpenILS::Utils::Fieldmapper;
23 use OpenSRF::Utils::Logger q/$logger/;
24 use OpenILS::Utils::CStoreEditor;
25 use OpenILS::Application::AppUtils;
27 my $U = 'OpenILS::Application::AppUtils';
28 $ENV{OSRF_LOG_CLIENT} = 1;
30 my $osrf_config = '/openils/conf/opensrf_core.xml';
32 my $process_as = 'admin';
35 my $has_open_circ = 0;
36 my @included_penalties;
50 'osrf-config=s' => \$osrf_config,
51 'ids-file=s' => \$ids_file,
52 'process-as=s' => \$process_as,
53 'min-id=s' => \$min_id,
54 'max-id=s' => \$max_id,
55 'has-open-circ' => \$has_open_circ,
56 'owes-more-than=s' => \$owes_more_than,
57 'owes-less-than=s' => \$owes_less_than,
58 'has-penalty=s' => \$has_penalty,
59 'no-has-penalty=s' => \$no_has_penalty,
60 'include-penalty=s' => \@included_penalties,
61 'patron-home-context' => \$home_ou_context,
62 'verbose' => \$verbose,
70 Update patron penalties in batch with options for filtering which
77 --osrf-config [/openils/conf/opensrf_core.xml]
79 --process-as <eg-account>
80 Username of an Evergreen account to use for creating the
81 internal auth session. Defaults to 'admin'.
84 Use each user's home library as the penalty calculation
85 context. Otherwise the home library of the --process-as user
86 is used to identify the thresholds and custom penalties to
89 --has-penalty <penalty-name-or-id>
90 Limit to patrons that currently have a specific penalty. If
91 an id is specified, only that exact penalty is checked. If
92 a name is supplied, the system will check for a custom penalty
93 configured for use at the selected users' home libraries.
95 --no-has-penalty <penalty-name-or-id>
96 Limit to patrons that do not currently have a specific penalty.
97 If an id is specified, only that exact penalty is checked. If
98 a name is supplied, the system will check for a custom penalty
99 configured for use at the selected users' home libraries.
101 --include-penalty <penalty-name-or-id>
102 Limit to a specific penalty. Specify multiple times for
103 multiple penalties. If an id is specified, only the exact
104 penalties will be calculated. Custom penalties will be looked
105 up as needed if a name is supplied.
108 Lowest patron ID to process.
111 Highest patron ID to process.
113 Together with --min-id, these are useful for running parallel
114 batches of this script without overlapping and/or processing
115 chunks of a controlled size.
118 Limit to patrons that have at least on open circulation.
119 For simplicity, "open" in this context means null xact finish.
121 --owes-more-than <amount>
122 Limit to patrons who have an outstanding balance greater than
123 the specified amount.
125 --owes-less-than <amount>
126 Limit to patrons who have an outstanding balance less than
127 the specified amount.
130 Log debug info to STDOUT. This script logs various information
131 via \$logger regardless of this option.
139 help() if $help or !$ops;
141 # $lvl should match a $logger logging function. E.g. 'info', 'error', etc.
147 # always announce errors and warnings
148 return unless $verbose || $lvl =~ /error|warn/;
150 my $date_str = DateTime->now(time_zone => 'local')->strftime('%F %T');
151 print "$date_str $msg\n";
155 my ($limit, $offset) = @_;
159 open(IDS_FILE, '<', $ids_file)
160 or die "Cannot open user IDs file: $ids_file: $!\n";
162 my @ids = <IDS_FILE>;
166 @ids = grep { defined $_ } @ids[$offset..($offset + $limit)];
174 mus => ['balance_owed']
187 order_by => [{class => 'au', field => 'id'}]
190 my @where = ({'+au' => {deleted => 'f'}});
192 if (defined $max_id) {
195 '+au' => { # min_id defaults to 0.
196 id => {between => [$min_id, $max_id]}
200 } elsif (defined $min_id) {
204 # min_id defaults to 0.
205 id => {'>' => $min_id}
212 if ($has_penalty !~ /^\d+$/) { # got a penalty name, look up possible custom ones for the patron or processing user home org
213 $has_penalty = {in => { union => [
214 {select => { csp => ['id'] }, from => csp => where => { name => $has_penalty }},
216 { aous => [{column => value => transform => btrim => params => '"'}] },
219 name => 'circ.custom_penalty_override.'.$has_penalty,
221 {select => { aou => [{column => id => transform => 'actor.org_unit_ancestors' => result_field => id => alias => 'id'}]},
223 where => { id => ($home_ou_context ? { '+au' => 'home_ou' } : $auth_user_home) }}
232 select => {ausp => ['id']},
235 usr => {'=' => {'+au' => 'id'}},
236 standing_penalty => $has_penalty,
238 {stop_date => undef},
239 {stop_date => {'>' => 'now'}}
247 if ($no_has_penalty) {
249 if ($no_has_penalty !~ /^\d+$/) { # got a penalty name, look up possible custom ones for the patron or processing user home org
250 $no_has_penalty = {in => { union => [
251 {select => { csp => ['id'] }, from => csp => where => { name => $no_has_penalty }},
253 { aous => [{column => value => transform => btrim => params => '"'}] },
256 name => 'circ.custom_penalty_override.'.$no_has_penalty,
258 {select => { aou => [{column => id => transform => 'actor.org_unit_ancestors' => result_field => id => alias => 'id'}]},
260 where => { id => ($home_ou_context ? { '+au' => 'home_ou' } : $auth_user_home) }}
270 select => {ausp => ['id']},
273 usr => {'=' => {'+au' => 'id'}},
274 standing_penalty => $no_has_penalty,
276 {stop_date => undef},
277 {stop_date => {'>' => 'now'}}
286 # For owes more / less, there is a special case because not all
287 # patrons have a money.usr_summary row. If they don't, they
288 # effectively owe $0.00.
290 if (defined $owes_more_than) {
292 if ($owes_more_than > 0) {
296 balance_owed => {'>' => $owes_more_than}
304 balance_owed => {'>' => $owes_more_than}
308 usr => undef # owes $0.00
315 if (defined $owes_less_than) {
317 if ($owes_less_than < 0) {
320 balance_owed => {'<' => $owes_less_than}
322 }) if $owes_less_than;
329 balance_owed => {'<' => $owes_less_than}
333 usr => undef # owes $0.00
342 select => {circ => ['id']},
345 usr => {'=' => {'+au' => 'id'}},
350 }) if $has_open_circ;
352 $query->{where}->{'-and'} = \@where;
354 my $resp = $e->json_query($query);
356 return [map {$_->{id}} @$resp];
361 my $limit = $batch_size;
365 my $method = 'open-ils.actor.user.penalties.update';
366 $method .= '_at_home' if $home_ou_context;
369 my $user_ids = get_user_ids($limit, $offset);
371 my $num = scalar(@$user_ids);
378 "Processing batch $batches; count=$num; offset=$offset; ids=" .
379 @$user_ids[0] . '..' . @$user_ids[$#$user_ids]);
381 for my $user_id (@$user_ids) {
384 'open-ils.actor', $method,
385 $authtoken, $user_id, @included_penalties
391 $offset += $batch_size;
394 announce('debug', "$counter total patrons processed.");
399 my $auth_user = $e->search_actor_user(
400 {usrname => $process_as, deleted => 'f'})->[0];
402 die "No such user '$process_as' to use for authentication\n" unless $auth_user;
404 my $auth_resp = $U->simplereq(
405 'open-ils.auth_internal',
406 'open-ils.auth_internal.session.create',
407 {user_id => $auth_user->id, login_type => 'staff'}
410 die "Could not create an internal auth session\n" unless (
412 $auth_resp->{payload} &&
413 ($authtoken = $auth_resp->{payload}->{authtoken}) &&
414 ($auth_user_home = $auth_user->home_ou)
419 OpenSRF::System->bootstrap_client(config_file => $osrf_config);
420 Fieldmapper->import(IDL =>
421 OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
422 OpenILS::Utils::CStoreEditor::init();
423 $e = OpenILS::Utils::CStoreEditor->new;