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 OpenILS::Application;
7 use OpenILS::Utils::Fieldmapper;
8 use base 'OpenILS::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',
46 Returns an array of user information objects that the system
47 based on the search criteria provided. If the total fines
48 a user owes reaches or exceeds "fine_level" on or befre "age"
49 and the fines were created at "location", the user will be
50 included in the return set/,
54 desc => 'The authentication token',
58 desc => q/Number of days back to check/,
62 { name => 'fine_level',
63 desc => q/The fine threshold at which users will be included in the search results /,
67 desc => q/The short-name of the orginization unit (library) at which the fines were created.
68 If a selected location has 'child' locations (e.g. a library region), the
69 child locations will be included in the search/,
75 desc => q/An array of user information objects.
76 usr : Array of user information objects containing id, dob, profile, and groups
77 threshold_amount : The total amount the patron owes that is at least as old
78 as the fine "age" and whose transaction was created at the searched location
79 last_pertinent_billing : The time of the last billing that relates to this query
87 groups => [ 'Patron', 'Staff' ],
89 threshold_amount => 99,
96 sub users_of_interest {
97 my( $self, $conn, $auth, $age, $fine_level, $location ) = @_;
99 return OpenILS::Event->new('BAD_PARAMS')
100 unless ($auth and $age and $location);
102 my $e = new_editor(authtoken => $auth);
103 return $e->event unless $e->checkauth;
105 my $org = $e->search_actor_org_unit({shortname => $location})
106 or return $e->event; $org = $org->[0];
108 # they need global perms to view users so no org is provided
109 return $e->event unless $e->allowed('VIEW_USER');
113 my $ses = OpenSRF::AppSession->create('open-ils.storage');
116 my $req = $ses->request(
117 'open-ils.storage.money.collections.users_of_interest',
118 $age, $fine_level, $location);
120 # let the client know we're still here
121 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
123 return process_users_of_interest_results(
124 $self, $conn, $e, $req, $start, $age, $fine_level, $location);
128 sub process_users_of_interest_results {
129 my($self, $conn, $e, $req, $starttime, @params) = @_;
132 while( my $resp = $req->recv(timeout => 7200) ) {
134 return $req->failed if $req->failed;
135 my $hash = $resp->content;
139 $total = time - $starttime;
140 $logger->info("collections: request (@params) took $total seconds");
143 my $u = $e->retrieve_actor_user(
148 flesh_fields => {au => ["groups","profile", "card"]},
151 ) or return $e->event;
156 profile => $u->profile->name,
157 barcode => $u->card->barcode,
158 groups => [ map { $_->name } @{$u->groups} ],
161 $conn->respond($hash);
168 __PACKAGE__->register_method(
169 method => 'users_owing_money',
170 api_name => 'open-ils.collections.users_owing_money.retrieve',
176 Returns an array of users that owe money during
177 the given time frame at the location (or child locations)
182 desc => 'The authentication token',
185 { name => 'start_date',
186 desc => 'The start of the time interval to check',
187 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
190 { name => 'end_date',
191 desc => q/Then end date of the time interval to check/,
192 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
194 { name => 'fine_level',
195 desc => q/The fine threshold at which users will be included in the search results /,
198 { name => 'locations',
199 desc => q/ A list of one or more org-unit short names.
200 If a selected location has 'child' locations (e.g. a library region), the
201 child locations will be included in the search/,
206 desc => q/An array of user information objects/,
213 sub users_owing_money {
214 my( $self, $conn, $auth, $start_date, $end_date, $fine_level, @locations ) = @_;
216 return OpenILS::Event->new('BAD_PARAMS')
217 unless ($auth and $start_date and $end_date and @locations);
219 my $e = new_editor(authtoken => $auth);
220 return $e->event unless $e->checkauth;
222 # they need global perms to view users so no org is provided
223 return $e->event unless $e->allowed('VIEW_USER');
227 my $ses = OpenSRF::AppSession->create('open-ils.storage');
230 my $req = $ses->request(
231 'open-ils.storage.money.collections.users_owing_money',
232 $start_date, $end_date, $fine_level, @locations);
234 # let the client know we're still here
235 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
237 return process_users_of_interest_results(
238 $self, $conn, $e, $req, $start, $start_date, $end_date, $fine_level, @locations);
243 __PACKAGE__->register_method(
244 method => 'users_with_activity',
245 api_name => 'open-ils.collections.users_with_activity.retrieve',
251 Returns an array of users that are already in collections
252 and had any type of billing or payment activity within
253 the given time frame at the location (or child locations)
258 desc => 'The authentication token',
261 { name => 'start_date',
262 desc => 'The start of the time interval to check',
263 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
266 { name => 'end_date',
267 desc => q/Then end date of the time interval to check/,
268 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
270 { name => 'location',
271 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
272 If a selected location has 'child' locations (e.g. a library region), the
273 child locations will be included in the search/,
279 desc => q/An array of user information objects/,
285 sub users_with_activity {
286 my( $self, $conn, $auth, $start_date, $end_date, $location ) = @_;
287 return OpenILS::Event->new('BAD_PARAMS')
288 unless ($auth and $start_date and $end_date and $location);
290 my $e = new_editor(authtoken => $auth);
291 return $e->event unless $e->checkauth;
293 my $org = $e->search_actor_org_unit({shortname => $location})
294 or return $e->event; $org = $org->[0];
295 return $e->event unless $e->allowed('VIEW_USER', $org->id);
297 my $ses = OpenSRF::AppSession->create('open-ils.storage');
300 my $req = $ses->request(
301 'open-ils.storage.money.collections.users_with_activity.atomic',
302 $start_date, $end_date, $location);
304 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
307 while( my $resp = $req->recv(timeout => 7200) ) {
310 $total = time - $start;
311 $logger->info("collections: users_with_activity search ".
312 "($start_date, $end_date, $location) took $total seconds");
315 return $req->failed if $req->failed;
316 $conn->respond($resp->content);
324 __PACKAGE__->register_method(
325 method => 'put_into_collections',
326 api_name => 'open-ils.collections.put_into_collections',
331 Marks a user as being "in collections" at a given location
336 desc => 'The authentication token',
340 desc => 'The id of the user to plact into collections',
344 { name => 'location',
345 desc => q/The short-name of the orginization unit (library)
346 for which the user is being placed in collections/,
349 { name => 'fee_amount',
351 The amount of money that a patron should be fined.
352 If this field is empty, no fine is created.
356 { name => 'fee_note',
358 Custom note that is added to the the billing.
359 This field is not required.
360 Note: fee_note is not the billing_type. Billing_type type is
361 decided by the system. (e.g. "fee for collections").
362 fee_note is purely used for any additional needed information
363 and is only visible to staff.
370 desc => q/A SUCCESS event on success, error event on failure/,
375 sub put_into_collections {
376 my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
378 return OpenILS::Event->new('BAD_PARAMS')
379 unless ($auth and $user_id and $location);
381 my $e = new_editor(authtoken => $auth, xact =>1);
382 return $e->event unless $e->checkauth;
384 my $org = $e->search_actor_org_unit({shortname => $location});
385 return $e->event unless $org = $org->[0];
386 return $e->event unless $e->allowed('money.collections_tracker.create', $org->id);
388 my $existing = $e->search_money_collections_tracker(
390 location => $org->id,
392 collector => $e->requestor->id
397 return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
399 $logger->info("collect: user ".$e->requestor->id.
400 " putting user $user_id into collections for $location");
402 my $tracker = Fieldmapper::money::collections_tracker->new;
404 $tracker->usr($user_id);
405 $tracker->collector($e->requestor->id);
406 $tracker->location($org->id);
407 $tracker->enter_time('now');
409 $e->create_money_collections_tracker($tracker)
413 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
418 return OpenILS::Event->new('SUCCESS');
421 sub add_collections_fee {
422 my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
426 $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
428 my $xact = Fieldmapper::money::grocery->new;
429 $xact->usr($patron_id);
430 $xact->xact_start('now');
431 $xact->billing_location($org->id);
433 $xact = $e->create_money_grocery($xact) or return $e->event;
435 my $bill = Fieldmapper::money::billing->new;
436 $bill->note($fee_note);
437 $bill->xact($xact->id);
438 $bill->billing_type(OILS_BILLING_TYPE_COLLECTION_FEE);
439 $bill->amount($fee_amount);
441 $e->create_money_billing($bill) or return $e->event;
448 __PACKAGE__->register_method(
449 method => 'remove_from_collections',
450 api_name => 'open-ils.collections.remove_from_collections',
452 Returns the users that are currently in collections and
453 had activity during the provided interval. Dates are inclusive.
454 @param start_date The beginning of the activity interval
455 @param end_date The end of the activity interval
456 @param location The location at which the fines were created
461 __PACKAGE__->register_method(
462 method => 'remove_from_collections',
463 api_name => 'open-ils.collections.remove_from_collections',
468 Removes a user from the collections table for the given location
473 desc => 'The authentication token',
477 desc => 'The id of the user to plact into collections',
481 { name => 'location',
482 desc => q/The short-name of the orginization unit (library)
483 for which the user is being removed from collections/,
489 desc => q/A SUCCESS event on success, error event on failure/,
495 sub remove_from_collections {
496 my( $self, $conn, $auth, $user_id, $location ) = @_;
498 return OpenILS::Event->new('BAD_PARAMS')
499 unless ($auth and $user_id and $location);
501 my $e = new_editor(authtoken => $auth, xact=>1);
502 return $e->event unless $e->checkauth;
504 my $org = $e->search_actor_org_unit({shortname => $location})
505 or return $e->event; $org = $org->[0];
506 return $e->event unless $e->allowed('money.collections_tracker.delete', $org->id);
508 my $tracker = $e->search_money_collections_tracker(
509 { usr => $user_id, location => $org->id })
512 $e->delete_money_collections_tracker($tracker->[0])
516 return OpenILS::Event->new('SUCCESS');
520 #__PACKAGE__->register_method(
521 # method => 'transaction_details',
522 # api_name => 'open-ils.collections.user_transaction_details.retrieve',
528 __PACKAGE__->register_method(
529 method => 'transaction_details',
530 api_name => 'open-ils.collections.user_transaction_details.retrieve',
535 Returns a list of fleshed user objects with transaction details
540 desc => 'The authentication token',
543 { name => 'start_date',
544 desc => 'The start of the time interval to check',
545 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
548 { name => 'end_date',
549 desc => q/Then end date of the time interval to check/,
550 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
552 { name => 'location',
553 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
554 If a selected location has 'child' locations (e.g. a library region), the
555 child locations will be included in the search/,
560 desc => 'An array of user ids',
566 desc => q/A list of objects. Object keys include:
568 transactions : An object with keys :
569 circulations : Fleshed circulation objects
570 grocery : Fleshed 'grocery' transaction objects
577 sub transaction_details {
578 my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
580 return OpenILS::Event->new('BAD_PARAMS')
581 unless ($auth and $start_date and $end_date and $location and $user_list);
583 my $e = new_editor(authtoken => $auth);
584 return $e->event unless $e->checkauth;
586 # they need global perms to view users so no org is provided
587 return $e->event unless $e->allowed('VIEW_USER');
589 my $org = $e->search_actor_org_unit({shortname => $location})
590 or return $e->event; $org = $org->[0];
592 # get a reference to the org inside of the tree
593 $org = $U->find_org($U->fetch_org_tree(), $org->id);
596 for my $uid (@$user_list) {
599 $blob->{usr} = $e->retrieve_actor_user(
608 "standing_penalties",
619 $blob->{transactions} = {
621 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
623 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date)
626 # for each transaction, flesh the workstatoin on any attached payment
627 # and make the payment object a real object (e.g. cash payment),
628 # not just a generic payment object
630 @{$blob->{transactions}->{circulations}},
631 @{$blob->{transactions}->{grocery}} ) {
634 if( $ps = $xact->payments and @$ps ) {
635 my @fleshed; my $evt;
637 ($p, $evt) = flesh_payment($e,$p);
641 $xact->payments(\@fleshed);
645 push( @data, $blob );
654 my $type = $p->payment_type;
655 $logger->debug("collect: fleshing workstation on payment $type : ".$p->id);
656 my $meth = "retrieve_money_$type";
657 $p = $e->$meth($p->id) or return (undef, $e->event);
659 $p->payment_type($type);
661 $e->retrieve_actor_workstation(
666 flesh_fields => { aws => [ 'owning_lib' ] }
671 } catch Error with {};
676 # --------------------------------------------------------------
677 # Collect all open circs for the user
678 # For each circ, see if any billings or payments were created
679 # during the given time period.
680 # --------------------------------------------------------------
681 sub fetch_circ_xacts {
685 my $start_date = shift;
686 my $end_date = shift;
690 # at the specified org and each descendent org,
691 # fetch the open circs for this user
692 $U->walk_org_tree( $org,
695 $logger->debug("collect: searching for open circs at " . $n->shortname);
698 $e->search_action_circulation(
712 my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
714 for my $cid (@$active_ids) {
716 $e->retrieve_action_circulation(
722 circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
734 my( $e, $copy ) = @_;
735 return if $copy->price and $copy->price > 0;
736 my $vol = $e->retrieve_asset_call_number($copy->call_number);
737 my $org = ($vol and $vol->id != OILS_PRECAT_CALL_NUMBER)
738 ? $vol->owning_lib : $copy->circ_lib;
739 my $setting = $e->retrieve_actor_org_unit_setting(
740 { org_unit => $org, name => OILS_SETTING_DEF_ITEM_PRICE } );
741 $copy->price($setting->value);
746 sub fetch_grocery_xacts {
750 my $start_date = shift;
751 my $end_date = shift;
754 $U->walk_org_tree( $org,
757 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
760 $e->search_money_grocery(
763 billing_location => $n->id,
773 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
775 for my $id (@$active_ids) {
777 $e->retrieve_money_grocery(
783 mg => [ "billings", "payments", "billing_location" ] }
795 # --------------------------------------------------------------
796 # Given a list of xact id's, this returns a list of id's that
797 # had any activity within the given time span
798 # --------------------------------------------------------------
800 my( $e, $ids, $start_date, $end_date ) = @_;
803 # { payment_ts => { between => [ $start, $end ] } } ' ;)
808 # see if any billings were created in the given time range
809 my $bills = $e->search_money_billing (
812 billing_ts => { between => [ $start_date, $end_date ] },
821 # see if any payments were created in the given range
822 $payments = $e->search_money_payment (
825 payment_ts => { between => [ $start_date, $end_date ] },
832 push( @active, $id ) if @$bills or @$payments;
839 __PACKAGE__->register_method(
840 method => 'create_user_note',
841 api_name => 'open-ils.collections.patron_note.create',
845 desc => q/ Adds a note to a patron's account /,
848 desc => 'The authentication token',
851 { name => 'user_barcode',
852 desc => q/The patron's barcode/,
856 desc => q/The title of the note/,
861 desc => q/The text of the note/,
868 Returns SUCCESS event on success, error event otherwise.
876 sub create_user_note {
877 my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
879 my $e = new_editor(authtoken=>$auth, xact=>1);
880 return $e->event unless $e->checkauth;
881 return $e->event unless $e->allowed('UPDATE_USER'); # XXX Makre more specific perm for this
883 return $e->event unless
884 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
886 my $note = Fieldmapper::actor::usr_note->new;
887 $note->usr($card->usr);
888 $note->title($title);
889 $note->creator($e->requestor->id);
890 $note->create_date('now');
892 $note->value($note_txt);
894 $e->create_actor_usr_note($note) or return $e->event;
896 return OpenILS::Event->new('SUCCESS');