]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Collections.pm
fixed typo
[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(OILS_BILLING_TYPE_COLLECTION_FEE);
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->payment_type($type);
537                 $p->cash_drawer(
538                         $e->retrieve_actor_workstation(
539                                 [
540                                         $p->cash_drawer,
541                                         {
542                                                 flesh => 1,
543                                                 flesh_fields => { aws => [ 'owning_lib' ] }
544                                         }
545                                 ]
546                         )
547                 );
548         } catch Error with {};
549         return ($p);
550 }
551
552
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 {
559         my $e                           = shift;
560         my $uid                 = shift;
561         my $org                 = shift;
562         my $start_date = shift;
563         my $end_date    = shift;
564
565         my @circs;
566
567         # at the specified org and each descendent org, 
568         # fetch the open circs for this user
569         $U->walk_org_tree( $org, 
570                 sub {
571                         my $n = shift;
572                         $logger->debug("collect: searching for open circs at " . $n->shortname);
573                         push( @circs, 
574                                 @{
575                                         $e->search_action_circulation(
576                                                 {
577                                                         usr                     => $uid, 
578                                                         circ_lib                => $n->id,
579                                                 }, 
580                                                 {idlist => 1}
581                                         )
582                                 }
583                         );
584                 }
585         );
586
587
588         my @data;
589         my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
590
591         for my $cid (@$active_ids) {
592                 push( @data, 
593                         $e->retrieve_action_circulation(
594                                 [
595                                         $cid,
596                                         {
597                                                 flesh => 1,
598                                                 flesh_fields => { 
599                                                         circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
600                                                 }
601                                         }
602                                 ]
603                         )
604                 );
605         }
606
607         return \@data;
608 }
609
610 sub set_copy_price {
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);
619 }
620
621
622
623 sub fetch_grocery_xacts {
624         my $e                           = shift;
625         my $uid                 = shift;
626         my $org                 = shift;
627         my $start_date = shift;
628         my $end_date    = shift;
629
630         my @xacts;
631         $U->walk_org_tree( $org, 
632                 sub {
633                         my $n = shift;
634                         $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
635                         push( @xacts, 
636                                 @{
637                                         $e->search_money_grocery(
638                                                 {
639                                                         usr                                     => $uid, 
640                                                         billing_location        => $n->id,
641                                                 }, 
642                                                 {idlist => 1}
643                                         )
644                                 }
645                         );
646                 }
647         );
648
649         my @data;
650         my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
651
652         for my $id (@$active_ids) {
653                 push( @data, 
654                         $e->retrieve_money_grocery(
655                                 [
656                                         $id,
657                                         {
658                                                 flesh => 1,
659                                                 flesh_fields => { 
660                                                         mg => [ "billings", "payments", "billing_location" ] }
661                                         }
662                                 ]
663                         )
664                 );
665         }
666
667         return \@data;
668 }
669
670
671
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 # --------------------------------------------------------------
676 sub fetch_active {
677         my( $e, $ids, $start_date, $end_date ) = @_;
678
679         # use this..
680         # { payment_ts => { between => [ $start, $end ] } } ' ;) 
681
682         my @active;
683         for my $id (@$ids) {
684
685                 # see if any billings were created in the given time range
686                 my $bills = $e->search_money_billing (
687                         {
688                                 xact                    => $id,
689                                 billing_ts      => { between => [ $start_date, $end_date ] },
690                         },
691                         {idlist =>1}
692                 );
693
694                 my $payments = [];
695
696                 if( !@$bills ) {
697
698                         # see if any payments were created in the given range
699                         $payments = $e->search_money_payment (
700                                 {
701                                         xact                    => $id,
702                                         payment_ts      => { between => [ $start_date, $end_date ] },
703                                 },
704                                 {idlist =>1}
705                         );
706                 }
707
708
709                 push( @active, $id ) if @$bills or @$payments;
710         }
711
712         return \@active;
713 }
714
715
716 __PACKAGE__->register_method(
717         method    => 'create_user_note',
718         api_name  => 'open-ils.collections.patron_note.create',
719         api_level => 1,
720         argc      => 4,
721         signature => { 
722                 desc     => q/ Adds a note to a patron's account /,
723                 params   => [
724                         {       name => 'auth',
725                                 desc => 'The authentication token',
726                                 type => 'string' },
727
728                         {       name => 'user_barcode',
729                                 desc => q/The patron's barcode/,
730                                 type => q/string/,
731                         },
732                         {       name => 'title',
733                                 desc => q/The title of the note/,
734                                 type => q/string/,
735                         },
736
737                         {       name => 'note',
738                                 desc => q/The text of the note/,
739                                 type => q/string/,
740                         },
741                 ],
742
743                 'return' => { 
744                         desc            => q/
745                                 Returns SUCCESS event on success, error event otherwise.
746                                 /,
747                         type            => 'object'
748                 }
749         }
750 );
751
752
753 sub create_user_note {
754         my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
755
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'); # XXX Makre more specific perm for this
759
760         return $e->event unless 
761                 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
762
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');
768         $note->pub('f');
769         $note->value($note_txt);
770
771         $e->create_actor_usr_note($note) or return $e->event;
772         $e->commit;
773         return OpenILS::Event->new('SUCCESS');
774 }
775
776
777
778 1;