]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm
integrate the new booking.reservation billable transaction table with all the parts...
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Money.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2005  Georgia Public Library Service 
3 # Bill Erickson <billserickson@gmail.com>
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15
16 package OpenILS::Application::Circ::Money;
17 use base qw/OpenILS::Application/;
18 use strict; use warnings;
19 use OpenILS::Application::AppUtils;
20 my $apputils = "OpenILS::Application::AppUtils";
21 my $U = "OpenILS::Application::AppUtils";
22
23 use OpenSRF::EX qw(:try);
24 use OpenILS::Perm;
25 use Data::Dumper;
26 use OpenILS::Event;
27 use OpenSRF::Utils::Logger qw/:logger/;
28 use OpenILS::Utils::CStoreEditor qw/:funcs/;
29 use OpenILS::Utils::Penalty;
30
31 __PACKAGE__->register_method(
32     method => "make_payments",
33     api_name => "open-ils.circ.money.payment",
34     signature => {
35         desc => q/Create payments for a given user and set of transactions,
36             login must have CREATE_PAYMENT privileges.
37             If any payments fail, all are reverted back./,
38         params => [
39             {desc => 'Authtoken', type => 'string'},
40             {desc => q/Arguments Hash, supporting the following params:
41                 { 
42                     payment_type
43                     userid
44                     patron_credit
45                     note
46                     cc_args: {
47                         where_process   1 to use processor, !1 for out-of-band
48                         approval_code   (for out-of-band payment)
49                         type            (for out-of-band payment)
50                         number          (for call to payment processor)
51                         expire_month    (for call to payment processor)
52                         expire_year     (for call to payment processor)
53                         billing_first   (for call to payment processor)
54                         billing_last    (for call to payment processor)
55                         billing_address (for call to payment processor)
56                         billing_city    (for call to payment processor)
57                         billing_state   (for call to payment processor)
58                         billing_zip     (for call to payment processor)
59                         note            (if payments->{note} is blank, use this)
60                     },
61                     check_number
62                     payments: [ 
63                         [trans_id, amt], 
64                         [...]
65                     ], 
66                 }/, type => 'hash'
67             },
68         ]
69     }
70 );
71 sub make_payments {
72     my($self, $client, $auth, $payments) = @_;
73
74     my $e = new_editor(authtoken => $auth, xact => 1);
75     return $e->die_event unless $e->checkauth;
76
77     my $type = $payments->{payment_type};
78     my $user_id = $payments->{userid};
79     my $credit = $payments->{patron_credit} || 0;
80     my $drawer = $e->requestor->wsid;
81     my $note = $payments->{note};
82     my $cc_args = $payments->{cc_args};
83     my $check_number = $payments->{check_number};
84     my $total_paid = 0;
85     my $this_ou = $e->requestor->ws_ou;
86     my %orgs;
87
88     # unless/until determined by payment processor API
89     my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
90
91     my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
92
93     # A user is allowed to make credit card payments on his/her own behalf
94     # All other scenarious require permission
95     unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
96         return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
97     }
98
99     # first collect the transactions and make sure the transaction
100     # user matches the requested user
101     my %xacts;
102     for my $pay (@{$payments->{payments}}) {
103         my $xact_id = $pay->[0];
104         my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
105             or return $e->die_event;
106         
107         if($xact->usr != $user_id) {
108             $e->rollback;
109             return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
110         }
111
112         $xacts{$xact_id} = $xact;
113     }
114
115     my @payment_objs;
116
117     for my $pay (@{$payments->{payments}}) {
118         my $transid = $pay->[0];
119         my $amount = $pay->[1];
120         $amount =~ s/\$//og; # just to be safe
121         my $trans = $xacts{$transid};
122
123         $total_paid += $amount;
124
125         $orgs{$U->xact_org($transid, $e)} = 1;
126
127         # A negative payment is a refund.  
128         if( $amount < 0 ) {
129
130             # Negative credit card payments are not allowed
131             if($type eq 'credit_card_payment') {
132                 $e->rollback;
133                 return OpenILS::Event->new(
134                     'BAD_PARAMS', 
135                     note => q/Negative credit card payments not allowed/
136                 );
137             }
138
139             # If the refund causes the transaction balance to exceed 0 dollars, 
140             # we are in effect loaning the patron money.  This is not allowed.
141             if( ($trans->balance_owed - $amount) > 0 ) {
142                 $e->rollback;
143                 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
144             }
145
146             # Otherwise, make sure the refund does not exceed desk payments
147             # This is also not allowed
148             my $desk_total = 0;
149             my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
150             $desk_total += $_->amount for @$desk_payments;
151
152             if( (-$amount) > $desk_total ) {
153                 $e->rollback;
154                 return OpenILS::Event->new(
155                     'REFUND_EXCEEDS_DESK_PAYMENTS', 
156                     payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
157             }
158         }
159
160         my $payobj = "Fieldmapper::money::$type";
161         $payobj = $payobj->new;
162
163         $payobj->amount($amount);
164         $payobj->amount_collected($amount);
165         $payobj->xact($transid);
166         $payobj->note($note);
167         if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
168             $payobj->note($cc_args->{note});
169         }
170
171         if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
172         if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
173         if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
174         if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
175
176         # Store the last 4 digits of the CC number
177         if ($payobj->has_field('cc_number')) {
178             $payobj->cc_number(substr($cc_args->{number}, -4));
179         }
180         if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
181         if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
182         
183         # Note: It is important not to set approval_code
184         # on the fieldmapper object yet.
185
186         push(@payment_objs, $payobj);
187
188     } # all payment objects have been created and inserted. 
189
190     #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED  ###
191     $e->rollback;
192
193     # After we try to externally process a credit card (if desired), we'll
194     # open a new transaction.  We cannot leave one open while credit card
195     # processing might be happening, as it can easily time out the database
196     # transaction.
197     if($type eq 'credit_card_payment') {
198         $approval_code = $cc_args->{approval_code};
199         # If an approval code was not given, we'll need
200         # to call to the payment processor ourselves.
201         if ($cc_args->{where_process} == 1) {
202             return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
203                 if not $cc_args->{number};
204             my $response = $apputils->simplereq(
205                 'open-ils.credit',
206                 'open-ils.credit.process',
207                 {
208                     "desc" => $cc_args->{note},
209                     "amount" => $total_paid,
210                     "patron_id" => $user_id,
211                     "cc" => $cc_args->{number},
212                     "expiration" => sprintf(
213                         "%02d-%04d",
214                         $cc_args->{expire_month},
215                         $cc_args->{expire_year}
216                     ),
217                     "ou" => $this_ou,
218                     "first_name" => $cc_args->{billing_first},
219                     "last_name" => $cc_args->{billing_last},
220                     "address" => $cc_args->{billing_address},
221                     "city" => $cc_args->{billing_city},
222                     "state" => $cc_args->{billing_state},
223                     "zip" => $cc_args->{billing_zip},
224                 }
225             );
226
227             if (exists $response->{ilsevent}) {
228                 return $response;
229             }
230             if ($response->{statusCode} != 200) {
231                 $logger->info("Credit card payment for user $user_id " .
232                     "failed with message: " . $response->{statusText});
233                 return OpenILS::Event->new(
234                     'CREDIT_PROCESSOR_DECLINED_TRANSACTION',
235                     note => $response->{statusText}
236                 );
237             }
238             $approval_code = $response->{approvalCode};
239             $cc_type = $response->{cardType};
240             $cc_processor = $response->{processor};
241             $logger->info("Credit card payment processing for " .
242                 "user $user_id succeeded");
243         }
244         else {
245             return OpenILS::Event->new(
246                 'BAD_PARAMS', note => 'Need approval code'
247             ) if not $cc_args->{approval_code};
248         }
249     }
250
251     ### RE-OPEN TRANSACTION HERE ###
252     $e->xact_begin;
253
254     # create payment records
255     my $create_money_method = "create_money_" . $type;
256     for my $payment (@payment_objs) {
257         # update the transaction if it's done
258         my $amount = $payment->amount;
259         my $transid = $payment->xact;
260         my $trans = $xacts{$transid};
261         if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
262             # Any overpay on this transaction goes directly into patron
263             # credit making payment with existing patron credit.
264             $credit -= $amount if $type eq 'credit_payment';
265
266             $cred = -$cred;
267             $credit += $cred;
268             my $circ = $e->retrieve_action_circulation($transid);
269
270             if(!$circ || $circ->stop_fines) {
271                 # If this is a circulation, we can't close the transaction
272                 # unless stop_fines is set.
273                 $trans = $e->retrieve_money_billable_transaction($transid);
274                 $trans->xact_finish("now");
275                 if (!$e->update_money_billable_transaction($trans)) {
276                     $logger->warn("update_money_billable_transaction() " .
277                         "failed");
278                     $e->rollback;
279                     return OpenILS::Event->new(
280                         'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
281                         note => 'update_money_billable_transaction() failed'
282                     );
283                 }
284             }
285         }
286
287         $payment->approval_code($approval_code) if $approval_code;
288         $payment->cc_type($cc_type) if $cc_type;
289         $payment->cc_processor($cc_processor) if $cc_processor;
290         if (!$e->$create_money_method($payment)) {
291             $logger->warn("$create_money_method failed: " .
292                 Dumper($payment)); # won't contain CC number.
293             $e->rollback;
294             return OpenILS::Event->new(
295                 'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
296                 note => "$create_money_method failed"
297             );
298         }
299     }
300
301     my $evt = _update_patron_credit($e, $patron, $credit);
302     if ($evt) {
303         $logger->warn("_update_patron_credit() failed");
304         $e->rollback;
305         return OpenILS::Event->new(
306             'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
307             note => "_update_patron_credit() failed"
308         );
309     }
310
311     for my $org_id (keys %orgs) {
312         # calculate penalties for each of the affected orgs
313         $evt = OpenILS::Utils::Penalty->calculate_penalties(
314             $e, $user_id, $org_id
315         );
316         if ($evt) {
317             $logger->warn(
318                 "OpenILS::Utils::Penalty::calculate_penalties() failed"
319             );
320             $e->rollback;
321             return OpenILS::Event->new(
322                 'CREDIT_PROCESSOR_SUCCESS_WO_RECORD',
323                 note => "OpenILS::Utils::Penalty::calculate_penalties() failed"
324             );
325         }
326     }
327
328     $e->commit;
329     return 1;
330 }
331
332 sub _update_patron_credit {
333     my($e, $patron, $credit) = @_;
334     return undef if $credit == 0;
335     $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
336     return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
337     $e->update_actor_user($patron) or return $e->die_event;
338     return undef;
339 }
340
341
342 __PACKAGE__->register_method(
343     method    => "retrieve_payments",
344     api_name    => "open-ils.circ.money.payment.retrieve.all_",
345     notes        => "Returns a list of payments attached to a given transaction"
346     );
347 sub retrieve_payments {
348     my( $self, $client, $login, $transid ) = @_;
349
350     my( $staff, $evt ) =  
351         $apputils->checksesperm($login, 'VIEW_TRANSACTION');
352     return $evt if $evt;
353
354     # XXX the logic here is wrong.. we need to check the owner of the transaction
355     # to make sure the requestor has access
356
357     # XXX grab the view, for each object in the view, grab the real object
358
359     return $apputils->simplereq(
360         'open-ils.cstore',
361         'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
362 }
363
364
365 __PACKAGE__->register_method(
366     method    => "retrieve_payments2",
367     authoritative => 1,
368     api_name    => "open-ils.circ.money.payment.retrieve.all",
369     notes        => "Returns a list of payments attached to a given transaction"
370     );
371     
372 sub retrieve_payments2 {
373     my( $self, $client, $login, $transid ) = @_;
374
375     my $e = new_editor(authtoken=>$login);
376     return $e->event unless $e->checkauth;
377     return $e->event unless $e->allowed('VIEW_TRANSACTION');
378
379     my @payments;
380     my $pmnts = $e->search_money_payment({ xact => $transid });
381     for( @$pmnts ) {
382         my $type = $_->payment_type;
383         my $meth = "retrieve_money_$type";
384         my $p = $e->$meth($_->id) or return $e->event;
385         $p->payment_type($type);
386         $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
387             if $p->has_field('cash_drawer');
388         push( @payments, $p );
389     }
390
391     return \@payments;
392 }
393
394
395 __PACKAGE__->register_method(
396     method    => "create_grocery_bill",
397     api_name    => "open-ils.circ.money.grocery.create",
398     notes        => <<"    NOTE");
399     Creates a new grocery transaction using the transaction object provided
400     PARAMS: (login_session, money.grocery (mg) object)
401     NOTE
402
403 sub create_grocery_bill {
404     my( $self, $client, $login, $transaction ) = @_;
405
406     my( $staff, $evt ) = $apputils->checkses($login);
407     return $evt if $evt;
408     $evt = $apputils->check_perms($staff->id, 
409         $transaction->billing_location, 'CREATE_TRANSACTION' );
410     return $evt if $evt;
411
412
413     $logger->activity("Creating grocery bill " . Dumper($transaction) );
414
415     $transaction->clear_id;
416     my $session = $apputils->start_db_session;
417     my $transid = $session->request(
418         'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
419
420     throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
421
422     $logger->debug("Created new grocery transaction $transid");
423     
424     $apputils->commit_db_session($session);
425
426     my $e = new_editor(xact=>1);
427     $evt = _check_open_xact($e, $transid);
428     return $evt if $evt;
429     $e->commit;
430
431     return $transid;
432 }
433
434
435 __PACKAGE__->register_method(
436     method => 'fetch_reservation',
437     api_name => 'open-ils.circ.booking.reservation.retrieve'
438 );
439 sub fetch_grocery {
440     my( $self, $conn, $auth, $id ) = @_;
441     my $e = new_editor(authtoken=>$auth);
442     return $e->event unless $e->checkauth;
443     return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
444     my $g = $e->retrieve_booking_reservation($id)
445         or return $e->event;
446     return $g;
447 }
448
449 __PACKAGE__->register_method(
450     method => 'fetch_grocery',
451     api_name => 'open-ils.circ.money.grocery.retrieve'
452 );
453 sub fetch_grocery {
454     my( $self, $conn, $auth, $id ) = @_;
455     my $e = new_editor(authtoken=>$auth);
456     return $e->event unless $e->checkauth;
457     return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
458     my $g = $e->retrieve_money_grocery($id)
459         or return $e->event;
460     return $g;
461 }
462
463
464 __PACKAGE__->register_method(
465     method    => "billing_items",
466     authoritative => 1,
467     api_name    => "open-ils.circ.money.billing.retrieve.all",
468     notes        =><<"    NOTE");
469     Returns a list of billing items for the given transaction.
470     PARAMS( login, transaction_id )
471     NOTE
472
473 sub billing_items {
474     my( $self, $client, $login, $transid ) = @_;
475
476     my( $trans, $evt ) = $U->fetch_billable_xact($transid);
477     return $evt if $evt;
478
479     my $staff;
480     ($staff, $evt ) = $apputils->checkses($login);
481     return $evt if $evt;
482
483     if($staff->id ne $trans->usr) {
484         $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
485         return $evt if $evt;
486     }
487     
488     return $apputils->simplereq( 'open-ils.cstore',
489         'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
490 }
491
492
493 __PACKAGE__->register_method(
494     method    => "billing_items_create",
495     api_name    => "open-ils.circ.money.billing.create",
496     notes        =><<"    NOTE");
497     Creates a new billing line item
498     PARAMS( login, bill_object (mb) )
499     NOTE
500
501 sub billing_items_create {
502     my( $self, $client, $login, $billing ) = @_;
503
504     my $e = new_editor(authtoken => $login, xact => 1);
505     return $e->die_event unless $e->checkauth;
506     return $e->die_event unless $e->allowed('CREATE_BILL');
507
508     my $xact = $e->retrieve_money_billable_transaction($billing->xact)
509         or return $e->die_event;
510
511     # if the transaction was closed, re-open it
512     if($xact->xact_finish) {
513         $xact->clear_xact_finish;
514         $e->update_money_billable_transaction($xact)
515             or return $e->die_event;
516     }
517
518     my $amt = $billing->amount;
519     $amt =~ s/\$//og;
520     $billing->amount($amt);
521
522     $e->create_money_billing($billing) or return $e->die_event;
523     my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
524     return $evt if $evt;
525     $e->commit;
526
527     return $billing->id;
528 }
529
530
531 __PACKAGE__->register_method(
532     method        =>    'void_bill',
533     api_name        => 'open-ils.circ.money.billing.void',
534     signature    => q/
535         Voids a bill
536         @param authtoken Login session key
537         @param billid Id for the bill to void.  This parameter may be repeated to reference other bills.
538         @return 1 on success, Event on error
539     /
540 );
541 sub void_bill {
542     my( $s, $c, $authtoken, @billids ) = @_;
543
544     my $e = new_editor( authtoken => $authtoken, xact => 1 );
545     return $e->die_event unless $e->checkauth;
546     return $e->die_event unless $e->allowed('VOID_BILLING');
547
548     my %users;
549     for my $billid (@billids) {
550
551         my $bill = $e->retrieve_money_billing($billid)
552             or return $e->die_event;
553
554         my $xact = $e->retrieve_money_billable_transaction($bill->xact)
555             or return $e->die_event;
556
557         if($U->is_true($bill->voided)) {
558             $e->rollback;
559             return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
560         }
561
562         my $org = $U->xact_org($bill->xact, $e);
563         $users{$xact->usr} = {} unless $users{$xact->usr};
564         $users{$xact->usr}->{$org} = 1;
565
566         $bill->voided('t');
567         $bill->voider($e->requestor->id);
568         $bill->void_time('now');
569     
570         $e->update_money_billing($bill) or return $e->die_event;
571         my $evt = _check_open_xact($e, $bill->xact, $xact);
572         return $evt if $evt;
573     }
574
575     # calculate penalties for all user/org combinations
576     for my $user_id (keys %users) {
577         for my $org_id (keys %{$users{$user_id}}) {
578             OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
579         }
580     }
581     $e->commit;
582     return 1;
583 }
584
585
586 __PACKAGE__->register_method(
587     method        =>    'edit_bill_note',
588     api_name        => 'open-ils.circ.money.billing.note.edit',
589     signature    => q/
590         Edits the note for a bill
591         @param authtoken Login session key
592         @param note The replacement note for the bills we're editing
593         @param billid Id for the bill to edit the note of.  This parameter may be repeated to reference other bills.
594         @return 1 on success, Event on error
595     /
596 );
597 sub edit_bill_note {
598     my( $s, $c, $authtoken, $note, @billids ) = @_;
599
600     my $e = new_editor( authtoken => $authtoken, xact => 1 );
601     return $e->die_event unless $e->checkauth;
602     return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
603
604     for my $billid (@billids) {
605
606         my $bill = $e->retrieve_money_billing($billid)
607             or return $e->die_event;
608
609         $bill->note($note);
610         # FIXME: Does this get audited?  Need some way so that the original creator of the bill does not get credit/blame for the new note.
611     
612         $e->update_money_billing($bill) or return $e->die_event;
613     }
614     $e->commit;
615     return 1;
616 }
617
618
619 __PACKAGE__->register_method(
620     method        =>    'edit_payment_note',
621     api_name        => 'open-ils.circ.money.payment.note.edit',
622     signature    => q/
623         Edits the note for a payment
624         @param authtoken Login session key
625         @param note The replacement note for the payments we're editing
626         @param paymentid Id for the payment to edit the note of.  This parameter may be repeated to reference other payments.
627         @return 1 on success, Event on error
628     /
629 );
630 sub edit_payment_note {
631     my( $s, $c, $authtoken, $note, @paymentids ) = @_;
632
633     my $e = new_editor( authtoken => $authtoken, xact => 1 );
634     return $e->die_event unless $e->checkauth;
635     return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
636
637     for my $paymentid (@paymentids) {
638
639         my $payment = $e->retrieve_money_payment($paymentid)
640             or return $e->die_event;
641
642         $payment->note($note);
643         # FIXME: Does this get audited?  Need some way so that the original taker of the payment does not get credit/blame for the new note.
644     
645         $e->update_money_payment($payment) or return $e->die_event;
646     }
647
648     $e->commit;
649     return 1;
650 }
651
652 sub _check_open_xact {
653     my( $editor, $xactid, $xact ) = @_;
654
655     # Grab the transaction
656     $xact ||= $editor->retrieve_money_billable_transaction($xactid);
657     return $editor->event unless $xact;
658     $xactid ||= $xact->id;
659
660     # grab the summary and see how much is owed on this transaction
661     my ($summary) = $U->fetch_mbts($xactid, $editor);
662
663     # grab the circulation if it is a circ;
664     my $circ = $editor->retrieve_action_circulation($xactid);
665
666     # If nothing is owed on the transaction but it is still open
667     # and this transaction is not an open circulation, close it
668     if( 
669         ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
670         ( !$circ or $circ->stop_fines )) {
671
672         $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
673         $xact->xact_finish('now');
674         $editor->update_money_billable_transaction($xact)
675             or return $editor->event;
676         return undef;
677     }
678
679     # If money is owed or a refund is due on the xact and xact_finish
680     # is set, clear it (to reopen the xact) and update
681     if( $summary->balance_owed != 0 and $xact->xact_finish ) {
682         $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
683         $xact->clear_xact_finish;
684         $editor->update_money_billable_transaction($xact)
685             or return $editor->event;
686         return undef;
687     }
688     return undef;
689 }
690
691
692 __PACKAGE__->register_method (
693     method => 'fetch_mbts',
694     authoritative => 1,
695     api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
696 );
697 sub fetch_mbts {
698     my( $self, $conn, $auth, $id) = @_;
699
700     my $e = new_editor(xact => 1, authtoken=>$auth);
701     return $e->event unless $e->checkauth;
702     my ($mbts) = $U->fetch_mbts($id, $e);
703
704     my $user = $e->retrieve_actor_user($mbts->usr)
705         or return $e->die_event;
706
707     return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
708     $e->rollback;
709     return $mbts
710 }
711
712
713 __PACKAGE__->register_method(
714     method => 'desk_payments',
715     api_name => 'open-ils.circ.money.org_unit.desk_payments'
716 );
717 sub desk_payments {
718     my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
719     my $e = new_editor(authtoken=>$auth);
720     return $e->event unless $e->checkauth;
721     return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
722     my $data = $U->storagereq(
723         'open-ils.storage.money.org_unit.desk_payments.atomic',
724         $org, $start_date, $end_date );
725
726     $_->workstation( $_->workstation->name ) for(@$data);
727     return $data;
728 }
729
730
731 __PACKAGE__->register_method(
732     method => 'user_payments',
733     api_name => 'open-ils.circ.money.org_unit.user_payments'
734 );
735
736 sub user_payments {
737     my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
738     my $e = new_editor(authtoken=>$auth);
739     return $e->event unless $e->checkauth;
740     return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
741     my $data = $U->storagereq(
742         'open-ils.storage.money.org_unit.user_payments.atomic',
743         $org, $start_date, $end_date );
744     for(@$data) {
745         $_->usr->card(
746             $e->retrieve_actor_card($_->usr->card)->barcode);
747         $_->usr->home_ou(
748             $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
749     }
750     return $data;
751 }
752
753 1;