From 0da257428ea3204c057883e7901406bd6369e57c Mon Sep 17 00:00:00 2001 From: charles Date: Wed, 4 Mar 2015 11:43:48 -0500 Subject: [PATCH] Bug 12768 - Replacement cost and processing fee management --- C4/Accounts.pm | 67 ++++++++-------- C4/Circulation.pm | 5 +- admin/itemtypes.pl | 16 +++- installer/data/mysql/kohastructure.sql | 2 + installer/data/mysql/sysprefs.sql | 2 + installer/data/mysql/updatedatabase.pl | 10 +++ .../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 ++++++++++++++++++++++ 10 files changed, 179 insertions(+), 44 deletions(-) create mode 100644 t/db_dependent/Circulation/Chargelostitem.t diff --git a/C4/Accounts.pm b/C4/Accounts.pm index c98013a..9fb316c 100644 --- a/C4/Accounts.pm +++ b/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); } - } } @@ -409,17 +402,17 @@ should be the empty string. #' # FIXME: In Koha 3.0 , the only account adjustment 'types' passed to this function # are : -# 'C' = CREDIT +# '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 +# 'N' = New Card fee +# 'F' = Fine +# 'A' = Account Management 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 +420,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 +428,7 @@ sub manualinvoice { or ( $type eq 'N' ) or ( $type eq 'M' ) ) { - $notifyid = 1; + $notifyid = 1 unless $skip_notify; } if ( $itemnum ) { @@ -473,9 +467,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 = ?" diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 14dfb0e..d08918f 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -3604,10 +3604,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(); @@ -3621,7 +3622,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 "; } diff --git a/admin/itemtypes.pl b/admin/itemtypes.pl index 1bfad90..11780c8 100755 --- a/admin/itemtypes.pl +++ b/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 ); } diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 08ef9ee..1f3d9e7 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -1270,6 +1270,8 @@ CREATE TABLE `itemtypes` ( -- defines the item types itemtype varchar(10) NOT NULL default '', -- unique key, a code associated with the item type description mediumtext, -- a plain text explanation of the item type rentalcharge double(16,4) default NULL, -- the amount charged when this item is checked out/issued + defaultreplacecost double(16,4) default NULL, -- default replacement cost + processfee double(16,4) default NULL, -- default text be recorded in the column note when the processing fee is applied notforloan smallint(6) default NULL, -- 1 if the item is not for loan, 0 if the item is available for loan imageurl varchar(200) default NULL, -- URL for the item type icon summary text, -- information from the summary field, may include HTML diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 9020b4b..0833235 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -340,6 +340,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'), @@ -456,6 +457,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'), ('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'), ('UseQueryParser','0',NULL,'If enabled, try to use QueryParser for queries.','YesNo'), diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 0ed56c6..b206b8f 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -9793,6 +9793,16 @@ if(CheckVersion($DBversion)) { SetVersion($DBversion); } +$DBversion = 'XXX'; +if ( CheckVersion($DBversion) ) { + $dbh->do("ALTER TABLE `itemtypes` ADD `defaultreplacecost` DOUBLE( 16, 4 ) NULL DEFAULT NULL AFTER `rentalcharge`"); + $dbh->do("ALTER TABLE `itemtypes` ADD `processfee` DOUBLE( 16, 4 ) NULL DEFAULT NULL AFTER `defaultreplacecost`"); + $dbh->do("INSERT INTO systempreferences (variable,value,explanation,type) VALUES('useDefaultReplacementCost',0,'default replacement cost defined in item type','YesNo')"); + $dbh->do("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')"); + print "Upgrade to $DBversion done (Bug 12768 - Replacement cost and processing fee management)\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index 1759f46..c8ac2a1 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -597,6 +597,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; } diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt index 2e61a16..f26a0c9 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/itemtypes.tt +++ b/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 diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index a296bf6..65309f9 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -632,6 +632,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:" diff --git a/t/db_dependent/Circulation/Chargelostitem.t b/t/db_dependent/Circulation/Chargelostitem.t new file mode 100644 index 0000000..e9d9694 --- /dev/null +++ b/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'" ); -- 1.9.1