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