1 package OpenILS::Application::Collections;
2 use strict; use warnings;
3 use OpenSRF::EX qw(:try);
4 use OpenILS::Application::AppUtils;
5 use OpenSRF::Utils::Logger qw(:logger);
6 use OpenSRF::Application;
7 use OpenILS::Utils::Fieldmapper;
8 use base 'OpenSRF::Application';
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
11 use OpenILS::Const qw/:const/;
12 my $U = "OpenILS::Application::AppUtils";
15 # --------------------------------------------------------------
16 # Loads the config info
17 # --------------------------------------------------------------
18 sub initialize { return 1; }
21 __PACKAGE__->register_method(
22 method => 'users_of_interest',
23 api_name => 'open-ils.collections.users_of_interest.retrieve',
28 Returns an array of user information objects that the system
29 based on the search criteria provided. If the total fines
30 a user owes reaches or exceeds "fine_level" on or befre "age"
31 and the fines were created at "location", the user will be
32 included in the return set/,
36 desc => 'The authentication token',
40 desc => q/The date before or at which the user's fine level exceeded the fine_level param/,
41 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
44 { name => 'fine_level',
45 desc => q/The fine threshold at which users will be included in the search results /,
46 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
49 desc => q/The short-name of the orginization unit (library) at which the fines were created.
50 If a selected location has 'child' locations (e.g. a library region), the
51 child locations will be included in the search/,
57 desc => q/An array of user information objects.
58 usr : Array of user information objects containing id, dob, profile, and groups
59 threshold_amount : The total amount the patron owes that is at least as old
60 as the fine "age" and whose transaction was created at the searched location
61 last_pertinent_billing : The time of the last billing that relates to this query
69 groups => [ 'Patron', 'Staff' ],
71 threshold_amount => 99,
78 sub users_of_interest {
79 my( $self, $conn, $auth, $age, $fine_level, $location ) = @_;
81 return OpenILS::Event->new('BAD_PARAMS')
82 unless ($auth and $age and $fine_level and $location);
84 my $e = new_editor(authtoken => $auth);
85 return $e->event unless $e->checkauth;
87 my $org = $e->search_actor_org_unit({shortname => $location})
88 or return $e->event; $org = $org->[0];
90 # they need global perms to view users so no org is provided
91 return $e->event unless $e->allowed('VIEW_USER');
93 my $data = $U->storagereq(
94 'open-ils.storage.money.collections.users_of_interest.atomic',
95 $age, $fine_level, $location);
97 return [] unless $data and @$data;
100 my $u = $e->retrieve_actor_user(
105 flesh_fields => {au => ["groups","profile", "card"]},
106 select => {au => ["profile","id","dob", "card"]}
114 profile => $u->profile->name,
115 barcode => $u->card->barcode,
116 groups => [ map { $_->name } @{$u->groups} ],
124 __PACKAGE__->register_method(
125 method => 'users_with_activity',
126 api_name => 'open-ils.collections.users_with_activity.retrieve',
131 Returns an array of users that are already in collections
132 and had any type of billing or payment activity within
133 the given time frame at the location (or child locations)
138 desc => 'The authentication token',
141 { name => 'start_date',
142 desc => 'The start of the time interval to check',
143 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
146 { name => 'end_date',
147 desc => q/Then end date of the time interval to check/,
148 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
150 { name => 'location',
151 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
152 If a selected location has 'child' locations (e.g. a library region), the
153 child locations will be included in the search/,
159 desc => q/An array of user information objects/,
165 sub users_with_activity {
166 my( $self, $conn, $auth, $start_date, $end_date, $location ) = @_;
167 return OpenILS::Event->new('BAD_PARAMS')
168 unless ($auth and $start_date and $end_date and $location);
170 my $e = new_editor(authtoken => $auth);
171 return $e->event unless $e->checkauth;
173 my $org = $e->search_actor_org_unit({shortname => $location})
174 or return $e->event; $org = $org->[0];
175 return $e->event unless $e->allowed('VIEW_USER', $org->id);
177 return $U->storagereq(
178 'open-ils.storage.money.collections.users_with_activity.atomic',
179 $start_date, $end_date, $location);
184 __PACKAGE__->register_method(
185 method => 'put_into_collections',
186 api_name => 'open-ils.collections.put_into_collections',
191 Marks a user as being "in collections" at a given location
196 desc => 'The authentication token',
200 desc => 'The id of the user to plact into collections',
204 { name => 'location',
205 desc => q/The short-name of the orginization unit (library)
206 for which the user is being placed in collections/,
209 { name => 'fee_amount',
211 The amount of money that a patron should be fined.
212 If this field is empty, no fine is created.
216 { name => 'fee_note',
218 Custom note that is added to the the billing.
219 This field is not required.
220 Note: fee_note is not the billing_type. Billing_type type is
221 decided by the system. (e.g. "fee for collections").
222 fee_note is purely used for any additional needed information
223 and is only visible to staff.
230 desc => q/A SUCCESS event on success, error event on failure/,
235 sub put_into_collections {
236 my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
238 return OpenILS::Event->new('BAD_PARAMS')
239 unless ($auth and $user_id and $location);
241 my $e = new_editor(authtoken => $auth, xact =>1);
242 return $e->event unless $e->checkauth;
244 my $org = $e->search_actor_org_unit({shortname => $location});
245 return $e->event unless $org = $org->[0];
246 return $e->event unless $e->allowed('money.collections_tracker.create', $org->id);
248 my $existing = $e->search_money_collections_tracker(
250 location => $org->id,
252 collector => $e->requestor->id
257 return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
259 $logger->info("collect: user ".$e->requestor->id.
260 " putting user $user_id into collections for $location");
262 my $tracker = Fieldmapper::money::collections_tracker->new;
264 $tracker->usr($user_id);
265 $tracker->collector($e->requestor->id);
266 $tracker->location($org->id);
267 $tracker->enter_time('now');
269 $e->create_money_collections_tracker($tracker)
273 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
278 return OpenILS::Event->new('SUCCESS');
281 sub add_collections_fee {
282 my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
286 $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
288 my $xact = Fieldmapper::money::grocery->new;
289 $xact->usr($patron_id);
290 $xact->xact_start('now');
291 $xact->billing_location($org->id);
293 $xact = $e->create_money_grocery($xact) or return $e->event;
295 my $bill = Fieldmapper::money::billing->new;
296 $bill->note($fee_note);
297 $bill->xact($xact->id);
298 $bill->billing_type('SYSTEM: Long Overdue Processing Fee'); # XXX
299 $bill->amount($fee_amount);
301 $e->create_money_billing($bill) or return $e->event;
308 __PACKAGE__->register_method(
309 method => 'remove_from_collections',
310 api_name => 'open-ils.collections.remove_from_collections',
312 Returns the users that are currently in collections and
313 had activity during the provided interval. Dates are inclusive.
314 @param start_date The beginning of the activity interval
315 @param end_date The end of the activity interval
316 @param location The location at which the fines were created
321 __PACKAGE__->register_method(
322 method => 'remove_from_collections',
323 api_name => 'open-ils.collections.remove_from_collections',
328 Removes a user from the collections table for the given location
333 desc => 'The authentication token',
337 desc => 'The id of the user to plact into collections',
341 { name => 'location',
342 desc => q/The short-name of the orginization unit (library)
343 for which the user is being removed from collections/,
349 desc => q/A SUCCESS event on success, error event on failure/,
355 sub remove_from_collections {
356 my( $self, $conn, $auth, $user_id, $location ) = @_;
358 return OpenILS::Event->new('BAD_PARAMS')
359 unless ($auth and $user_id and $location);
361 my $e = new_editor(authtoken => $auth, xact=>1);
362 return $e->event unless $e->checkauth;
364 my $org = $e->search_actor_org_unit({shortname => $location})
365 or return $e->event; $org = $org->[0];
366 return $e->event unless $e->allowed('money.collections_tracker.delete', $org->id);
368 my $tracker = $e->search_money_collections_tracker(
369 { usr => $user_id, location => $org->id })
372 $e->delete_money_collections_tracker($tracker->[0])
376 return OpenILS::Event->new('SUCCESS');
380 #__PACKAGE__->register_method(
381 # method => 'transaction_details',
382 # api_name => 'open-ils.collections.user_transaction_details.retrieve',
388 __PACKAGE__->register_method(
389 method => 'transaction_details',
390 api_name => 'open-ils.collections.user_transaction_details.retrieve',
395 Returns a list of fleshed user objects with transaction details
400 desc => 'The authentication token',
403 { name => 'start_date',
404 desc => 'The start of the time interval to check',
405 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
408 { name => 'end_date',
409 desc => q/Then end date of the time interval to check/,
410 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
412 { name => 'location',
413 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
414 If a selected location has 'child' locations (e.g. a library region), the
415 child locations will be included in the search/,
420 desc => 'An array of user ids',
426 desc => q/A list of objects. Object keys include:
428 transactions : An object with keys :
429 circulations : Fleshed circulation objects
430 grocery : Fleshed 'grocery' transaction objects
437 sub transaction_details {
438 my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
440 return OpenILS::Event->new('BAD_PARAMS')
441 unless ($auth and $start_date and $end_date and $location and $user_list);
443 my $e = new_editor(authtoken => $auth);
444 return $e->event unless $e->checkauth;
446 # they need global perms to view users so no org is provided
447 return $e->event unless $e->allowed('VIEW_USER');
449 my $org = $e->search_actor_org_unit({shortname => $location})
450 or return $e->event; $org = $org->[0];
452 # get a reference to the org inside of the tree
453 $org = $U->find_org($U->fetch_org_tree(), $org->id);
456 for my $uid (@$user_list) {
459 $blob->{usr} = $e->retrieve_actor_user(
468 "standing_penalties",
479 $blob->{transactions} = {
481 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
483 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date)
486 # for each transaction, flesh the workstatoin on any attached payment
487 # and make the payment object a real object (e.g. cash payment),
488 # not just a generic payment object
490 @{$blob->{transactions}->{circulations}},
491 @{$blob->{transactions}->{grocery}} ) {
494 if( $ps = $xact->payments and @$ps ) {
495 my @fleshed; my $evt;
497 ($p, $evt) = flesh_payment($e,$p);
501 $xact->payments(\@fleshed);
505 push( @data, $blob );
514 my $type = $p->payment_type;
515 $logger->debug("collect: fleshing workstation on payment $type : ".$p->id);
516 my $meth = "retrieve_money_$type";
517 $p = $e->$meth($p->id) or return (undef, $e->event);
520 $e->retrieve_actor_workstation(
525 flesh_fields => { aws => [ 'owning_lib' ] }
530 } catch Error with {};
535 # --------------------------------------------------------------
536 # Collect all open circs for the user
537 # For each circ, see if any billings or payments were created
538 # during the given time period.
539 # --------------------------------------------------------------
540 sub fetch_circ_xacts {
544 my $start_date = shift;
545 my $end_date = shift;
549 # at the specified org and each descendent org,
550 # fetch the open circs for this user
551 $U->walk_org_tree( $org,
554 $logger->debug("collect: searching for open circs at " . $n->shortname);
557 $e->search_action_circulation(
571 my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
573 for my $cid (@$active_ids) {
575 $e->retrieve_action_circulation(
581 circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
593 my( $e, $copy ) = @_;
594 return if $copy->price and $copy->price > 0;
595 my $vol = $e->retrieve_asset_call_number($copy->call_number);
596 my $org = ($vol and $vol->id != OILS_PRECAT_CALL_NUMBER)
597 ? $vol->owning_lib : $copy->circ_lib;
598 my $setting = $e->retrieve_actor_org_unit_setting(
599 { org_unit => $org, name => OILS_SETTING_DEF_ITEM_PRICE } );
600 $copy->price($setting->value);
605 sub fetch_grocery_xacts {
609 my $start_date = shift;
610 my $end_date = shift;
613 $U->walk_org_tree( $org,
616 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
619 $e->search_money_grocery(
622 billing_location => $n->id,
632 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
634 for my $id (@$active_ids) {
636 $e->retrieve_money_grocery(
642 mg => [ "billings", "payments", "billing_location" ] }
654 # --------------------------------------------------------------
655 # Given a list of xact id's, this returns a list of id's that
656 # had any activity within the given time span
657 # --------------------------------------------------------------
659 my( $e, $ids, $start_date, $end_date ) = @_;
662 # { payment_ts => { between => [ $start, $end ] } } ' ;)
667 # see if any billings were created in the given time range
668 my $bills = $e->search_money_billing (
671 billing_ts => { between => [ $start_date, $end_date ] },
680 # see if any payments were created in the given range
681 $payments = $e->search_money_payment (
684 payment_ts => { between => [ $start_date, $end_date ] },
691 push( @active, $id ) if @$bills or @$payments;
698 __PACKAGE__->register_method(
699 method => 'create_user_note',
700 api_name => 'open-ils.collections.patron_note.create',
704 desc => q/ Adds a note to a patron's account /,
707 desc => 'The authentication token',
710 { name => 'user_barcode',
711 desc => q/The patron's barcode/,
715 desc => q/The title of the note/,
720 desc => q/The text of the note/,
727 Returns SUCCESS event on success, error event otherwise.
735 sub create_user_note {
736 my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
738 my $e = new_editor(authtoken=>$auth, xact=>1);
739 return $e->event unless $e->checkauth;
740 return $e->event unless $e->allowed('UPDATE_USER');
742 return $e->event unless
743 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
745 my $note = Fieldmapper::actor::usr_note->new;
746 $note->usr($card->usr);
747 $note->title($title);
748 $note->creator($e->requestor->id);
749 $note->create_date('now');
751 $note->value($note_txt);
753 $e->create_actor_usr_note($note) or return $e->event;
755 return OpenILS::Event->new('SUCCESS');