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