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; }
20 __PACKAGE__->register_method(
21 method => 'user_from_bc',
22 api_name => 'open-ils.collections.user_id_from_barcode',
26 my( $self, $conn, $auth, $bc ) = @_;
27 my $e = new_editor(authtoken=>$auth);
28 return $e->event unless $e->checkauth;
29 return $e->event unless $e->allowed('VIEW_USER');
30 my $card = $e->search_actor_card({barcode=>$bc})->[0]
32 my $user = $e->retrieve_actor_user($card->usr)
38 __PACKAGE__->register_method(
39 method => 'users_of_interest',
40 api_name => 'open-ils.collections.users_of_interest.retrieve',
45 Returns an array of user information objects that the system
46 based on the search criteria provided. If the total fines
47 a user owes reaches or exceeds "fine_level" on or befre "age"
48 and the fines were created at "location", the user will be
49 included in the return set/,
53 desc => 'The authentication token',
57 desc => q/The date before or at which the user's fine level exceeded the fine_level param/,
58 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
61 { name => 'fine_level',
62 desc => q/The fine threshold at which users will be included in the search results /,
63 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
66 desc => q/The short-name of the orginization unit (library) at which the fines were created.
67 If a selected location has 'child' locations (e.g. a library region), the
68 child locations will be included in the search/,
74 desc => q/An array of user information objects.
75 usr : Array of user information objects containing id, dob, profile, and groups
76 threshold_amount : The total amount the patron owes that is at least as old
77 as the fine "age" and whose transaction was created at the searched location
78 last_pertinent_billing : The time of the last billing that relates to this query
86 groups => [ 'Patron', 'Staff' ],
88 threshold_amount => 99,
95 sub users_of_interest {
96 my( $self, $conn, $auth, $age, $fine_level, $location ) = @_;
98 return OpenILS::Event->new('BAD_PARAMS')
99 unless ($auth and $age and $fine_level and $location);
101 my $e = new_editor(authtoken => $auth);
102 return $e->event unless $e->checkauth;
104 my $org = $e->search_actor_org_unit({shortname => $location})
105 or return $e->event; $org = $org->[0];
107 # they need global perms to view users so no org is provided
108 return $e->event unless $e->allowed('VIEW_USER');
110 my $data = $U->storagereq(
111 'open-ils.storage.money.collections.users_of_interest.atomic',
112 $age, $fine_level, $location);
114 return [] unless $data and @$data;
117 my $u = $e->retrieve_actor_user(
122 flesh_fields => {au => ["groups","profile", "card"]},
123 select => {au => ["profile","id","dob", "card"]}
126 ) or return $e->event;
131 profile => $u->profile->name,
132 barcode => $u->card->barcode,
133 groups => [ map { $_->name } @{$u->groups} ],
141 __PACKAGE__->register_method(
142 method => 'users_with_activity',
143 api_name => 'open-ils.collections.users_with_activity.retrieve',
148 Returns an array of users that are already in collections
149 and had any type of billing or payment activity within
150 the given time frame at the location (or child locations)
155 desc => 'The authentication token',
158 { name => 'start_date',
159 desc => 'The start of the time interval to check',
160 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
163 { name => 'end_date',
164 desc => q/Then end date of the time interval to check/,
165 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
167 { name => 'location',
168 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
169 If a selected location has 'child' locations (e.g. a library region), the
170 child locations will be included in the search/,
176 desc => q/An array of user information objects/,
182 sub users_with_activity {
183 my( $self, $conn, $auth, $start_date, $end_date, $location ) = @_;
184 return OpenILS::Event->new('BAD_PARAMS')
185 unless ($auth and $start_date and $end_date and $location);
187 my $e = new_editor(authtoken => $auth);
188 return $e->event unless $e->checkauth;
190 my $org = $e->search_actor_org_unit({shortname => $location})
191 or return $e->event; $org = $org->[0];
192 return $e->event unless $e->allowed('VIEW_USER', $org->id);
194 return $U->storagereq(
195 'open-ils.storage.money.collections.users_with_activity.atomic',
196 $start_date, $end_date, $location);
201 __PACKAGE__->register_method(
202 method => 'put_into_collections',
203 api_name => 'open-ils.collections.put_into_collections',
208 Marks a user as being "in collections" at a given location
213 desc => 'The authentication token',
217 desc => 'The id of the user to plact into collections',
221 { name => 'location',
222 desc => q/The short-name of the orginization unit (library)
223 for which the user is being placed in collections/,
226 { name => 'fee_amount',
228 The amount of money that a patron should be fined.
229 If this field is empty, no fine is created.
233 { name => 'fee_note',
235 Custom note that is added to the the billing.
236 This field is not required.
237 Note: fee_note is not the billing_type. Billing_type type is
238 decided by the system. (e.g. "fee for collections").
239 fee_note is purely used for any additional needed information
240 and is only visible to staff.
247 desc => q/A SUCCESS event on success, error event on failure/,
252 sub put_into_collections {
253 my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
255 return OpenILS::Event->new('BAD_PARAMS')
256 unless ($auth and $user_id and $location);
258 my $e = new_editor(authtoken => $auth, xact =>1);
259 return $e->event unless $e->checkauth;
261 my $org = $e->search_actor_org_unit({shortname => $location});
262 return $e->event unless $org = $org->[0];
263 return $e->event unless $e->allowed('money.collections_tracker.create', $org->id);
265 my $existing = $e->search_money_collections_tracker(
267 location => $org->id,
269 collector => $e->requestor->id
274 return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
276 $logger->info("collect: user ".$e->requestor->id.
277 " putting user $user_id into collections for $location");
279 my $tracker = Fieldmapper::money::collections_tracker->new;
281 $tracker->usr($user_id);
282 $tracker->collector($e->requestor->id);
283 $tracker->location($org->id);
284 $tracker->enter_time('now');
286 $e->create_money_collections_tracker($tracker)
290 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
295 return OpenILS::Event->new('SUCCESS');
298 sub add_collections_fee {
299 my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
303 $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
305 my $xact = Fieldmapper::money::grocery->new;
306 $xact->usr($patron_id);
307 $xact->xact_start('now');
308 $xact->billing_location($org->id);
310 $xact = $e->create_money_grocery($xact) or return $e->event;
312 my $bill = Fieldmapper::money::billing->new;
313 $bill->note($fee_note);
314 $bill->xact($xact->id);
315 $bill->billing_type('SYSTEM: Long Overdue Processing Fee'); # XXX
316 $bill->amount($fee_amount);
318 $e->create_money_billing($bill) or return $e->event;
325 __PACKAGE__->register_method(
326 method => 'remove_from_collections',
327 api_name => 'open-ils.collections.remove_from_collections',
329 Returns the users that are currently in collections and
330 had activity during the provided interval. Dates are inclusive.
331 @param start_date The beginning of the activity interval
332 @param end_date The end of the activity interval
333 @param location The location at which the fines were created
338 __PACKAGE__->register_method(
339 method => 'remove_from_collections',
340 api_name => 'open-ils.collections.remove_from_collections',
345 Removes a user from the collections table for the given location
350 desc => 'The authentication token',
354 desc => 'The id of the user to plact into collections',
358 { name => 'location',
359 desc => q/The short-name of the orginization unit (library)
360 for which the user is being removed from collections/,
366 desc => q/A SUCCESS event on success, error event on failure/,
372 sub remove_from_collections {
373 my( $self, $conn, $auth, $user_id, $location ) = @_;
375 return OpenILS::Event->new('BAD_PARAMS')
376 unless ($auth and $user_id and $location);
378 my $e = new_editor(authtoken => $auth, xact=>1);
379 return $e->event unless $e->checkauth;
381 my $org = $e->search_actor_org_unit({shortname => $location})
382 or return $e->event; $org = $org->[0];
383 return $e->event unless $e->allowed('money.collections_tracker.delete', $org->id);
385 my $tracker = $e->search_money_collections_tracker(
386 { usr => $user_id, location => $org->id })
389 $e->delete_money_collections_tracker($tracker->[0])
393 return OpenILS::Event->new('SUCCESS');
397 #__PACKAGE__->register_method(
398 # method => 'transaction_details',
399 # api_name => 'open-ils.collections.user_transaction_details.retrieve',
405 __PACKAGE__->register_method(
406 method => 'transaction_details',
407 api_name => 'open-ils.collections.user_transaction_details.retrieve',
412 Returns a list of fleshed user objects with transaction details
417 desc => 'The authentication token',
420 { name => 'start_date',
421 desc => 'The start of the time interval to check',
422 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
425 { name => 'end_date',
426 desc => q/Then end date of the time interval to check/,
427 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
429 { name => 'location',
430 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
431 If a selected location has 'child' locations (e.g. a library region), the
432 child locations will be included in the search/,
437 desc => 'An array of user ids',
443 desc => q/A list of objects. Object keys include:
445 transactions : An object with keys :
446 circulations : Fleshed circulation objects
447 grocery : Fleshed 'grocery' transaction objects
454 sub transaction_details {
455 my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
457 return OpenILS::Event->new('BAD_PARAMS')
458 unless ($auth and $start_date and $end_date and $location and $user_list);
460 my $e = new_editor(authtoken => $auth);
461 return $e->event unless $e->checkauth;
463 # they need global perms to view users so no org is provided
464 return $e->event unless $e->allowed('VIEW_USER');
466 my $org = $e->search_actor_org_unit({shortname => $location})
467 or return $e->event; $org = $org->[0];
469 # get a reference to the org inside of the tree
470 $org = $U->find_org($U->fetch_org_tree(), $org->id);
473 for my $uid (@$user_list) {
476 $blob->{usr} = $e->retrieve_actor_user(
485 "standing_penalties",
496 $blob->{transactions} = {
498 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
500 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date)
503 # for each transaction, flesh the workstatoin on any attached payment
504 # and make the payment object a real object (e.g. cash payment),
505 # not just a generic payment object
507 @{$blob->{transactions}->{circulations}},
508 @{$blob->{transactions}->{grocery}} ) {
511 if( $ps = $xact->payments and @$ps ) {
512 my @fleshed; my $evt;
514 ($p, $evt) = flesh_payment($e,$p);
518 $xact->payments(\@fleshed);
522 push( @data, $blob );
531 my $type = $p->payment_type;
532 $logger->debug("collect: fleshing workstation on payment $type : ".$p->id);
533 my $meth = "retrieve_money_$type";
534 $p = $e->$meth($p->id) or return (undef, $e->event);
536 $p->payment_type($type);
538 $e->retrieve_actor_workstation(
543 flesh_fields => { aws => [ 'owning_lib' ] }
548 } catch Error with {};
553 # --------------------------------------------------------------
554 # Collect all open circs for the user
555 # For each circ, see if any billings or payments were created
556 # during the given time period.
557 # --------------------------------------------------------------
558 sub fetch_circ_xacts {
562 my $start_date = shift;
563 my $end_date = shift;
567 # at the specified org and each descendent org,
568 # fetch the open circs for this user
569 $U->walk_org_tree( $org,
572 $logger->debug("collect: searching for open circs at " . $n->shortname);
575 $e->search_action_circulation(
589 my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
591 for my $cid (@$active_ids) {
593 $e->retrieve_action_circulation(
599 circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
611 my( $e, $copy ) = @_;
612 return if $copy->price and $copy->price > 0;
613 my $vol = $e->retrieve_asset_call_number($copy->call_number);
614 my $org = ($vol and $vol->id != OILS_PRECAT_CALL_NUMBER)
615 ? $vol->owning_lib : $copy->circ_lib;
616 my $setting = $e->retrieve_actor_org_unit_setting(
617 { org_unit => $org, name => OILS_SETTING_DEF_ITEM_PRICE } );
618 $copy->price($setting->value);
623 sub fetch_grocery_xacts {
627 my $start_date = shift;
628 my $end_date = shift;
631 $U->walk_org_tree( $org,
634 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
637 $e->search_money_grocery(
640 billing_location => $n->id,
650 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
652 for my $id (@$active_ids) {
654 $e->retrieve_money_grocery(
660 mg => [ "billings", "payments", "billing_location" ] }
672 # --------------------------------------------------------------
673 # Given a list of xact id's, this returns a list of id's that
674 # had any activity within the given time span
675 # --------------------------------------------------------------
677 my( $e, $ids, $start_date, $end_date ) = @_;
680 # { payment_ts => { between => [ $start, $end ] } } ' ;)
685 # see if any billings were created in the given time range
686 my $bills = $e->search_money_billing (
689 billing_ts => { between => [ $start_date, $end_date ] },
698 # see if any payments were created in the given range
699 $payments = $e->search_money_payment (
702 payment_ts => { between => [ $start_date, $end_date ] },
709 push( @active, $id ) if @$bills or @$payments;
716 __PACKAGE__->register_method(
717 method => 'create_user_note',
718 api_name => 'open-ils.collections.patron_note.create',
722 desc => q/ Adds a note to a patron's account /,
725 desc => 'The authentication token',
728 { name => 'user_barcode',
729 desc => q/The patron's barcode/,
733 desc => q/The title of the note/,
738 desc => q/The text of the note/,
745 Returns SUCCESS event on success, error event otherwise.
753 sub create_user_note {
754 my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
756 my $e = new_editor(authtoken=>$auth, xact=>1);
757 return $e->event unless $e->checkauth;
758 return $e->event unless $e->allowed('UPDATE_USER');
760 return $e->event unless
761 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
763 my $note = Fieldmapper::actor::usr_note->new;
764 $note->usr($card->usr);
765 $note->title($title);
766 $note->creator($e->requestor->id);
767 $note->create_date('now');
769 $note->value($note_txt);
771 $e->create_actor_usr_note($note) or return $e->event;
773 return OpenILS::Event->new('SUCCESS');