From 6cdb8a2504aeb81c06a58978db6f0ab795ce3ae5 Mon Sep 17 00:00:00 2001 From: erickson Date: Mon, 9 Apr 2007 17:09:46 +0000 Subject: [PATCH] made backdating smarter by using the circ interval and parsing the backdate as an ISO time so we get timezones and not just YYYY-MM-DD ported claims-returned and lost code to CStoreEditor, made everything run inside the same transaction to prevent bugs with leaving transactions closed when they should be re-opened (among other things) mark-lost now supports org settings at any level in the tree new settings are whether to apply default item price on price==0 vs. price==null git-svn-id: svn://svn.open-ils.org/ILS/trunk@7130 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../src/perlmods/OpenILS/Application/AppUtils.pm | 43 ++- Open-ILS/src/perlmods/OpenILS/Application/Circ.pm | 358 +++++++++++---------- 2 files changed, 226 insertions(+), 175 deletions(-) diff --git a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm index 2783cdd6e9..5ba19024c1 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm @@ -1132,7 +1132,7 @@ sub patron_total_items_out { sub fetch_mbts { my $self = shift; my $id = shift; - my $editor = shift || new_editor(); + my $editor = shift || OpenILS::Utils::CStoreEditor->new; $id = $id->id if (ref($id)); @@ -1199,8 +1199,8 @@ sub make_mbts { $s->total_paid( sprintf('%0.2f', $tp / 100 ) ); $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) ); - $s->xact_type( 'grocery' ) if ($x->grocery); - $s->xact_type( 'circulation' ) if ($x->circulation); + $s->xact_type('grocery') if ($x->grocery); + $s->xact_type('circulation') if ($x->circulation); $logger->debug("Created mbts with balance_owed = ". $s->balance_owed); @@ -1211,9 +1211,42 @@ sub make_mbts { } +sub ou_ancestor_setting_value { + my $obj = ou_ancestor_setting(@_); + return ($obj) ? $obj->{value} : undef; +} + +sub ou_ancestor_setting { + my( $self, $orgid, $name, $e ) = @_; + $e = $e || OpenILS::Utils::CStoreEditor->new; + + do { + my $setting = $e->search_actor_org_unit_setting({org_unit=>$orgid, name=>$name})->[0]; + + if( $setting ) { + $logger->info("found org_setting $name at org $orgid : " . $setting->value); + return { org => $orgid, value => JSON->JSON2perl($setting->value) }; + } + + my $org = $e->retrieve_actor_org_unit($orgid) or return $e->event; + $orgid = $org->parent_ou or return undef; + + } while(1); + + return undef; +} - - + +# returns the ISO8601 string representation of the requested epoch in GMT +sub epoch2ISO8601 { + my( $self, $epoch ) = @_; + my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($epoch); + $year += 1900; $mon += 1; + my $date = sprintf( + '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d-00', + $year, $mon, $mday, $hour, $min, $sec); + return $date; +} 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm index b166f6b544..c17c8839fd 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm @@ -214,215 +214,233 @@ sub title_from_transaction { } -__PACKAGE__->register_method( - method => "set_circ_lost", - api_name => "open-ils.circ.circulation.set_lost", - NOTES => <<" NOTES"); - Params are login, barcode - login must have SET_CIRC_LOST perms - Sets a circulation to lost - NOTES __PACKAGE__->register_method( - method => "set_circ_lost", - api_name => "open-ils.circ.circulation.set_claims_returned", - NOTES => <<" NOTES"); - Params are login, barcode - login must have SET_CIRC_MISSING perms - Sets a circulation to lost - NOTES + method => "new_set_circ_lost", + api_name => "open-ils.circ.circulation.set_lost", + signature => q/ + Sets the copy and related open circulation to lost + @param auth + @param args : barcode + / +); -sub set_circ_lost { - my( $self, $client, $login, $args ) = @_; - my( $user, $circ, $copy, $evt ); - my $barcode = $$args{barcode}; - my $backdate = $$args{backdate}; +# --------------------------------------------------------------------- +# Sets a circulation to lost. updates copy status to lost +# applies copy and/or prcoessing fees depending on org settings +# --------------------------------------------------------------------- +sub new_set_circ_lost { + my( $self, $conn, $auth, $args ) = @_; - ( $user, $evt ) = $U->checkses($login); - return $evt if $evt; + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; - # Grab the related copy - ($copy, $evt) = $U->fetch_copy_by_barcode($barcode); - return $evt if $evt; + my $barcode = $$args{barcode}; + $logger->info("marking item lost $barcode"); - my $isclaims = $self->api_name =~ /claims_returned/; - my $islost = $self->api_name =~ /lost/; - my $session = $U->start_db_session(); + # --------------------------------------------------------------------- + # gather the pieces + my $copy = $e->search_asset_copy([ + {barcode=>$barcode, deleted=>'f'}, + {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])->[0] + or return $e->die_event; - # grab the circulation -# ( $circ ) = $U->fetch_open_circulation( $copy->id ); -# return 1 unless $circ; + my $owning_lib = + ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ? + $copy->circ_lib : $copy->call_number->owning_lib; - $circ = new_editor()->search_action_circulation( - { checkin_time => undef, target_copy => $copy->id } )->[0]; - return 1 unless $circ; + my $circ = $e->search_action_circulation( + {checkin_time => undef, target_copy => $copy->id} )->[0] + or return $e->die_event; - if($islost) { - $evt = _set_circ_lost($copy, $circ, $user, $session) if $islost; - return $evt if $evt; + $e->allowed('SET_CIRC_LOST', $circ->circ_lib) or return $e->die_event; + + # --------------------------------------------------------------------- + # fetch the related org settings + my $default_price = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_DEF_ITEM_PRICE, $e) || 0; + my $proc_fee = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_LOST_PROCESSING_FEE, $e) || 0; + my $charge_on_0 = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_CHARGE_LOST_ON_ZERO, $e) || 0; + my $void_overdue = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_VOID_OVERDUE_ON_LOST, $e) || 0; + + $logger->info("org settings: default price = $default_price, ". + "processing fee = $proc_fee, charge on 0 = $charge_on_0, void overdues = $void_overdue"); + + # --------------------------------------------------------------------- + # move the copy into LOST status + unless( $copy->status == OILS_COPY_STATUS_LOST ) { + $copy->status(OILS_COPY_STATUS_LOST); + $copy->editor($e->requestor->id); + $copy->edit_date('now'); + $e->update_asset_copy($copy) or return $e->die_event; } - if($isclaims) { - $evt = _set_circ_claims_returned( - $user, $circ, $session, $backdate ); - return $evt if $evt; + # --------------------------------------------------------------------- + # determine the appropriate item price to charge and create the billing + my $price = $copy->price; + $price = $default_price unless defined $price; + $price = 0 if $price < 0; + $price = $default_price if $price == 0 and $charge_on_0; + + if( $price > 0 ) { + my $evt = create_bill($e, $price, 'Lost Materials', $circ->id); + return $evt if $evt; + } + + # --------------------------------------------------------------------- + # if there is a processing fee, charge that too + if( $proc_fee > 0 ) { + my $evt = create_bill($e, $proc_fee, 'Lost Materials Processing Fee', $circ->id); + return $evt if $evt; + } + + # --------------------------------------------------------------------- + # mark the circ as lost and stop the fines + $circ->stop_fines(OILS_STOP_FINES_LOST); + $circ->stop_fines_time('now') unless $circ->stop_fines_time; + $e->update_action_circulation($circ) or return $e->die_event; + + # --------------------------------------------------------------------- + # void all overdue fines on this circ if configured + if( $void_overdue ) { + my $evt = void_overdues($e, $circ); + return $evt if $evt; + } + + $e->commit; + return 1; +} - } - $circ->stop_fines_time('now') unless $circ->stop_fines_time; - my $s = $session->request( - "open-ils.storage.direct.action.circulation.update", $circ )->gather(1); +sub create_bill { + my( $e, $amount, $type, $xactid ) = @_; - return $U->DB_UPDATE_FAILED($circ) unless defined($s); - $U->commit_db_session($session); + $logger->info("The system is charging $amount [$type] on xact $xactid"); - return 1; -} + # ----------------------------------------------------------------- + # make sure the transaction is not closed + my $xact = $e->retrieve_money_billable_transaction($xactid) + or return $e->die_event; -sub _set_circ_lost { - my( $copy, $circ, $reqr, $session ) = @_; + if( $xact->xact_finish ) { + my ($mbts) = $U->fetch_mbts($xactid, $e); + if( ($mbts->balance_owed + $amount) != 0 ) { + $logger->info("* re-opening xact $xactid"); + $xact->clear_xact_finish; + $e->update_money_billable_transaction($xact) + or return $e->die_event; + } + } - my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_LOST'); - return $evt if $evt; + # ----------------------------------------------------------------- + # now create the billing + my $bill = Fieldmapper::money::billing->new; + $bill->xact($xactid); + $bill->amount($amount); + $bill->billing_type($type); + $bill->note('SYSTEM GENERATED'); + $e->create_money_billing($bill) or return $e->die_event; - $logger->activity("user ".$reqr->id." marking copy ".$copy->id. - " lost for circ ". $circ->id. " and checking for necessary charges"); + return undef; +} - if( $copy->status ne OILS_COPY_STATUS_LOST ) { - $copy->status(OILS_COPY_STATUS_LOST); - $U->update_copy( - copy => $copy, - editor => $reqr->id, - session => $session); - } - # if the copy has a price defined and/or a processing fee, bill the patron - - # if the location that owns the copy has a processing fee, charge the user - my $owner = $U->fetch_copy_owner($copy->id); - $logger->info("circ fetching org settings for $owner ". - "to determine processing fee and default copy price"); - - my $settings = $U->simplereq( - 'open-ils.actor', 'open-ils.actor.org_unit.settings.retrieve', $owner ); - my $fee = $settings->{'circ.lost_materials_processing_fee'} || 0; - - # If the copy has a price configured, charge said price to the user - # otherwise use the default price - my $s = OILS_SETTING_DEF_ITEM_PRICE; - my $copy_price = $copy->price; - $copy_price = $settings->{$s} unless defined $copy_price; - if($copy_price and $copy_price > 0) { - $logger->debug("lost copy has a price of $copy_price"); - $evt = _make_bill($session, $copy_price, 'Lost Materials', $circ->id); - return $evt if $evt; - } +# ----------------------------------------------------------------- +# Voids overdue fines on the given circ. if a backdate is +# provided, then we only void back to the backdate +# ----------------------------------------------------------------- +sub void_overdues { + my( $e, $circ, $backdate ) = @_; + + my $bill_search = { + xact => $circ->id, + billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS + }; + + if( $backdate ) { + # ------------------------------------------------------------------ + # Fines for overdue materials are assessed up to, but not including, + # one fine interval after the fines are applicable. Here, we add + # one fine interval to the backdate to ensure that we are not + # voiding fines that were applicable before the backdate. + # ------------------------------------------------------------------ + my $interval = OpenSRF::Utils->interval_to_seconds($circ->fine_interval); + my $date = DateTime::Format::ISO8601->parse_datetime($backdate); + $backdate = $U->epoch2ISO8601($date->epoch + $interval); + $logger->info("applying backdate $backdate in overdue voiding"); + $$bill_search{billing_ts} = {'>=' => $backdate}; + } + + my $bills = $e->search_money_billing($bill_search); + + for my $bill (@$bills) { + next if $U->is_true($bill->voided); + $logger->info("voiding overdue bill ".$bill->id); + $bill->voided('t'); + $bill->void_time('now'); + $bill->voider($e->requestor->id); + my $n = $bill->note || ""; + $bill->note("$n\nSystem: VOIDED FOR BACKDATE"); + $e->update_money_billing($bill) or return $e->die_event; + } - if( $fee ) { - $evt = _make_bill($session, $fee, 'Lost Materials Processing Fee', $circ->id); - return $evt if $evt; - } - - $circ->stop_fines(OILS_STOP_FINES_LOST); return undef; } -sub _make_bill { - my( $session, $amount, $type, $xactid ) = @_; - $logger->info("The system is charging $amount ". - " [$type] for lost materials on circulation $xactid"); - # ----------------------------------------------------------------- - # make sure the transaction is not closed - my $e = new_editor(xact=>1); - my $xact = $e->retrieve_money_billable_transaction($xactid) - or return $e->die_event; - - if( $xact->xact_finish ) { - my ($mbts) = $U->fetch_mbts($xactid, $e); - if( ($mbts->balance_owed + $amount) != 0 ) { - $logger->info("re-opening xact $xactid"); - $xact->clear_xact_finish; - $e->update_money_billable_transaction($xact) - or return $e->die_event; - $e->commit; - } - } - - $e->rollback; # has no effect if it's already been comitted - # ----------------------------------------------------------------- +__PACKAGE__->register_method( + method => "set_circ_claims_returned", + api_name => "open-ils.circ.circulation.set_claims_returned", + signature => q/ + Sets the circ for the given item as claims returned + If a backdate is provided, overdue fines will be voided + back to the backdate + @param auth + @param args : barcode, backdate + / +); - my $bill = Fieldmapper::money::billing->new; - $bill->xact($xactid); - $bill->amount($amount); - $bill->billing_type($type); - $bill->note('SYSTEM GENERATED'); +sub set_circ_claims_returned { + my( $self, $conn, $auth, $args ) = @_; - my $id = $session->request( - 'open-ils.storage.direct.money.billing.create', $bill )->gather(1); + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; - return $U->DB_UPDATE_FAILED($bill) unless defined $id; - return undef; -} + my $barcode = $$args{barcode}; + my $backdate = $$args{backdate}; -sub _set_circ_claims_returned { - my( $reqr, $circ, $session, $backdate ) = @_; + $logger->info("marking circ for item $barcode as claims returned"); - my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_CLAIMS_RETURNED'); - return $evt if $evt; - $circ->stop_fines("CLAIMSRETURNED"); + my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0] + or return $e->die_event; - $logger->info("user ".$reqr->id. - " marking circ". $circ->id. " as claims returned with backdate $backdate"); + my $circ = $e->search_action_circulation( + {checkin_time => undef, target_copy => $copy->id})->[0] + or return $e->die_event; - # allow the caller to backdate the circulation and void any fines - # that occurred after the backdate - if($backdate) { - my $s = cr_handle_backdate($backdate, $circ, $reqr, $session ); - $logger->debug("backdate method returned $s"); - $circ->stop_fines_time(clense_ISO8601($backdate)) - } + $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib) + or return $e->die_event; - return undef; -} + $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED); + $circ->stop_fines_time('now') unless $circ->stop_fines_time; + $e->update_action_circulation($circ) or return $e->die_event; -sub cr_handle_backdate { - my( $backdate, $circ, $requestor, $session, $closecirc ) = @_; - - my $bd = $backdate; - $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; - $bd = "${bd}T23:59:59"; - - my $bills = $session->request( - "open-ils.storage.direct.money.billing.search_where.atomic", - billing_ts => { '>=' => $bd }, - xact => $circ->id, - billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS - )->gather(1); - - $logger->debug("backdate found ".scalar(@$bills)." bills to void"); - - if($bills) { - for my $bill (@$bills) { - unless( $U->is_true($bill->voided) ) { - $logger->info("voiding bill ".$bill->id); - $bill->voided('t'); - $bill->void_time('now'); - $bill->voider($requestor->id); - my $n = $bill->note || ""; - $bill->note($n . "\nSystem: VOIDED FOR BACKDATE"); - my $s = $session->request( - "open-ils.storage.direct.money.billing.update", $bill)->gather(1); - return $U->DB_UPDATE_FAILED($bill) unless $s; - } - } - } + if( $backdate ) { + # make it look like the circ stopped at the cliams returned time + $circ->stop_fines_time(clense_ISO8601($backdate)); + my $evt = void_overdues($e, $circ, $backdate); + return $evt if $evt; + } - return 100; + $e->commit; + return 1; } -- 2.11.0