]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Collections.pm
3e8fb678fcb9ab97fc7df459fac46a24be8d3cd1
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Collections.pm
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/;
10 use OpenILS::Event;
11 use OpenILS::Const qw/:const/;
12 my $U = "OpenILS::Application::AppUtils";
13
14
15 # --------------------------------------------------------------
16 # Loads the config info
17 # --------------------------------------------------------------
18 sub initialize { return 1; }
19
20 __PACKAGE__->register_method(
21         method => 'user_from_bc',
22         api_name => 'open-ils.collections.user_id_from_barcode',
23 );
24
25 sub user_from_bc {
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]
31                 or return $e->event;
32         my $user = $e->retrieve_actor_user($card->usr)
33                 or return $e->event;
34         return $user->id;       
35 }
36
37
38 __PACKAGE__->register_method(
39         method    => 'users_of_interest',
40         api_name  => 'open-ils.collections.users_of_interest.retrieve',
41         api_level => 1,
42         argc      => 4,
43         signature => { 
44                 desc     => q/
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/,
50                             
51                 params   => [
52                         {       name => 'auth',
53                                 desc => 'The authentication token',
54                                 type => 'string' },
55
56                         {       name => 'age',
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 /,
59                         },
60
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 /,
64                         },
65                         {       name => 'location',
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/,
69                                 type => q/string/,
70                         },
71                 ],
72
73                 'return' => { 
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
79                                                 /,
80                         type            => 'array',
81                         example => {
82                                 usr     => {
83                                         id                      => 'id',
84                                         dob             => '1970-01-01',
85                                         profile => 'Patron',
86                                         groups  => [ 'Patron', 'Staff' ],
87                                 },
88                                 threshold_amount => 99,
89                         }
90                 }
91         }
92 );
93
94
95 sub users_of_interest {
96         my( $self, $conn, $auth, $age, $fine_level, $location ) = @_;
97
98         return OpenILS::Event->new('BAD_PARAMS') 
99                 unless ($auth and $age and $fine_level and $location);
100
101         my $e = new_editor(authtoken => $auth);
102         return $e->event unless $e->checkauth;
103
104         my $org = $e->search_actor_org_unit({shortname => $location})
105                 or return $e->event; $org = $org->[0];
106
107         # they need global perms to view users so no org is provided
108         return $e->event unless $e->allowed('VIEW_USER'); 
109
110         my $data = $U->storagereq(
111                 'open-ils.storage.money.collections.users_of_interest.atomic', 
112                 $age, $fine_level, $location);
113
114         return [] unless $data and @$data;
115
116         for (@$data) {
117                 my $u = $e->retrieve_actor_user(
118                         [
119                                 $_->{usr},
120                                 {
121                                         flesh                           => 1,
122                                         flesh_fields    => {au => ["groups","profile", "card"]},
123                                         select                  => {au => ["profile","id","dob", "card"]}
124                                 }
125                         ]
126                 ) or return $e->event;
127
128                 $_->{usr} = {
129                         id                      => $u->id,
130                         dob             => $u->dob,
131                         profile => $u->profile->name,
132                         barcode => $u->card->barcode,
133                         groups  => [ map { $_->name } @{$u->groups} ],
134                 };
135         }
136
137         return $data;
138 }
139
140
141 __PACKAGE__->register_method(
142         method    => 'users_with_activity',
143         api_name  => 'open-ils.collections.users_with_activity.retrieve',
144         api_level => 1,
145         argc      => 4,
146         signature => { 
147                 desc     => q/
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)
151                         provided/,
152                             
153                 params   => [
154                         {       name => 'auth',
155                                 desc => 'The authentication token',
156                                 type => 'string' },
157
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 /,
161                         },
162
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 /,
166                         },
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/,
171                                 type => q'string',
172                         },
173                 ],
174
175                 'return' => { 
176                         desc            => q/An array of user information objects/,
177                         type            => 'array',
178                 }
179         }
180 );
181
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);
186
187         my $e = new_editor(authtoken => $auth);
188         return $e->event unless $e->checkauth;
189
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);
193
194         return $U->storagereq(
195                 'open-ils.storage.money.collections.users_with_activity.atomic', 
196                 $start_date, $end_date, $location);
197 }
198
199
200
201 __PACKAGE__->register_method(
202         method    => 'put_into_collections',
203         api_name  => 'open-ils.collections.put_into_collections',
204         api_level => 1,
205         argc      => 3,
206         signature => { 
207                 desc     => q/
208                         Marks a user as being "in collections" at a given location
209                         /,
210                             
211                 params   => [
212                         {       name => 'auth',
213                                 desc => 'The authentication token',
214                                 type => 'string' },
215
216                         {       name => 'user_id',
217                                 desc => 'The id of the user to plact into collections',
218                                 type => 'number',
219                         },
220
221                         {       name => 'location',
222                                 desc => q/The short-name of the orginization unit (library) 
223                                         for which the user is being placed in collections/,
224                                 type => q'string',
225                         },
226                         {       name => 'fee_amount',
227                                 desc => q/
228                                         The amount of money that a patron should be fined.  
229                                         If this field is empty, no fine is created.
230                                 /,
231                                 type => 'string',
232                         },
233                         {       name => 'fee_note',
234                                 desc => q/
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.
241                                 /,
242                                 type => 'string',
243                         },
244                 ],
245
246                 'return' => { 
247                         desc            => q/A SUCCESS event on success, error event on failure/,
248                         type            => 'object',
249                 }
250         }
251 );
252 sub put_into_collections {
253         my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
254
255         return OpenILS::Event->new('BAD_PARAMS') 
256                 unless ($auth and $user_id and $location);
257
258         my $e = new_editor(authtoken => $auth, xact =>1);
259         return $e->event unless $e->checkauth;
260
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);
264
265         my $existing = $e->search_money_collections_tracker(
266                 {
267                         location                => $org->id,
268                         usr                     => $user_id,
269                         collector       => $e->requestor->id
270                 },
271                 {idlist => 1}
272         );
273
274         return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
275
276         $logger->info("collect: user ".$e->requestor->id. 
277                 " putting user $user_id into collections for $location");
278
279         my $tracker = Fieldmapper::money::collections_tracker->new;
280
281         $tracker->usr($user_id);
282         $tracker->collector($e->requestor->id);
283         $tracker->location($org->id);
284         $tracker->enter_time('now');
285
286         $e->create_money_collections_tracker($tracker) 
287                 or return $e->event;
288
289         if( $fee_amount ) {
290                 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
291                 return $evt if $evt;
292         }
293
294         $e->commit;
295         return OpenILS::Event->new('SUCCESS');
296 }
297
298 sub add_collections_fee {
299         my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
300
301         $fee_note ||= "";
302
303         $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
304
305         my $xact = Fieldmapper::money::grocery->new;
306         $xact->usr($patron_id);
307         $xact->xact_start('now');
308         $xact->billing_location($org->id);
309
310         $xact = $e->create_money_grocery($xact) or return $e->event;
311
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);
317
318         $e->create_money_billing($bill) or return $e->event;
319         return undef;
320 }
321
322
323
324
325 __PACKAGE__->register_method(
326         method          => 'remove_from_collections',
327         api_name                => 'open-ils.collections.remove_from_collections',
328         signature       => q/
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
334         /
335 );
336
337
338 __PACKAGE__->register_method(
339         method    => 'remove_from_collections',
340         api_name  => 'open-ils.collections.remove_from_collections',
341         api_level => 1,
342         argc      => 3,
343         signature => { 
344                 desc     => q/
345                         Removes a user from the collections table for the given location
346                         /,
347                             
348                 params   => [
349                         {       name => 'auth',
350                                 desc => 'The authentication token',
351                                 type => 'string' },
352
353                         {       name => 'user_id',
354                                 desc => 'The id of the user to plact into collections',
355                                 type => 'number',
356                         },
357
358                         {       name => 'location',
359                                 desc => q/The short-name of the orginization unit (library) 
360                                         for which the user is being removed from collections/,
361                                 type => q'string',
362                         },
363                 ],
364
365                 'return' => { 
366                         desc            => q/A SUCCESS event on success, error event on failure/,
367                         type            => 'object',
368                 }
369         }
370 );
371
372 sub remove_from_collections {
373         my( $self, $conn, $auth, $user_id, $location ) = @_;
374
375         return OpenILS::Event->new('BAD_PARAMS') 
376                 unless ($auth and $user_id and $location);
377
378         my $e = new_editor(authtoken => $auth, xact=>1);
379         return $e->event unless $e->checkauth;
380
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);
384
385         my $tracker = $e->search_money_collections_tracker(
386                 { usr => $user_id, location => $org->id })
387                 or return $e->event;
388
389         $e->delete_money_collections_tracker($tracker->[0])
390                 or return $e->event;
391
392         $e->commit;
393         return OpenILS::Event->new('SUCCESS');
394 }
395
396
397 #__PACKAGE__->register_method(
398 #       method          => 'transaction_details',
399 #       api_name                => 'open-ils.collections.user_transaction_details.retrieve',
400 #       signature       => q/
401 #       /
402 #);
403
404
405 __PACKAGE__->register_method(
406         method    => 'transaction_details',
407         api_name  => 'open-ils.collections.user_transaction_details.retrieve',
408         api_level => 1,
409         argc      => 5,
410         signature => { 
411                 desc     => q/
412                         Returns a list of fleshed user objects with transaction details
413                         /,
414                             
415                 params   => [
416                         {       name => 'auth',
417                                 desc => 'The authentication token',
418                                 type => 'string' },
419
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 /,
423                         },
424
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 /,
428                         },
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/,
433                                 type => q'string',
434                         },
435                         {
436                                 name => 'user_list',
437                                 desc => 'An array of user ids',
438                                 type => 'array',
439                         },
440                 ],
441
442                 'return' => { 
443                         desc            => q/A list of objects.  Object keys include:
444                                 usr :
445                                 transactions : An object with keys :
446                                         circulations : Fleshed circulation objects
447                                         grocery : Fleshed 'grocery' transaction objects
448                                 /,
449                         type            => 'object'
450                 }
451         }
452 );
453
454 sub transaction_details {
455         my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
456
457         return OpenILS::Event->new('BAD_PARAMS') 
458                 unless ($auth and $start_date and $end_date and $location and $user_list);
459
460         my $e = new_editor(authtoken => $auth);
461         return $e->event unless $e->checkauth;
462
463         # they need global perms to view users so no org is provided
464         return $e->event unless $e->allowed('VIEW_USER'); 
465
466         my $org = $e->search_actor_org_unit({shortname => $location})
467                 or return $e->event; $org = $org->[0];
468
469         # get a reference to the org inside of the tree
470         $org = $U->find_org($U->fetch_org_tree(), $org->id);
471
472         my @data;
473         for my $uid (@$user_list) {
474                 my $blob = {};
475
476                 $blob->{usr} = $e->retrieve_actor_user(
477                         [
478                                 $uid,
479                 {
480                 "flesh"        => 1,
481                 "flesh_fields" =>  {
482                 "au" => [
483                         "cards",
484                         "card",
485                         "standing_penalties",
486                         "addresses",
487                         "billing_address",
488                         "mailing_address",
489                         "stat_cat_entries"
490                 ]
491                 }
492                 }
493                         ]
494                 );
495
496                 $blob->{transactions} = {
497                         circulations    => 
498                                 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
499                         grocery                 => 
500                                 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date)
501                 };
502
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
506                 for my $xact ( 
507                         @{$blob->{transactions}->{circulations}}, 
508                         @{$blob->{transactions}->{grocery}} ) {
509
510                         my $ps;
511                         if( $ps = $xact->payments and @$ps ) {
512                                 my @fleshed; my $evt;
513                                 for my $p (@$ps) {
514                                         ($p, $evt) = flesh_payment($e,$p);
515                                         return $evt if $evt;
516                                         push(@fleshed, $p);
517                                 }
518                                 $xact->payments(\@fleshed);
519                         }
520                 }
521
522                 push( @data, $blob );
523         }
524
525         return \@data;
526 }
527
528 sub flesh_payment {
529         my $e = shift;
530         my $p = shift;
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);
535         try {
536                 $p->cash_drawer(
537                         $e->retrieve_actor_workstation(
538                                 [
539                                         $p->cash_drawer,
540                                         {
541                                                 flesh => 1,
542                                                 flesh_fields => { aws => [ 'owning_lib' ] }
543                                         }
544                                 ]
545                         )
546                 );
547         } catch Error with {};
548         return ($p);
549 }
550
551
552 # --------------------------------------------------------------
553 # Collect all open circs for the user 
554 # For each circ, see if any billings or payments were created
555 # during the given time period.  
556 # --------------------------------------------------------------
557 sub fetch_circ_xacts {
558         my $e                           = shift;
559         my $uid                 = shift;
560         my $org                 = shift;
561         my $start_date = shift;
562         my $end_date    = shift;
563
564         my @circs;
565
566         # at the specified org and each descendent org, 
567         # fetch the open circs for this user
568         $U->walk_org_tree( $org, 
569                 sub {
570                         my $n = shift;
571                         $logger->debug("collect: searching for open circs at " . $n->shortname);
572                         push( @circs, 
573                                 @{
574                                         $e->search_action_circulation(
575                                                 {
576                                                         usr                     => $uid, 
577                                                         circ_lib                => $n->id,
578                                                 }, 
579                                                 {idlist => 1}
580                                         )
581                                 }
582                         );
583                 }
584         );
585
586
587         my @data;
588         my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
589
590         for my $cid (@$active_ids) {
591                 push( @data, 
592                         $e->retrieve_action_circulation(
593                                 [
594                                         $cid,
595                                         {
596                                                 flesh => 1,
597                                                 flesh_fields => { 
598                                                         circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
599                                                 }
600                                         }
601                                 ]
602                         )
603                 );
604         }
605
606         return \@data;
607 }
608
609 sub set_copy_price {
610         my( $e, $copy ) = @_;
611         return if $copy->price and $copy->price > 0;
612         my $vol = $e->retrieve_asset_call_number($copy->call_number);
613         my $org = ($vol and $vol->id != OILS_PRECAT_CALL_NUMBER) 
614                 ? $vol->owning_lib : $copy->circ_lib;
615         my $setting = $e->retrieve_actor_org_unit_setting(
616                 { org_unit => $org, name => OILS_SETTING_DEF_ITEM_PRICE } );
617         $copy->price($setting->value);
618 }
619
620
621
622 sub fetch_grocery_xacts {
623         my $e                           = shift;
624         my $uid                 = shift;
625         my $org                 = shift;
626         my $start_date = shift;
627         my $end_date    = shift;
628
629         my @xacts;
630         $U->walk_org_tree( $org, 
631                 sub {
632                         my $n = shift;
633                         $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
634                         push( @xacts, 
635                                 @{
636                                         $e->search_money_grocery(
637                                                 {
638                                                         usr                                     => $uid, 
639                                                         billing_location        => $n->id,
640                                                 }, 
641                                                 {idlist => 1}
642                                         )
643                                 }
644                         );
645                 }
646         );
647
648         my @data;
649         my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
650
651         for my $id (@$active_ids) {
652                 push( @data, 
653                         $e->retrieve_money_grocery(
654                                 [
655                                         $id,
656                                         {
657                                                 flesh => 1,
658                                                 flesh_fields => { 
659                                                         mg => [ "billings", "payments", "billing_location" ] }
660                                         }
661                                 ]
662                         )
663                 );
664         }
665
666         return \@data;
667 }
668
669
670
671 # --------------------------------------------------------------
672 # Given a list of xact id's, this returns a list of id's that
673 # had any activity within the given time span
674 # --------------------------------------------------------------
675 sub fetch_active {
676         my( $e, $ids, $start_date, $end_date ) = @_;
677
678         # use this..
679         # { payment_ts => { between => [ $start, $end ] } } ' ;) 
680
681         my @active;
682         for my $id (@$ids) {
683
684                 # see if any billings were created in the given time range
685                 my $bills = $e->search_money_billing (
686                         {
687                                 xact                    => $id,
688                                 billing_ts      => { between => [ $start_date, $end_date ] },
689                         },
690                         {idlist =>1}
691                 );
692
693                 my $payments = [];
694
695                 if( !@$bills ) {
696
697                         # see if any payments were created in the given range
698                         $payments = $e->search_money_payment (
699                                 {
700                                         xact                    => $id,
701                                         payment_ts      => { between => [ $start_date, $end_date ] },
702                                 },
703                                 {idlist =>1}
704                         );
705                 }
706
707
708                 push( @active, $id ) if @$bills or @$payments;
709         }
710
711         return \@active;
712 }
713
714
715 __PACKAGE__->register_method(
716         method    => 'create_user_note',
717         api_name  => 'open-ils.collections.patron_note.create',
718         api_level => 1,
719         argc      => 4,
720         signature => { 
721                 desc     => q/ Adds a note to a patron's account /,
722                 params   => [
723                         {       name => 'auth',
724                                 desc => 'The authentication token',
725                                 type => 'string' },
726
727                         {       name => 'user_barcode',
728                                 desc => q/The patron's barcode/,
729                                 type => q/string/,
730                         },
731                         {       name => 'title',
732                                 desc => q/The title of the note/,
733                                 type => q/string/,
734                         },
735
736                         {       name => 'note',
737                                 desc => q/The text of the note/,
738                                 type => q/string/,
739                         },
740                 ],
741
742                 'return' => { 
743                         desc            => q/
744                                 Returns SUCCESS event on success, error event otherwise.
745                                 /,
746                         type            => 'object'
747                 }
748         }
749 );
750
751
752 sub create_user_note {
753         my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
754
755         my $e = new_editor(authtoken=>$auth, xact=>1);
756         return $e->event unless $e->checkauth;
757         return $e->event unless $e->allowed('UPDATE_USER');
758
759         return $e->event unless 
760                 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
761
762         my $note = Fieldmapper::actor::usr_note->new;
763         $note->usr($card->usr);
764         $note->title($title);
765         $note->creator($e->requestor->id);
766         $note->create_date('now');
767         $note->pub('f');
768         $note->value($note_txt);
769
770         $e->create_actor_usr_note($note) or return $e->event;
771         $e->commit;
772         return OpenILS::Event->new('SUCCESS');
773 }
774
775
776
777 1;