@@ -, +, @@ --- C4/Accounts.pm | 72 ++++++++--------- C4/Circulation.pm | 5 +- admin/itemtypes.pl | 16 +++- ...-replacement_cost_processing_fee_management.sql | 2 + installer/data/mysql/sysprefs.sql | 2 + .../intranet-tmpl/prog/en/css/staff-global.css | 4 + .../prog/en/modules/admin/itemtypes.tt | 16 +++- .../en/modules/admin/preferences/circulation.pref | 12 +++ t/db_dependent/Circulation/Chargelostitem.t | 89 ++++++++++++++++++++++ 9 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_12768-replacement_cost_processing_fee_management.sql create mode 100644 t/db_dependent/Circulation/Chargelostitem.t --- a/C4/Accounts.pm +++ a/C4/Accounts.pm @@ -353,42 +353,35 @@ sub chargelostitem{ # a charge has been added # FIXME : if no replacement price, borrower just doesn't get charged? my $dbh = C4::Context->dbh(); - my ($borrowernumber, $itemnumber, $amount, $description) = @_; - + my ($issues, $description) = @_; + my $borrowernumber = $issues->{'borrowernumber'}; + my $itemnumber = $issues->{'itemnumber'}; + my $replacementprice = $issues->{'replacementprice'}; + my $defaultreplacecost = $issues->{'defaultreplacecost'}; + my $processfee = $issues->{'processfee'}; + my $usedefaultreplacementcost = C4::Context->preference("useDefaultReplacementCost"); + my $processingfeenote = C4::Context->preference("ProcessingFeeNote"); + + if ($usedefaultreplacementcost && $replacementprice == 0){ + $replacementprice = $defaultreplacecost; + } # first make sure the borrower hasn't already been charged for this item my $sth1=$dbh->prepare("SELECT * from accountlines WHERE borrowernumber=? AND itemnumber=? and accounttype='L'"); $sth1->execute($borrowernumber,$itemnumber); my $existing_charge_hashref=$sth1->fetchrow_hashref(); - # OK, they haven't + my $accountline; unless ($existing_charge_hashref) { - my $manager_id = 0; - $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; - # This item is on issue ... add replacement cost to the borrower's record and mark it returned - # Note that we add this to the account even if there's no replacement price, allowing some other - # process (or person) to update it, since we don't handle any defaults for replacement prices. - my $accountno = getnextacctno($borrowernumber); - my $sth2=$dbh->prepare("INSERT INTO accountlines - (borrowernumber,accountno,date,amount,description,accounttype,amountoutstanding,itemnumber,manager_id) - VALUES (?,?,now(),?,?,'L',?,?,?)"); - $sth2->execute($borrowernumber,$accountno,$amount, - $description,$amount,$itemnumber,$manager_id); - if ( C4::Context->preference("FinesLog") ) { - logaction("FINES", 'CREATE', $borrowernumber, Dumper({ - action => 'create_fee', - borrowernumber => $borrowernumber, - accountno => $accountno, - amount => $amount, - amountoutstanding => $amount, - description => $description, - accounttype => 'L', - itemnumber => $itemnumber, - manager_id => $manager_id, - })); + #add processing fee + if ($processfee > 0){ + manualinvoice($borrowernumber, $itemnumber, $description, 'PF', $processfee, $processingfeenote, 1); + } + #add replace cost + if ($replacementprice > 0){ + manualinvoice($borrowernumber, $itemnumber, $description, 'L', $replacementprice, undef, 1); } - } } @@ -408,18 +401,19 @@ should be the empty string. #' # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function -# are : -# 'C' = CREDIT -# 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere) -# 'N' = New Card fee -# 'F' = Fine -# 'A' = Account Management fee -# 'M' = Sundry -# 'L' = Lost Item +# are : +# 'C' = CREDIT +# 'FOR' = FORGIVEN (Formerly 'F', but 'F' is taken to mean 'FINE' elsewhere) +# 'N' = New Card fee +# 'F' = Fine +# 'A' = Account Management fee +# 'PF' = Processing fee +# 'M' = Sundry +# 'L' = Lost Item # sub manualinvoice { - my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note ) = @_; + my ( $borrowernumber, $itemnum, $desc, $type, $amount, $note, $skip_notify ) = @_; my $manager_id = 0; $manager_id = C4::Context->userenv->{'number'} if C4::Context->userenv; my $dbh = C4::Context->dbh; @@ -427,6 +421,7 @@ sub manualinvoice { my $insert; my $accountno = getnextacctno($borrowernumber); my $amountleft = $amount; + $skip_notify //= 0; if ( ( $type eq 'L' ) or ( $type eq 'F' ) @@ -434,7 +429,7 @@ sub manualinvoice { or ( $type eq 'N' ) or ( $type eq 'M' ) ) { - $notifyid = 1; + $notifyid = 1 unless $skip_notify; } if ( $itemnum ) { @@ -473,9 +468,8 @@ sub manualinvoice { } sub getcharges { - my ( $borrowerno, $timestamp, $accountno ) = @_; + my ( $borrowerno, $accountno ) = @_; my $dbh = C4::Context->dbh; - my $timestamp2 = $timestamp - 1; my $query = ""; my $sth = $dbh->prepare( "SELECT * FROM accountlines WHERE borrowernumber=? AND accountno = ?" --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -3629,10 +3629,11 @@ sub LostItem{ my ($itemnumber, $mark_returned) = @_; my $dbh = C4::Context->dbh(); - my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title + my $sth=$dbh->prepare("SELECT issues.*,items.*,biblio.title,itemtypes.defaultreplacecost, itemtypes.processfee FROM issues JOIN items USING (itemnumber) JOIN biblio USING (biblionumber) + JOIN itemtypes on items.itype = itemtypes.itemtype WHERE issues.itemnumber=?"); $sth->execute($itemnumber); my $issues=$sth->fetchrow_hashref(); @@ -3646,7 +3647,7 @@ sub LostItem{ defined($fix) or warn "_FixOverduesOnReturn($borrowernumber, $itemnumber...) failed!"; # zero is OK, check defined } if (C4::Context->preference('WhenLostChargeReplacementFee')){ - C4::Accounts::chargelostitem($borrowernumber, $itemnumber, $issues->{'replacementprice'}, "Lost Item $issues->{'title'} $issues->{'barcode'}"); + C4::Accounts::chargelostitem($issues, "Lost Item $issues->{'title'} $issues->{'barcode'}"); #FIXME : Should probably have a way to distinguish this from an item that really was returned. #warn " $issues->{'borrowernumber'} / $itemnumber "; } --- a/admin/itemtypes.pl +++ a/admin/itemtypes.pl @@ -116,6 +116,8 @@ if ( $op eq 'add_form' ) { itemtype => $itemtype, description => $data->{'description'}, rentalcharge => sprintf( "%.2f", $data->{'rentalcharge'} ), + defaultreplacecost => sprintf( "%.2f", $data->{'defaultreplacecost'} ), + processfee => sprintf( "%.2f", $data->{'processfee'} ), notforloan => $data->{'notforloan'}, imageurl => $data->{'imageurl'}, template => C4::Context->preference('template'), @@ -144,6 +146,8 @@ elsif ( $op eq 'add_validate' ) { UPDATE itemtypes SET description = ? , rentalcharge = ? + , defaultreplacecost = ? + , processfee = ? , notforloan = ? , imageurl = ? , summary = ? @@ -156,6 +160,8 @@ elsif ( $op eq 'add_validate' ) { $sth->execute( $input->param('description'), $input->param('rentalcharge'), + $input->param('defaultreplacecost'), + $input->param('processfee'), ( $input->param('notforloan') ? 1 : 0 ), ( $input->param('image') eq 'removeImage' ? '' : ( @@ -168,15 +174,15 @@ elsif ( $op eq 'add_validate' ) { $input->param('checkinmsg'), $input->param('checkinmsgtype'), $sip_media_type, - $input->param('itemtype') + $input->param('itemtype'), ); } else { # add a new itemtype & not modif an old my $query = " INSERT INTO itemtypes - (itemtype,description,rentalcharge, notforloan, imageurl, summary, checkinmsg, checkinmsgtype, sip_media_type) + (itemtype,description, rentalcharge, defaultreplacecost, processfee, notforloan, imageurl, summary, checkinmsg, checkinmsgtype, sip_media_type) VALUES - (?,?,?,?,?,?,?,?,?); + (?,?,?,?,?,?,?,?,?,?,?); "; my $sth = $dbh->prepare($query); my $image = $input->param('image'); @@ -184,6 +190,8 @@ elsif ( $op eq 'add_validate' ) { $input->param('itemtype'), $input->param('description'), $input->param('rentalcharge'), + $input->param('defaultreplacecost'), + $input->param('processfee'), $input->param('notforloan') ? 1 : 0, $image eq 'removeImage' ? '' : $image eq 'remoteImage' ? $input->param('remoteImage') : @@ -250,6 +258,8 @@ else { # DEFAULT foreach my $itemtype ( @{$results} ) { $itemtype->{imageurl} = getitemtypeimagelocation( 'intranet', $itemtype->{imageurl} ); $itemtype->{rentalcharge} = sprintf( '%.2f', $itemtype->{rentalcharge} ); + $itemtype->{defaultreplacecost} = sprintf( '%.2f', $itemtype->{defaultreplacecost} ); + $itemtype->{processfee} = sprintf( '%.2f', $itemtype->{processfee} ); push( @loop, $itemtype ); } --- a/installer/data/mysql/atomicupdate/bug_12768-replacement_cost_processing_fee_management.sql +++ a/installer/data/mysql/atomicupdate/bug_12768-replacement_cost_processing_fee_management.sql @@ -0,0 +1,2 @@ +INSERT INTO systempreferences (variable,value,explanation,type) VALUES ('useDefaultReplacementCost',0,'default replacement cost defined in item type','YesNo'); +INSERT INTO systempreferences (variable,value,explanation,type) VALUES ('ProcessingFeeNote','','Set the text to be recorded in the column note, table accountlines when the processing fee (defined in item type) is applied','textarea'); --- a/installer/data/mysql/sysprefs.sql +++ a/installer/data/mysql/sysprefs.sql @@ -344,6 +344,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('previousIssuesDefaultSortOrder','asc','asc|desc','Specify the sort order of Previous Issues on the circulation page','Choice'), ('printcirculationslips','1','','If ON, enable printing circulation receipts','YesNo'), ('PrintNoticesMaxLines','0','','If greater than 0, sets the maximum number of lines an overdue notice will print. If the number of items is greater than this number, the notice will end with a warning asking the borrower to check their online account for a full list of overdue items.','Integer'), +('ProcessingFeeNote', '', NULL, 'Set the text to be recorded in the column note, table accountlines when the processing fee (defined in item type) is applied', 'textarea'), ('QueryAutoTruncate','1',NULL,'If ON, query truncation is enabled by default','YesNo'), ('QueryFuzzy','1',NULL,'If ON, enables fuzzy option for searches','YesNo'), ('QueryStemming','1',NULL,'If ON, enables query stemming','YesNo'), @@ -461,6 +462,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('UseControlNumber','0','','If ON, record control number (w subfields) and control number (001) are used for linking of bibliographic records.','YesNo'), ('UseCourseReserves','0',NULL,'Enable the course reserves feature.','YesNo'), ('useDaysMode','Calendar','Calendar|Days|Datedue','Choose the method for calculating due date: select Calendar to use the holidays module, and Days to ignore the holidays module','Choice'), +('useDefaultReplacementCost', '0', NULL, 'default replacement cost defined in item type', 'YesNo'), ('useDischarge','','','Allows librarians to discharge borrowers and borrowers to request a discharge','YesNo'), ('UseICU','0','1','Tell Koha if ICU indexing is in use for Zebra or not.','YesNo'), ('UseKohaPlugins','0','','Enable or disable the ability to use Koha Plugins.','YesNo'), --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -604,6 +604,10 @@ fieldset.rows fieldset { width: 9em; } +.yui-b fieldset.rows ol.oladditemtype label, .yui-b fieldset.rows ol.oladditemtype span.label { + width: 13em; +} + .yui-b fieldset.rows div.hint { margin-left : 10.5em; } --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt @@ -162,7 +162,7 @@ Item types administration [% END %] -
    +
    1. [% IF ( notforloan ) %] @@ -175,7 +175,15 @@ Item types administration
    2. -
    3. + +
    4. + + +
    5. +
    6. + + +
    7. @@ -262,6 +270,8 @@ Item types administration Description Not for loan Charge + Default replacement cost + Processing fee (when lost) Checkin message Actions @@ -280,6 +290,8 @@ Item types administration [% loo.rentalcharge %] [% END %] + [% loo.defaultreplacecost %] + [% loo.processfee %] [% loo.checkinmsg | html_line_break %] Edit --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -626,6 +626,18 @@ Circulation: yes: Charge no: "Don't Charge" - the replacement price when a patron loses an item. + - + - pref: useDefaultReplacementCost + choices: + yes: use + no: "Don't use" + - the default replacement cost defined in item type. + - + - "Set the text to be recorded in the column 'note', table 'accountlines' when the processing fee (defined in item type) is applied." + - pref: ProcessingFeeNote + type: textarea + class: code + Self Checkout: - - "Include the following JavaScript on all pages in the web-based self checkout:" --- a/t/db_dependent/Circulation/Chargelostitem.t +++ a/t/db_dependent/Circulation/Chargelostitem.t @@ -0,0 +1,89 @@ +#!/usr/bin/perl +# +# This Koha test module is a stub! +# Add more tests here!!! + +use strict; +use warnings; +use Modern::Perl; + +use Test::MockModule; +use C4::Biblio; +use C4::Items; +use C4::Members; +use C4::Branch; +use C4::Category; +use C4::Circulation; +use MARC::Record; +use Test::More tests => 7; + +BEGIN { + use_ok('C4::Accounts'); +} + +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; +$dbh->do(q|DELETE FROM accountlines|); + +my $branchcode; +my $branch_created; +my @branches = keys %{ GetBranches() }; +if (@branches) { + $branchcode = $branches[0]; +} else { + $branchcode = 'B'; + ModBranch({ add => 1, branchcode => $branchcode, branchname => 'Branch' }); + $branch_created = 1; +} + +my %item_branch_infos = ( + homebranch => $branchcode, + holdingbranch => $branchcode, +); + +my ($biblionumber1) = AddBiblio(MARC::Record->new, ''); +my $itemnumber1 = AddItem({ barcode => '0101', %item_branch_infos }, $biblionumber1); +my $itemnumber2 = AddItem({ barcode => '0102', %item_branch_infos }, $biblionumber1); + +my ($biblionumber2) = AddBiblio(MARC::Record->new, ''); +my $itemnumber3 = AddItem({ barcode => '0203', %item_branch_infos }, $biblionumber2); + +my $categorycode; +my $category_created; +my @categories = C4::Category->all; +if (@categories) { + $categorycode = $categories[0]->{categorycode} +} else { + $categorycode = 'C'; + C4::Context->dbh->do( + "INSERT INTO categories(categorycode) VALUES(?)", undef, $categorycode); + $category_created = 1; +} + +my $borrowernumber = AddMember(categorycode => $categorycode, branchcode => $branchcode); +my $borrower = GetMember(borrowernumber => $borrowernumber); + +# Need to mock userenv for AddIssue +my $module = new Test::MockModule('C4::Context'); +$module->mock('userenv', sub { { branch => $branchcode } }); +AddIssue($borrower, '0101'); +AddIssue($borrower, '0203'); + +# Begin tests... +my $processfee = 10; +my $issues; +$issues = C4::Circulation::GetIssues({biblionumber => $biblionumber1}); +my $issue=$issues->[0]; +$issue->{'processfee'} = $processfee; +my $accountlineId = C4::Accounts::chargelostitem($issue, 'test'); + +my @accountline = C4::Accounts::getcharges($borrowernumber, $accountlineId); + +is( scalar(@accountline), 1, 'accountline should have 1 row' ); +is( int($accountline[0]->{amount}), $processfee, "The accountline amount should be precessfee value " ); +is( $accountline[0]->{accounttype}, 'PF', "The accountline accounttype should be PF " ); +is( $accountline[0]->{borrowernumber}, $borrowernumber, "The accountline borrowernumber should be the exemple borrownumber" ); +my $itemnumber = C4::Items::GetItemnumberFromBarcode('0101'); +is( $accountline[0]->{itemnumber}, $itemnumber, "The accountline itemnumber should the linked with barcode '0101'" ); +is( $accountline[0]->{description}, 'test', "The accountline description should be 'test'" ); --