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