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/;
12 use OpenILS::Const qw/:const/;
13 my $U = "OpenILS::Application::AppUtils";
15 use Scalar::Util 'blessed';
21 # --------------------------------------------------------------
22 # Loads the config info
23 # --------------------------------------------------------------
24 sub initialize { return 1; }
26 __PACKAGE__->register_method(
27 method => 'user_from_bc',
28 api_name => 'open-ils.collections.user_id_from_barcode',
32 my( $self, $conn, $auth, $bc ) = @_;
33 my $e = new_editor(authtoken=>$auth);
34 return $e->event unless $e->checkauth;
35 return $e->event unless $e->allowed('VIEW_USER');
36 my $card = $e->search_actor_card({barcode=>$bc})->[0]
38 my $user = $e->retrieve_actor_user($card->usr)
44 __PACKAGE__->register_method(
45 method => 'users_of_interest',
46 api_name => 'open-ils.collections.users_of_interest.retrieve',
52 Returns an array of user information objects that the system
53 based on the search criteria provided. If the total fines
54 a user owes reaches or exceeds "fine_level" on or befre "age"
55 and the fines were created at "location", the user will be
56 included in the return set/,
60 desc => 'The authentication token',
64 desc => q/Number of days back to check/,
68 { name => 'fine_level',
69 desc => q/The fine threshold at which users will be included in the search results /,
73 desc => q/The short-name of the orginization unit (library) at which the fines were created.
74 If a selected location has 'child' locations (e.g. a library region), the
75 child locations will be included in the search/,
81 desc => q/An array of user information objects.
82 usr : Array of user information objects containing id, dob, profile, and groups
83 threshold_amount : The total amount the patron owes that is at least as old
84 as the fine "age" and whose transaction was created at the searched location
85 last_pertinent_billing : The time of the last billing that relates to this query
93 groups => [ 'Patron', 'Staff' ],
95 threshold_amount => 99,
102 sub users_of_interest {
103 my( $self, $conn, $auth, $age, $fine_level, $location ) = @_;
105 return OpenILS::Event->new('BAD_PARAMS')
106 unless ($auth and $age and $location);
108 my $e = new_editor(authtoken => $auth);
109 return $e->event unless $e->checkauth;
111 my $org = $e->search_actor_org_unit({shortname => $location})
112 or return $e->event; $org = $org->[0];
114 # they need global perms to view users so no org is provided
115 return $e->event unless $e->allowed('VIEW_USER');
119 my $ses = OpenSRF::AppSession->create('open-ils.storage');
122 my $req = $ses->request(
123 'open-ils.storage.money.collections.users_of_interest',
124 $age, $fine_level, $location);
126 # let the client know we're still here
127 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
129 return process_users_of_interest_results(
130 $self, $conn, $e, $req, $start, $age, $fine_level, $location);
134 __PACKAGE__->register_method(
135 method => 'users_of_interest_warning_penalty',
136 api_name => 'open-ils.collections.users_of_interest.warning_penalty.retrieve',
142 Returns an array of user information objects for users that have the
143 PATRON_EXCEEDS_COLLECTIONS_WARNING penalty applied,
144 based on the search criteria provided./,
148 desc => 'The authentication token',
152 desc => q/The short-name of the orginization unit (library) at which the penalty is applied.
153 If a selected location has 'child' locations (e.g. a library region), the
154 child locations will be included in the search/,
158 desc => q/Optional. Minimum age of the penalty application/,
159 type => q/interval, e.g "30 days"/,
162 desc => q/Optional. Maximum age of the penalty application/,
163 type => q/interval, e.g "90 days"/,
168 desc => q/An array of user information objects.
169 usr : Array of user information objects containing id, dob, profile, and groups
170 threshold_amount : The total amount the patron owes that is at least as old
171 as the fine "age" and whose transaction was created at the searched location
172 last_pertinent_billing : The time of the last billing that relates to this query
180 groups => [ 'Patron', 'Staff' ],
182 threshold_amount => 99, # TODO: still needed?
190 sub users_of_interest_warning_penalty {
191 my( $self, $conn, $auth, $location, $min_age, $max_age ) = @_;
193 return OpenILS::Event->new('BAD_PARAMS') unless ($auth and $location);
195 my $e = new_editor(authtoken => $auth);
196 return $e->event unless $e->checkauth;
198 my $org = $e->search_actor_org_unit({shortname => $location})
199 or return $e->event; $org = $org->[0];
201 # they need global perms to view users so no org is provided
202 return $e->event unless $e->allowed('VIEW_USER');
204 my $org_ids = $e->json_query({from => ['actor.org_unit_full_path', $org->id]});
206 my $ses = OpenSRF::AppSession->create('open-ils.cstore');
209 my $max_set_date = DateTime->now->subtract(seconds =>
210 interval_to_seconds($max_age))->strftime( '%F %T%z' ) if $max_age;
211 my $min_set_date = DateTime->now->subtract(seconds =>
212 interval_to_seconds($min_age))->strftime( '%F %T%z' ) if $min_age;
216 select => {ausp => ['usr']},
223 filter => {name => 'circ.collections.exempt'}
228 location => [ map {$_->{id}} @$org_ids ]
237 standing_penalty => 4, # PATRON_EXCEEDS_COLLECTIONS_WARNING
238 org_unit => [ map {$_->{id}} @$org_ids ],
240 {stop_date => undef},
241 {stop_date => {'>' => 'now'}}
244 # We are only interested in users that do not have the
245 # circ.collections.exempt setting applied
246 '+aus' => {value => undef},
247 # and we're only interested in users that are not in the
248 # collections tracker table
249 '+mct' => {id => undef}
253 $query->{where}->{'-and'} = [] if $max_set_date or $min_set_date;
254 push(@{$query->{where}->{'-and'}}, {set_date => {'>' => $max_set_date}}) if $max_set_date;
255 push(@{$query->{where}->{'-and'}}, {set_date => {'<' => $min_set_date}}) if $min_set_date;
257 my $req = $ses->request('open-ils.cstore.json_query', $query);
259 # let the client know we're still here
260 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
262 return process_users_of_interest_results(
263 $self, $conn, $e, $req, $start, $min_age, '', $location, $max_age);
269 sub process_users_of_interest_results {
270 my($self, $conn, $e, $req, $starttime, @params) = @_;
273 while( my $resp = $req->recv(timeout => 7200) ) {
275 return $req->failed if $req->failed;
276 my $hash = $resp->content;
280 $total = time - $starttime;
281 $logger->info("collections: request (@params) took $total seconds");
284 my $u = $e->retrieve_actor_user(
289 flesh_fields => {au => ["groups","profile", "card"]},
292 ) or return $e->event;
297 profile => $u->profile->name,
298 barcode => $u->card->barcode,
299 groups => [ map { $_->name } @{$u->groups} ],
302 $conn->respond($hash);
309 __PACKAGE__->register_method(
310 method => 'users_with_activity',
311 api_name => 'open-ils.collections.users_with_activity.retrieve',
317 Returns an array of users that are already in collections
318 and had any type of billing or payment activity within
319 the given time frame at the location (or child locations)
324 desc => 'The authentication token',
327 { name => 'start_date',
328 desc => 'The start of the time interval to check',
329 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
332 { name => 'end_date',
333 desc => q/Then end date of the time interval to check/,
334 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
336 { name => 'location',
337 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
338 If a selected location has 'child' locations (e.g. a library region), the
339 child locations will be included in the search/,
345 desc => q/An array of user information objects/,
351 sub users_with_activity {
352 my( $self, $conn, $auth, $start_date, $end_date, $location ) = @_;
353 return OpenILS::Event->new('BAD_PARAMS')
354 unless ($auth and $start_date and $end_date and $location);
356 my $e = new_editor(authtoken => $auth);
357 return $e->event unless $e->checkauth;
359 my $org = $e->search_actor_org_unit({shortname => $location})
360 or return $e->event; $org = $org->[0];
361 return $e->event unless $e->allowed('VIEW_USER', $org->id);
363 my $ses = OpenSRF::AppSession->create('open-ils.storage');
366 my $req = $ses->request(
367 'open-ils.storage.money.collections.users_with_activity.atomic',
368 $start_date, $end_date, $location);
370 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
373 while( my $resp = $req->recv(timeout => 7200) ) {
376 $total = time - $start;
377 $logger->info("collections: users_with_activity search ".
378 "($start_date, $end_date, $location) took $total seconds");
381 return $req->failed if $req->failed;
382 $conn->respond($resp->content);
390 __PACKAGE__->register_method(
391 method => 'put_into_collections',
392 api_name => 'open-ils.collections.put_into_collections',
397 Marks a user as being "in collections" at a given location
402 desc => 'The authentication token',
406 desc => 'The id of the user to plact into collections',
410 { name => 'location',
411 desc => q/The short-name of the orginization unit (library)
412 for which the user is being placed in collections/,
415 { name => 'fee_amount',
417 The amount of money that a patron should be fined.
418 If this field is empty, no fine is created.
422 { name => 'fee_note',
424 Custom note that is added to the the billing.
425 This field is not required.
426 Note: fee_note is not the billing_type. Billing_type type is
427 decided by the system. (e.g. "fee for collections").
428 fee_note is purely used for any additional needed information
429 and is only visible to staff.
436 desc => q/A SUCCESS event on success, error event on failure/,
441 sub put_into_collections {
442 my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
444 return OpenILS::Event->new('BAD_PARAMS')
445 unless ($auth and $user_id and $location);
447 my $e = new_editor(authtoken => $auth, xact =>1);
448 return $e->event unless $e->checkauth;
450 my $org = $e->search_actor_org_unit({shortname => $location});
451 return $e->event unless $org = $org->[0];
452 return $e->event unless $e->allowed('money.collections_tracker.create', $org->id);
454 my $existing = $e->search_money_collections_tracker(
456 location => $org->id,
458 collector => $e->requestor->id
463 return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
465 $logger->info("collect: user ".$e->requestor->id.
466 " putting user $user_id into collections for $location");
468 my $tracker = Fieldmapper::money::collections_tracker->new;
470 $tracker->usr($user_id);
471 $tracker->collector($e->requestor->id);
472 $tracker->location($org->id);
473 $tracker->enter_time('now');
475 $e->create_money_collections_tracker($tracker)
479 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
485 my $pen = Fieldmapper::actor::user_standing_penalty->new;
486 $pen->org_unit($org->id);
488 $pen->standing_penalty(30); # PATRON_IN_COLLECTIONS
489 $pen->staff($e->requestor->id);
490 $pen->note($fee_note) if $fee_note;
491 $U->simplereq('open-ils.actor', 'open-ils.actor.user.penalty.apply', $auth, $pen);
493 return OpenILS::Event->new('SUCCESS');
496 sub add_collections_fee {
497 my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
501 $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
503 my $xact = Fieldmapper::money::grocery->new;
504 $xact->usr($patron_id);
505 $xact->xact_start('now');
506 $xact->billing_location($org->id);
508 $xact = $e->create_money_grocery($xact) or return $e->event;
510 my $bill = Fieldmapper::money::billing->new;
511 $bill->note($fee_note);
512 $bill->xact($xact->id);
514 $bill->billing_type(OILS_BILLING_TYPE_COLLECTION_FEE);
515 $bill->amount($fee_amount);
517 $e->create_money_billing($bill) or return $e->event;
524 __PACKAGE__->register_method(
525 method => 'remove_from_collections',
526 api_name => 'open-ils.collections.remove_from_collections',
528 Returns the users that are currently in collections and
529 had activity during the provided interval. Dates are inclusive.
530 @param start_date The beginning of the activity interval
531 @param end_date The end of the activity interval
532 @param location The location at which the fines were created
537 __PACKAGE__->register_method(
538 method => 'remove_from_collections',
539 api_name => 'open-ils.collections.remove_from_collections',
544 Removes a user from the collections table for the given location
549 desc => 'The authentication token',
553 desc => 'The id of the user to plact into collections',
557 { name => 'location',
558 desc => q/The short-name of the orginization unit (library)
559 for which the user is being removed from collections/,
565 desc => q/A SUCCESS event on success, error event on failure/,
571 sub remove_from_collections {
572 my( $self, $conn, $auth, $user_id, $location ) = @_;
574 return OpenILS::Event->new('BAD_PARAMS')
575 unless ($auth and $user_id and $location);
577 my $e = new_editor(authtoken => $auth, xact=>1);
578 return $e->event unless $e->checkauth;
580 my $org = $e->search_actor_org_unit({shortname => $location})
581 or return $e->event; $org = $org->[0];
582 return $e->event unless $e->allowed('money.collections_tracker.delete', $org->id);
584 my $tracker = $e->search_money_collections_tracker(
585 { usr => $user_id, location => $org->id })
588 $e->delete_money_collections_tracker($tracker->[0])
592 return OpenILS::Event->new('SUCCESS');
596 #__PACKAGE__->register_method(
597 # method => 'transaction_details',
598 # api_name => 'open-ils.collections.user_transaction_details.retrieve',
604 __PACKAGE__->register_method(
605 method => 'transaction_details',
606 api_name => 'open-ils.collections.user_transaction_details.retrieve',
611 Returns a list of fleshed user objects with transaction details
616 desc => 'The authentication token',
619 { name => 'start_date',
620 desc => 'The start of the time interval to check',
621 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
624 { name => 'end_date',
625 desc => q/Then end date of the time interval to check/,
626 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
628 { name => 'location',
629 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
630 If a selected location has 'child' locations (e.g. a library region), the
631 child locations will be included in the search/,
636 desc => 'An array of user ids',
642 desc => q/A list of objects. Object keys include:
644 transactions : An object with keys :
645 circulations : Fleshed circulation objects
646 grocery : Fleshed 'grocery' transaction objects
653 sub transaction_details {
654 my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
656 return OpenILS::Event->new('BAD_PARAMS')
657 unless ($auth and $start_date and $end_date and $location and $user_list);
659 my $e = new_editor(authtoken => $auth);
660 return $e->event unless $e->checkauth;
662 # they need global perms to view users so no org is provided
663 return $e->event unless $e->allowed('VIEW_USER');
665 my $org = $e->search_actor_org_unit({shortname => $location})
666 or return $e->event; $org = $org->[0];
668 # get a reference to the org inside of the tree
669 $org = $U->find_org($U->get_org_tree(), $org->id);
672 for my $uid (@$user_list) {
675 $blob->{usr} = $e->retrieve_actor_user(
684 "standing_penalties",
695 $blob->{transactions} = {
697 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
699 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date),
701 fetch_reservation_xacts($e, $uid, $org, $start_date, $end_date)
704 # for each transaction, flesh the workstatoin on any attached payment
705 # and make the payment object a real object (e.g. cash payment),
706 # not just a generic payment object
708 @{$blob->{transactions}->{circulations}},
709 @{$blob->{transactions}->{reservations}},
710 @{$blob->{transactions}->{grocery}} ) {
713 if( $ps = $xact->payments and @$ps ) {
714 my @fleshed; my $evt;
716 ($p, $evt) = flesh_payment($e,$p);
720 $xact->payments(\@fleshed);
724 push( @data, $blob );
730 __PACKAGE__->register_method(
731 method => 'user_balance_summary',
732 api_name => 'open-ils.collections.user_balance_summary.generate',
737 desc => q/Collect balance information for users in collections. By default,
738 only the total balance owed is calculated. Use the "include_xacts"
739 param to include per-transaction summaries as well./,
742 desc => 'The authentication token',
746 Hash of API arguments. Options include:
747 location -- org unit shortname
748 start_date -- ISO 8601 date. limit to patrons added to collections on or after this date (optional).
749 end_date -- ISO 8601 date. limit to patrons added to collections on or before this date (optional).
750 user_id -- retrieve information only for this user (takes preference over
751 start and end_date). May be a single ID or list of IDs. (optional).
752 include_xacts -- If true, include a summary object per transaction in addition to the full balance owed
759 The file name prefix of the file to be created.
760 The file name format will be:
761 user_balance_YYYY-MM-DD_${location}_${start_date}_${end_date}_${user_id}.[tmp|xml]
762 Optional params not provided by the caller will not be part of the file name.
764 user_balance_BR1_2012-05-25_2012-01-01_2012-12-31 # start and end dates
765 user_balance_BR2_2012-05-25_153244 # user id only.
766 In-process files will have a .tmp suffix
767 Completed files will have a .xml suffix
774 sub user_balance_summary {
775 my ($self, $client, $auth, $args) = @_;
777 my $location = $$args{location};
778 my $start_date = $$args{start_date};
779 my $end_date = $$args{end_date};
780 my $user_id = $$args{user_id};
782 return OpenILS::Event->new('BAD_PARAMS')
783 unless $auth and $location and
784 ($start_date or $end_date or $user_id);
786 my $e = new_editor(authtoken => $auth);
787 return $e->event unless $e->checkauth;
789 my $org = $e->search_actor_org_unit({shortname => $location})->[0]
792 # they need global perms to view users so no org is provided
793 return $e->event unless $e->allowed('VIEW_USER', $org->id);
795 my $org_list = $U->get_org_descendants($org->id);
797 my ($evt, $file_prefix, $file_name, $FILE) = setup_batch_file('user_balance', $args);
799 $client->respond_complete($evt || $file_prefix);
804 @user_list = (ref $user_id eq 'ARRAY') ? @$user_id : ($user_id);
807 # collect the users from the tracker table based on the provided filters
810 select => {mct => ['usr']},
812 where => {location => $org_list}
815 $query->{where}->{enter_time} = {'>=' => $start_date};
816 $query->{where}->{enter_time} = {'<=' => $end_date};
817 my $users = $e->json_query($query);
818 @user_list = map {$_->{usr}} @$users;
821 print $FILE "<Collections>\n"; # append to the document as we have data
823 for my $user_id (@user_list) {
824 my $user_doc = XML::LibXML::Document->new;
825 my $root = $user_doc->createElement('User');
826 $user_doc->setDocumentElement($root);
828 my $user = $e->retrieve_actor_user([
835 'standing_penalties',
844 my $au_doc = $user->toXML({no_virt => 1, skip_fields => {au => ['passwd']}});
845 my $au_node = $au_doc->documentElement;
846 $user_doc->adoptNode($au_node);
847 $root->appendChild($au_node);
849 my $circ_ids = $e->search_action_circulation(
850 {usr => $user_id, circ_lib => $org_list, xact_finish => undef},
854 my $groc_ids = $e->search_money_grocery(
855 {usr => $user_id, billing_location => $org_list, xact_finish => undef},
859 my $res_ids = $e->search_booking_reservation(
860 {usr => $user_id, pickup_lib => $org_list, xact_finish => undef},
864 # get the sum owed an all transactions
865 my $balance = $e->json_query({
867 { column => 'balance_owed',
873 where => {id => [@$circ_ids, @$groc_ids, @$res_ids]}
876 $balance = $balance ? $balance->{balance_owed} : '0';
878 my $xacts_node = $user_doc->createElement('Transactions');
879 my $balance_node = $user_doc->createElement('BalanceOwed');
880 $balance_node->appendChild($user_doc->createTextNode($balance));
881 $xacts_node->appendChild($balance_node);
882 $root->appendChild($xacts_node);
884 if ($$args{include_xacts}) {
885 my $xacts = $e->search_money_billable_transaction_summary(
886 {id => [@$circ_ids, @$groc_ids, @$res_ids]},
890 for my $xact (@$xacts) {
891 my $xact_node = $xact->toXML({no_virt => 1})->documentElement;
892 $user_doc->adoptNode($xact_node);
893 $xacts_node->appendChild($xact_node);
897 print $FILE $user_doc->documentElement->toString(1) . "\n";
900 print $FILE "\n</Collections>";
903 (my $complete_file = $file_name) =~ s|.tmp$|.xml|og;
905 unless (move($file_name, $complete_file)) {
906 $logger->error("collections: unable to move ".
907 "user_balance file $file_name => $complete_file : $@");
913 sub setup_batch_file {
916 my $location = $$args{location};
917 my $start_date = $$args{start_date};
918 my $end_date = $$args{end_date};
919 my $user_id = $$args{user_id};
921 my $conf = OpenSRF::Utils::SettingsClient->new;
922 my $dir_name = $conf->config_value(apps =>
923 'open-ils.collections' => app_settings => 'batch_file_dir');
926 $logger->error("collections: no batch_file_dir directory configured");
927 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
930 unless (-e $dir_name) {
931 eval { mkpath($dir_name); };
933 $logger->error("collections: unable to create batch_file_dir directory $dir_name : $@");
934 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
938 my $file_prefix = "${prefix}_" . DateTime->now->strftime('%F') . "_$location";
939 $file_prefix .= "_$start_date" if $start_date;
940 $file_prefix .= "_$end_date" if $end_date;
941 $file_prefix .= "_$user_id" if $user_id;
944 my $file_name = File::Spec->catfile($dir_name, "$file_prefix.tmp");
946 unless (open($FILE, '>', $file_name)) {
947 $logger->error("collections: unable to open user_balance_summary file $file_name : $@");
948 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
951 return (undef, $file_prefix, $file_name, $FILE);
957 my $type = $p->payment_type;
958 $logger->debug("collect: fleshing workstation on payment $type : ".$p->id);
959 my $meth = "retrieve_money_$type";
960 $p = $e->$meth($p->id) or return (undef, $e->event);
962 $p->payment_type($type);
964 $e->retrieve_actor_workstation(
969 flesh_fields => { aws => [ 'owning_lib' ] }
974 } catch Error with {};
979 # --------------------------------------------------------------
980 # Collect all open circs for the user
981 # For each circ, see if any billings or payments were created
982 # during the given time period.
983 # --------------------------------------------------------------
984 sub fetch_circ_xacts {
988 my $start_date = shift;
989 my $end_date = shift;
993 # at the specified org and each descendent org,
994 # fetch the open circs for this user
995 $U->walk_org_tree( $org,
998 $logger->debug("collect: searching for open circs at " . $n->shortname);
1001 $e->search_action_circulation(
1015 my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
1017 for my $cid (@$active_ids) {
1019 $e->retrieve_action_circulation(
1025 circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
1036 sub fetch_grocery_xacts {
1040 my $start_date = shift;
1041 my $end_date = shift;
1044 $U->walk_org_tree( $org,
1047 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
1050 $e->search_money_grocery(
1053 billing_location => $n->id,
1063 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
1065 for my $id (@$active_ids) {
1067 $e->retrieve_money_grocery(
1073 mg => [ "billings", "payments", "billing_location" ] }
1083 sub fetch_reservation_xacts {
1087 my $start_date = shift;
1088 my $end_date = shift;
1091 $U->walk_org_tree( $org,
1094 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
1097 $e->search_booking_reservation(
1100 pickup_lib => $n->id,
1110 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
1112 for my $id (@$active_ids) {
1114 $e->retrieve_booking_reservation(
1120 bresv => [ "billings", "payments", "pickup_lib" ] }
1132 # --------------------------------------------------------------
1133 # Given a list of xact id's, this returns a list of id's that
1134 # had any activity within the given time span
1135 # --------------------------------------------------------------
1137 my( $e, $ids, $start_date, $end_date ) = @_;
1140 # { payment_ts => { between => [ $start, $end ] } } ' ;)
1143 for my $id (@$ids) {
1145 # see if any billings were created in the given time range
1146 my $bills = $e->search_money_billing (
1149 billing_ts => { between => [ $start_date, $end_date ] },
1158 # see if any payments were created in the given range
1159 $payments = $e->search_money_payment (
1162 payment_ts => { between => [ $start_date, $end_date ] },
1169 push( @active, $id ) if @$bills or @$payments;
1176 __PACKAGE__->register_method(
1177 method => 'create_user_note',
1178 api_name => 'open-ils.collections.patron_note.create',
1182 desc => q/ Adds a note to a patron's account /,
1185 desc => 'The authentication token',
1188 { name => 'user_barcode',
1189 desc => q/The patron's barcode/,
1193 desc => q/The title of the note/,
1198 desc => q/The text of the note/,
1205 Returns SUCCESS event on success, error event otherwise.
1213 sub create_user_note {
1214 my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
1216 my $e = new_editor(authtoken=>$auth, xact=>1);
1217 return $e->event unless $e->checkauth;
1218 return $e->event unless $e->allowed('UPDATE_USER'); # XXX Makre more specific perm for this
1220 return $e->event unless
1221 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
1223 my $note = Fieldmapper::actor::usr_note->new;
1224 $note->usr($card->usr);
1225 $note->title($title);
1226 $note->creator($e->requestor->id);
1227 $note->create_date('now');
1229 $note->value($note_txt);
1231 $e->create_actor_usr_note($note) or return $e->event;
1233 return OpenILS::Event->new('SUCCESS');