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