1 package OpenILS::Application::Collections;
2 use strict; use warnings;
3 use OpenSRF::EX qw(:try);
4 use OpenILS::Application::AppUtils;
5 use OpenSRF::Utils::Logger qw(:logger);
6 use OpenILS::Utils::DateTime 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;
214 my $sp_id = $U->ou_ancestor_setting_value($org->id, 'circ.custom_penalty_override.PATRON_EXCEEDS_COLLECTIONS_WARNING') || 4;
218 select => {ausp => ['usr']},
225 filter => {name => 'circ.collections.exempt'}
230 location => [ map {$_->{id}} @$org_ids ]
239 standing_penalty => [4,$sp_id], # PATRON_EXCEEDS_COLLECTIONS_WARNING
240 org_unit => [ map {$_->{id}} @$org_ids ],
242 {stop_date => undef},
243 {stop_date => {'>' => 'now'}}
246 # We are only interested in users that do not have the
247 # circ.collections.exempt setting applied
248 '+aus' => {value => undef},
249 # and we're only interested in users that are not in the
250 # collections tracker table
251 '+mct' => {id => undef}
255 $query->{where}->{'-and'} = [] if $max_set_date or $min_set_date;
256 push(@{$query->{where}->{'-and'}}, {set_date => {'>' => $max_set_date}}) if $max_set_date;
257 push(@{$query->{where}->{'-and'}}, {set_date => {'<' => $min_set_date}}) if $min_set_date;
259 my $req = $ses->request('open-ils.cstore.json_query', $query);
261 # let the client know we're still here
262 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
264 return process_users_of_interest_results(
265 $self, $conn, $e, $req, $start, $min_age, '', $location, $max_age);
271 sub process_users_of_interest_results {
272 my($self, $conn, $e, $req, $starttime, @params) = @_;
275 while( my $resp = $req->recv(timeout => 7200) ) {
277 return $req->failed if $req->failed;
278 my $hash = $resp->content;
282 $total = time - $starttime;
283 $logger->info("collections: request (@params) took $total seconds");
286 my $u = $e->retrieve_actor_user(
291 flesh_fields => {au => ["groups","profile", "card"]},
294 ) or return $e->event;
299 profile => $u->profile->name,
300 barcode => $u->card ? $u->card->barcode : undef ,
301 groups => [ map { $_->name } @{$u->groups} ],
304 $conn->respond($hash);
311 __PACKAGE__->register_method(
312 method => 'users_with_activity',
313 api_name => 'open-ils.collections.users_with_activity.retrieve',
319 Returns an array of users that are already in collections
320 and had any type of billing or payment activity within
321 the given time frame at the location (or child locations)
326 desc => 'The authentication token',
329 { name => 'start_date',
330 desc => 'The start of the time interval to check',
331 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
334 { name => 'end_date',
335 desc => q/Then end date of the time interval to check/,
336 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
338 { name => 'location',
339 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
340 If a selected location has 'child' locations (e.g. a library region), the
341 child locations will be included in the search/,
347 desc => q/An array of user information objects/,
353 sub users_with_activity {
354 my( $self, $conn, $auth, $start_date, $end_date, $location ) = @_;
355 return OpenILS::Event->new('BAD_PARAMS')
356 unless ($auth and $start_date and $end_date and $location);
358 my $e = new_editor(authtoken => $auth);
359 return $e->event unless $e->checkauth;
361 my $org = $e->search_actor_org_unit({shortname => $location})
362 or return $e->event; $org = $org->[0];
363 return $e->event unless $e->allowed('VIEW_USER', $org->id);
365 my $ses = OpenSRF::AppSession->create('open-ils.storage');
368 my $req = $ses->request(
369 'open-ils.storage.money.collections.users_with_activity.atomic',
370 $start_date, $end_date, $location);
372 $conn->status( new OpenSRF::DomainObject::oilsContinueStatus );
375 while( my $resp = $req->recv(timeout => 7200) ) {
378 $total = time - $start;
379 $logger->info("collections: users_with_activity search ".
380 "($start_date, $end_date, $location) took $total seconds");
383 return $req->failed if $req->failed;
384 $conn->respond($resp->content);
392 __PACKAGE__->register_method(
393 method => 'put_into_collections',
394 api_name => 'open-ils.collections.put_into_collections',
399 Marks a user as being "in collections" at a given location
404 desc => 'The authentication token',
408 desc => 'The id of the user to plact into collections',
412 { name => 'location',
413 desc => q/The short-name of the orginization unit (library)
414 for which the user is being placed in collections/,
417 { name => 'fee_amount',
419 The amount of money that a patron should be fined.
420 If this field is empty, no fine is created.
424 { name => 'fee_note',
426 Custom note that is added to the the billing.
427 This field is not required.
428 Note: fee_note is not the billing_type. Billing_type type is
429 decided by the system. (e.g. "fee for collections").
430 fee_note is purely used for any additional needed information
431 and is only visible to staff.
438 desc => q/A SUCCESS event on success, error event on failure/,
443 sub put_into_collections {
444 my( $self, $conn, $auth, $user_id, $location, $fee_amount, $fee_note ) = @_;
446 return OpenILS::Event->new('BAD_PARAMS')
447 unless ($auth and $user_id and $location);
449 my $e = new_editor(authtoken => $auth, xact =>1);
450 return $e->event unless $e->checkauth;
452 my $org = $e->search_actor_org_unit({shortname => $location});
453 return $e->event unless $org = $org->[0];
454 return $e->event unless $e->allowed('money.collections_tracker.create', $org->id);
456 my $existing = $e->search_money_collections_tracker(
458 location => $org->id,
460 collector => $e->requestor->id
465 return OpenILS::Event->new('MONEY_COLLECTIONS_TRACKER_EXISTS') if @$existing;
467 $logger->info("collect: user ".$e->requestor->id.
468 " putting user $user_id into collections for $location");
470 my $tracker = Fieldmapper::money::collections_tracker->new;
472 $tracker->usr($user_id);
473 $tracker->collector($e->requestor->id);
474 $tracker->location($org->id);
475 $tracker->enter_time('now');
477 $e->create_money_collections_tracker($tracker)
481 my $evt = add_collections_fee($e, $user_id, $org, $fee_amount, $fee_note );
487 my $sp_id = $U->ou_ancestor_setting_value($org->id, 'circ.custom_penalty_override.PATRON_IN_COLLECTIONS') || 30;
489 my $pen = Fieldmapper::actor::user_standing_penalty->new;
490 $pen->org_unit($org->id);
492 $pen->standing_penalty($sp_id); # PATRON_IN_COLLECTIONS
493 $pen->staff($e->requestor->id);
494 my $msg = { 'pub' => 0, 'title' => 'PATRON_IN_COLLECTIONS', 'message' => $fee_note };
495 $U->simplereq('open-ils.actor', 'open-ils.actor.user.penalty.apply', $auth, $pen, $msg);
497 return OpenILS::Event->new('SUCCESS');
500 sub add_collections_fee {
501 my( $e, $patron_id, $org, $fee_amount, $fee_note ) = @_;
505 $logger->info("collect: adding fee to user $patron_id : $fee_amount : $fee_note");
507 my $xact = Fieldmapper::money::grocery->new;
508 $xact->usr($patron_id);
509 $xact->xact_start('now');
510 $xact->billing_location($org->id);
512 $xact = $e->create_money_grocery($xact) or return $e->event;
514 my $bill = Fieldmapper::money::billing->new;
515 $bill->note($fee_note);
516 $bill->xact($xact->id);
518 $bill->billing_type(OILS_BILLING_TYPE_COLLECTION_FEE);
519 $bill->amount($fee_amount);
521 $e->create_money_billing($bill) or return $e->event;
528 __PACKAGE__->register_method(
529 method => 'remove_from_collections',
530 api_name => 'open-ils.collections.remove_from_collections',
532 Returns the users that are currently in collections and
533 had activity during the provided interval. Dates are inclusive.
534 @param start_date The beginning of the activity interval
535 @param end_date The end of the activity interval
536 @param location The location at which the fines were created
541 __PACKAGE__->register_method(
542 method => 'remove_from_collections',
543 api_name => 'open-ils.collections.remove_from_collections',
548 Removes a user from the collections table for the given location
553 desc => 'The authentication token',
557 desc => 'The id of the user to plact into collections',
561 { name => 'location',
562 desc => q/The short-name of the orginization unit (library)
563 for which the user is being removed from collections/,
569 desc => q/A SUCCESS event on success, error event on failure/,
575 sub remove_from_collections {
576 my( $self, $conn, $auth, $user_id, $location ) = @_;
578 return OpenILS::Event->new('BAD_PARAMS')
579 unless ($auth and $user_id and $location);
581 my $e = new_editor(authtoken => $auth, xact=>1);
582 return $e->event unless $e->checkauth;
584 my $org = $e->search_actor_org_unit({shortname => $location})
585 or return $e->event; $org = $org->[0];
586 return $e->event unless $e->allowed('money.collections_tracker.delete', $org->id);
588 my $tracker = $e->search_money_collections_tracker(
589 { usr => $user_id, location => $org->id })
592 $e->delete_money_collections_tracker($tracker->[0])
596 return OpenILS::Event->new('SUCCESS');
600 #__PACKAGE__->register_method(
601 # method => 'transaction_details',
602 # api_name => 'open-ils.collections.user_transaction_details.retrieve',
608 __PACKAGE__->register_method(
609 method => 'transaction_details',
610 api_name => 'open-ils.collections.user_transaction_details.retrieve',
615 Returns a list of fleshed user objects with transaction details
620 desc => 'The authentication token',
623 { name => 'start_date',
624 desc => 'The start of the time interval to check',
625 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
628 { name => 'end_date',
629 desc => q/Then end date of the time interval to check/,
630 type => q/string (ISO 8601 timestamp. E.g. 2006-06-24, 1994-11-05T08:15:30-05:00 /,
632 { name => 'location',
633 desc => q/The short-name of the orginization unit (library) at which the activity occurred.
634 If a selected location has 'child' locations (e.g. a library region), the
635 child locations will be included in the search/,
640 desc => 'An array of user ids',
646 desc => q/A list of objects. Object keys include:
648 transactions : An object with keys :
649 circulations : Fleshed circulation objects
650 grocery : Fleshed 'grocery' transaction objects
657 sub transaction_details {
658 my( $self, $conn, $auth, $start_date, $end_date, $location, $user_list ) = @_;
660 return OpenILS::Event->new('BAD_PARAMS')
661 unless ($auth and $start_date and $end_date and $location and $user_list);
663 my $e = new_editor(authtoken => $auth);
664 return $e->event unless $e->checkauth;
666 # they need global perms to view users so no org is provided
667 return $e->event unless $e->allowed('VIEW_USER');
669 my $org = $e->search_actor_org_unit({shortname => $location})
670 or return $e->event; $org = $org->[0];
672 # get a reference to the org inside of the tree
673 $org = $U->find_org($U->get_org_tree(), $org->id);
676 for my $uid (@$user_list) {
679 $blob->{usr} = $e->retrieve_actor_user(
688 "standing_penalties",
699 $blob->{transactions} = {
701 fetch_circ_xacts($e, $uid, $org, $start_date, $end_date),
703 fetch_grocery_xacts($e, $uid, $org, $start_date, $end_date),
705 fetch_reservation_xacts($e, $uid, $org, $start_date, $end_date)
708 # for each transaction, flesh the workstatoin on any attached payment
709 # and make the payment object a real object (e.g. cash payment),
710 # not just a generic payment object
712 @{$blob->{transactions}->{circulations}},
713 @{$blob->{transactions}->{reservations}},
714 @{$blob->{transactions}->{grocery}} ) {
717 if( $ps = $xact->payments and @$ps ) {
718 my @fleshed; my $evt;
720 ($p, $evt) = flesh_payment($e,$p);
724 $xact->payments(\@fleshed);
728 push( @data, $blob );
734 __PACKAGE__->register_method(
735 method => 'user_balance_summary',
736 api_name => 'open-ils.collections.user_balance_summary.generate',
741 desc => q/Collect balance information for users in collections. By default,
742 only the total balance owed is calculated. Use the "include_xacts"
743 param to include per-transaction summaries as well./,
746 desc => 'The authentication token',
750 Hash of API arguments. Options include:
751 location -- org unit shortname
752 start_date -- ISO 8601 date. limit to patrons added to collections on or after this date (optional).
753 end_date -- ISO 8601 date. limit to patrons added to collections on or before this date (optional).
754 user_id -- retrieve information only for this user (takes preference over
755 start and end_date). May be a single ID or list of IDs. (optional).
756 include_xacts -- If true, include a summary object per transaction in addition to the full balance owed
763 The file name prefix of the file to be created.
764 The file name format will be:
765 user_balance_YYYY-MM-DD_${location}_${start_date}_${end_date}_${user_id}.[tmp|xml]
766 Optional params not provided by the caller will not be part of the file name.
768 user_balance_BR1_2012-05-25_2012-01-01_2012-12-31 # start and end dates
769 user_balance_BR2_2012-05-25_153244 # user id only.
770 In-process files will have a .tmp suffix
771 Completed files will have a .xml suffix
778 sub user_balance_summary {
779 my ($self, $client, $auth, $args) = @_;
781 my $location = $$args{location};
782 my $start_date = $$args{start_date};
783 my $end_date = $$args{end_date};
784 my $user_id = $$args{user_id};
786 return OpenILS::Event->new('BAD_PARAMS')
787 unless $auth and $location and
788 ($start_date or $end_date or $user_id);
790 my $e = new_editor(authtoken => $auth);
791 return $e->event unless $e->checkauth;
793 my $org = $e->search_actor_org_unit({shortname => $location})->[0]
796 # they need global perms to view users so no org is provided
797 return $e->event unless $e->allowed('VIEW_USER', $org->id);
799 my $org_list = $U->get_org_descendants($org->id);
801 my ($evt, $file_prefix, $file_name, $FILE) = setup_batch_file('user_balance', $args);
803 $client->respond_complete($evt || $file_prefix);
808 @user_list = (ref $user_id eq 'ARRAY') ? @$user_id : ($user_id);
811 # collect the users from the tracker table based on the provided filters
814 select => {mct => ['usr']},
816 where => {location => $org_list}
819 $query->{where}->{enter_time} = {'>=' => $start_date};
820 $query->{where}->{enter_time} = {'<=' => $end_date};
821 my $users = $e->json_query($query);
822 @user_list = map {$_->{usr}} @$users;
825 print $FILE "<Collections>\n"; # append to the document as we have data
827 for my $user_id (@user_list) {
828 my $user_doc = XML::LibXML::Document->new;
829 my $root = $user_doc->createElement('User');
830 $user_doc->setDocumentElement($root);
832 my $user = $e->retrieve_actor_user([
839 'standing_penalties',
848 my $au_doc = $user->toXML({no_virt => 1, skip_fields => {au => ['passwd']}});
849 my $au_node = $au_doc->documentElement;
850 $user_doc->adoptNode($au_node);
851 $root->appendChild($au_node);
853 my $circ_ids = $e->search_action_circulation(
854 {usr => $user_id, circ_lib => $org_list, xact_finish => undef},
858 my $groc_ids = $e->search_money_grocery(
859 {usr => $user_id, billing_location => $org_list, xact_finish => undef},
863 my $res_ids = $e->search_booking_reservation(
864 {usr => $user_id, pickup_lib => $org_list, xact_finish => undef},
868 # get the sum owed an all transactions
870 if (@{[@$circ_ids, @$groc_ids, @$res_ids]}) {
871 $balance = $e->json_query({
873 { column => 'balance_owed',
879 where => {id => [@$circ_ids, @$groc_ids, @$res_ids]}
883 $balance = $balance ? $balance->{balance_owed} : '0';
885 my $xacts_node = $user_doc->createElement('Transactions');
886 my $balance_node = $user_doc->createElement('BalanceOwed');
887 $balance_node->appendChild($user_doc->createTextNode($balance));
888 $xacts_node->appendChild($balance_node);
889 $root->appendChild($xacts_node);
891 if ($$args{include_xacts}) {
892 my $xacts = $e->search_money_billable_transaction_summary(
893 {id => [@$circ_ids, @$groc_ids, @$res_ids]},
897 for my $xact (@$xacts) {
898 my $xact_node = $xact->toXML({no_virt => 1})->documentElement;
899 $user_doc->adoptNode($xact_node);
900 $xacts_node->appendChild($xact_node);
904 print $FILE $user_doc->documentElement->toString(1) . "\n";
907 print $FILE "\n</Collections>";
910 (my $complete_file = $file_name) =~ s|.tmp$|.xml|og;
912 unless (move($file_name, $complete_file)) {
913 $logger->error("collections: unable to move ".
914 "user_balance file $file_name => $complete_file : $@");
920 sub setup_batch_file {
923 my $location = $$args{location};
924 my $start_date = $$args{start_date};
925 my $end_date = $$args{end_date};
926 my $user_id = $$args{user_id};
928 my $conf = OpenSRF::Utils::SettingsClient->new;
929 my $dir_name = $conf->config_value(apps =>
930 'open-ils.collections' => app_settings => 'batch_file_dir');
933 $logger->error("collections: no batch_file_dir directory configured");
934 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
937 unless (-e $dir_name) {
938 eval { mkpath($dir_name); };
940 $logger->error("collections: unable to create batch_file_dir directory $dir_name : $@");
941 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
945 my $file_prefix = "${prefix}_" . DateTime->now->strftime('%F') . "_$location";
946 $file_prefix .= "_$start_date" if $start_date;
947 $file_prefix .= "_$end_date" if $end_date;
948 $file_prefix .= "_$user_id" if $user_id;
951 my $file_name = File::Spec->catfile($dir_name, "$file_prefix.tmp");
953 unless (open($FILE, '>', $file_name)) {
954 $logger->error("collections: unable to open user_balance_summary file $file_name : $@");
955 return OpenILS::Event->new('COLLECTIONS_FILE_ERROR');
958 return (undef, $file_prefix, $file_name, $FILE);
964 my $type = $p->payment_type;
965 $logger->debug("collect: fleshing workstation on payment $type : ".$p->id);
966 my $meth = "retrieve_money_$type";
967 $p = $e->$meth($p->id) or return (undef, $e->event);
969 $p->payment_type($type);
971 $e->retrieve_actor_workstation(
976 flesh_fields => { aws => [ 'owning_lib' ] }
981 } catch Error with {};
986 # --------------------------------------------------------------
987 # Collect all open circs for the user
988 # For each circ, see if any billings or payments were created
989 # during the given time period.
990 # --------------------------------------------------------------
991 sub fetch_circ_xacts {
995 my $start_date = shift;
996 my $end_date = shift;
1000 # at the specified org and each descendent org,
1001 # fetch the open circs for this user
1002 $U->walk_org_tree( $org,
1005 $logger->debug("collect: searching for open circs at " . $n->shortname);
1008 $e->search_action_circulation(
1022 my $active_ids = fetch_active($e, \@circs, $start_date, $end_date);
1024 for my $cid (@$active_ids) {
1026 $e->retrieve_action_circulation(
1032 circ => [ "billings", "payments", "circ_lib", 'target_copy' ]
1043 sub fetch_grocery_xacts {
1047 my $start_date = shift;
1048 my $end_date = shift;
1051 $U->walk_org_tree( $org,
1054 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
1057 $e->search_money_grocery(
1060 billing_location => $n->id,
1070 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
1072 for my $id (@$active_ids) {
1074 $e->retrieve_money_grocery(
1080 mg => [ "billings", "payments", "billing_location" ] }
1090 sub fetch_reservation_xacts {
1094 my $start_date = shift;
1095 my $end_date = shift;
1098 $U->walk_org_tree( $org,
1101 $logger->debug("collect: searching for open grocery xacts at " . $n->shortname);
1104 $e->search_booking_reservation(
1107 pickup_lib => $n->id,
1117 my $active_ids = fetch_active($e, \@xacts, $start_date, $end_date);
1119 for my $id (@$active_ids) {
1121 $e->retrieve_booking_reservation(
1127 bresv => [ "billings", "payments", "pickup_lib" ] }
1139 # --------------------------------------------------------------
1140 # Given a list of xact id's, this returns a list of id's that
1141 # had any activity within the given time span
1142 # --------------------------------------------------------------
1144 my( $e, $ids, $start_date, $end_date ) = @_;
1147 # { payment_ts => { between => [ $start, $end ] } } ' ;)
1150 for my $id (@$ids) {
1152 # see if any billings were created in the given time range
1153 my $bills = $e->search_money_billing (
1156 billing_ts => { between => [ $start_date, $end_date ] },
1165 # see if any payments were created in the given range
1166 $payments = $e->search_money_payment (
1169 payment_ts => { between => [ $start_date, $end_date ] },
1176 push( @active, $id ) if @$bills or @$payments;
1183 __PACKAGE__->register_method(
1184 method => 'create_user_note',
1185 api_name => 'open-ils.collections.patron_note.create',
1189 desc => q/ Adds a note to a patron's account /,
1192 desc => 'The authentication token',
1195 { name => 'user_barcode',
1196 desc => q/The patron's barcode/,
1200 desc => q/The title of the note/,
1205 desc => q/The text of the note/,
1212 Returns SUCCESS event on success, error event otherwise.
1220 sub create_user_note {
1221 my( $self, $conn, $auth, $user_barcode, $title, $note_txt ) = @_;
1223 my $e = new_editor(authtoken=>$auth, xact=>1);
1224 return $e->event unless $e->checkauth;
1225 return $e->event unless $e->allowed('UPDATE_USER'); # XXX Makre more specific perm for this
1227 return $e->event unless
1228 my $card = $e->search_actor_card({barcode=>$user_barcode})->[0];
1230 my $note = Fieldmapper::actor::usr_message->new;
1231 $note->usr($card->usr);
1232 $note->title($title);
1233 $note->sending_lib($e->requestor->home_ou);
1234 $note->create_date('now');
1236 $note->message($note_txt);
1238 $e->create_actor_usr_message($note) or return $e->event;
1240 return OpenILS::Event->new('SUCCESS');