From 5b09ca0d4908b1274617015ff5795f21673dbf0f Mon Sep 17 00:00:00 2001
From: Kyle M Hall <kyle@bywatersolutions.com>
Date: Tue, 15 Jul 2014 10:47:48 -0400
Subject: [PATCH] Bug 6427 - Add new Accounts related Koha modules

---
 Koha/Accounts.pm             |  557 ++++++++++++++++++++++++++++++++++++++++++
 Koha/Accounts/CreditTypes.pm |  125 ++++++++++
 Koha/Accounts/DebitTypes.pm  |  160 ++++++++++++
 Koha/Accounts/OffsetTypes.pm |   83 +++++++
 4 files changed, 925 insertions(+), 0 deletions(-)
 create mode 100644 Koha/Accounts.pm
 create mode 100644 Koha/Accounts/CreditTypes.pm
 create mode 100644 Koha/Accounts/DebitTypes.pm
 create mode 100644 Koha/Accounts/OffsetTypes.pm

diff --git a/Koha/Accounts.pm b/Koha/Accounts.pm
new file mode 100644
index 0000000..02165aa
--- /dev/null
+++ b/Koha/Accounts.pm
@@ -0,0 +1,557 @@
+package Koha::Accounts;
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+use Data::Dumper qw(Dumper);
+
+use C4::Context;
+use C4::Log qw(logaction);
+use Koha::DateUtils qw(get_timestamp);
+use Koha::Accounts::CreditTypes;
+use Koha::Accounts::DebitTypes;
+use Koha::Accounts::OffsetTypes;
+use Koha::Database;
+
+use vars qw($VERSION @ISA @EXPORT);
+
+BEGIN {
+    require Exporter;
+    @ISA    = qw(Exporter);
+    @EXPORT = qw(
+      AddDebit
+      AddCredit
+
+      VoidCredit
+
+      NormalizeBalances
+
+      RecalculateAccountBalance
+
+      DebitLostItem
+      CreditLostItem
+    );
+}
+
+=head1 NAME
+
+Koha::Accounts - Functions for dealing with Koha accounts
+
+=head1 SYNOPSIS
+
+use Koha::Accounts;
+
+=head1 DESCRIPTION
+
+The functions in this module deal with the monetary aspect of Koha,
+including looking up and modifying the amount of money owed by a
+patron.
+
+=head1 FUNCTIONS
+
+=head2 AddDebit
+
+my $debit = AddDebit({
+    borrower       => $borrower,
+    amount         => $amount,
+    [ type         => $type,        ]
+    [ itemnumber   => $itemnumber,  ]
+    [ issue_id     => $issue_id,    ]
+    [ description  => $description, ]
+    [ notes        => $notes,       ]
+    [ branchcode   => $branchcode,  ]
+    [ manager_id   => $manager_id,  ]
+    [ accruing     => $accruing,    ] # Default 0 if not accruing, 1 if accruing
+});
+
+Create a new debit for a given borrower. To standardize nomenclature, any charge
+against a borrower ( e.g. a fine, a new card charge, the cost of losing an item )
+will be referred to as a 'debit'.
+
+=cut
+
+sub AddDebit {
+    my ($params) = @_;
+
+    my $borrower    = $params->{borrower};
+    my $amount      = $params->{amount};
+    my $type        = $params->{type};
+    my $itemnumber  = $params->{itemnumber};
+    my $issue_id    = $params->{issue_id};
+    my $description = $params->{description};
+    my $notes       = $params->{notes};
+    my $branchcode  = $params->{branchcode};
+    my $manager_id  = $params->{manager_id};
+
+    my $userenv = C4::Context->userenv;
+
+    $branchcode ||=
+        $userenv
+      ? $userenv->{branch}
+      : undef;
+
+    $manager_id ||=
+        $userenv
+      ? $userenv->{number}
+      : undef;
+
+    my $accruing = $params->{accruing} || 0;
+
+    croak("Required parameter 'borrower' not passed in.")
+      unless ($borrower);
+    croak("Required parameter 'amount' not passed in.")
+      unless ($amount);
+    croak("Invalid debit type: '$type'!")
+      unless ( Koha::Accounts::DebitTypes::IsValid($type) );
+    croak("No issue id passed in for accruing debit!")
+      if ( $accruing && !$issue_id );
+
+    my $debit =
+      Koha::Database->new()->schema->resultset('AccountDebit')->create(
+        {
+            borrowernumber        => $borrower->borrowernumber(),
+            itemnumber            => $itemnumber,
+            issue_id              => $issue_id,
+            type                  => $type,
+            accruing              => $accruing,
+            amount_original       => $amount,
+            amount_outstanding    => $amount,
+            amount_last_increment => $amount,
+            description           => $description,
+            notes                 => $notes,
+            branchcode            => $branchcode,
+            manager_id            => $manager_id,
+            created_on            => get_timestamp(),
+        }
+      );
+
+    if ($debit) {
+        NormalizeBalances( { borrower => $borrower } );
+
+        if ( C4::Context->preference("FinesLog") ) {
+            logaction( "FINES", "CREATE_FEE", $debit->id,
+                Dumper( $debit->get_columns() ) );
+        }
+    }
+    else {
+        carp("Something went wrong! Debit not created!");
+    }
+
+    return $debit;
+}
+
+=head2 DebitLostItem
+
+my $debit = DebitLostItem({
+    borrower       => $borrower,
+    issue          => $issue,
+});
+
+DebitLostItem adds a replacement fee charge for the item
+of the given issue.
+
+=cut
+
+sub DebitLostItem {
+    my ($params) = @_;
+
+    my $borrower = $params->{borrower};
+    my $issue    = $params->{issue};
+
+    croak("Required param 'borrower' not passed in!") unless ($borrower);
+    croak("Required param 'issue' not passed in!")    unless ($issue);
+
+# Don't add lost debit if borrower has already been charged for this lost item before,
+# for this issue. It seems reasonable that a borrower could lose an item, find and return it,
+# check it out again, and lose it again, so we should do this based on issue_id, not itemnumber.
+    unless (
+        Koha::Database->new()->schema->resultset('AccountDebit')->search(
+            {
+                borrowernumber => $borrower->borrowernumber(),
+                issue_id       => $issue->issue_id(),
+                type           => Koha::Accounts::DebitTypes::Lost
+            }
+        )->count()
+      )
+    {
+        my $item = $issue->item();
+
+        $params->{accruing}   = 0;
+        $params->{type}       = Koha::Accounts::DebitTypes::Lost;
+        $params->{amount}     = $item->replacementprice();
+        $params->{itemnumber} = $item->itemnumber();
+        $params->{issue_id}   = $issue->issue_id();
+        $params->{description} =
+            "Lost Item "
+          . $issue->item()->biblio()->title() . " "
+          . $issue->item()->barcode();
+
+        #TODO: Shouldn't we have a default replacement price as a syspref?
+        if ( $params->{amount} ) {
+            return AddDebit($params);
+        }
+        else {
+            carp("Cannot add lost debit! Item has no replacement price!");
+        }
+    }
+}
+
+=head2 CreditLostItem
+
+my $debit = CreditLostItem(
+    {
+        borrower => $borrower,
+        debit    => $debit,
+    }
+);
+
+CreditLostItem creates a payment in the amount equal
+to the replacement price charge created by DebitLostItem.
+
+=cut
+
+sub CreditLostItem {
+    my ($params) = @_;
+
+    my $borrower = $params->{borrower};
+    my $debit    = $params->{debit};
+
+    croak("Required param 'borrower' not passed in!") unless ($borrower);
+    croak("Required param 'debit' not passed in!")
+      unless ($debit);
+
+    my $item =
+      Koha::Database->new()->schema->resultset('Item')
+      ->find( $debit->itemnumber() );
+    carp("No item found!") unless $item;
+
+    $params->{type}     = Koha::Accounts::CreditTypes::Found;
+    $params->{amount}   = $debit->amount_original();
+    $params->{debit_id} = $debit->debit_id();
+    $params->{notes}    = "Lost item found: " . $item->barcode();
+
+    return AddCredit($params);
+}
+
+=head2 AddCredit
+
+AddCredit({
+    borrower       => $borrower,
+    amount         => $amount,
+    [ branchcode   => $branchcode, ]
+    [ manager_id   => $manager_id, ]
+    [ debit_id     => $debit_id, ] # The primary debit to be paid
+    [ notes        => $notes, ]
+});
+
+Record credit by a patron. C<$borrowernumber> is the patron's
+borrower number. C<$credit> is a floating-point number, giving the
+amount that was paid.
+
+Amounts owed are paid off oldest first. That is, if the patron has a
+$1 fine from Feb. 1, another $1 fine from Mar. 1, and makes a credit
+of $1.50, then the oldest fine will be paid off in full, and $0.50
+will be credited to the next one.
+
+debit_id can be passed as a scalar or an array ref to make the passed
+in debit or debits the first to be credited.
+
+=cut
+
+sub AddCredit {
+    my ($params) = @_;
+
+    my $type            = $params->{type};
+    my $borrower        = $params->{borrower};
+    my $amount          = $params->{amount};
+    my $amount_received = $params->{amount_received};
+    my $debit_id        = $params->{debit_id};
+    my $notes           = $params->{notes};
+    my $branchcode      = $params->{branchcode};
+    my $manager_id      = $params->{manager_id};
+
+    my $userenv = C4::Context->userenv;
+
+    $branchcode ||=
+        $userenv
+      ? $userenv->{branch}
+      : undef;
+
+    $manager_id ||=
+        $userenv
+      ? $userenv->{number}
+      : undef;
+
+    unless ($borrower) {
+        croak("Required parameter 'borrower' not passed in");
+    }
+    unless ($amount) {
+        croak("Required parameter amount not passed in");
+    }
+
+    unless ($type && Koha::Accounts::CreditTypes::IsValid($type) ) {
+        carp("Invalid type $type passed in, assuming Payment");
+        $type = Koha::Accounts::CreditTypes::Payment;
+    }
+
+    # First, we make the credit. We'll worry about what we paid later on
+    my $credit =
+      Koha::Database->new()->schema->resultset('AccountCredit')->create(
+        {
+            borrowernumber   => $borrower->borrowernumber(),
+            type             => $type,
+            amount_received  => $amount_received,
+            amount_paid      => $amount,
+            amount_remaining => $amount,
+            notes            => $notes,
+            branchcode       => $branchcode,
+            manager_id       => $manager_id,
+            created_on       => get_timestamp(),
+        }
+      );
+
+    if ( C4::Context->preference("FinesLog") ) {
+        logaction( "FINES", "CREATE_PAYMENT", $credit->id,
+            Dumper( $credit->get_columns() ) );
+    }
+
+    # If we are given specific debits, pay those ones first.
+    if ($debit_id) {
+        my @debit_ids = ref($debit_id) eq "ARRAY" ? @$debit_id : $debit_id;
+        foreach my $debit_id (@debit_ids) {
+            my $debit =
+              Koha::Database->new()->schema->resultset('AccountDebit')
+              ->find($debit_id);
+
+            if ($debit) {
+                CreditDebit( { credit => $credit, debit => $debit } );
+            }
+            else {
+                carp("Invalid debit_id passed in!");
+            }
+        }
+    }
+
+    NormalizeBalances( { borrower => $borrower } );
+
+    return $credit;
+}
+
+=head2 CreditDebit
+
+$account_offset = CreditDebit({
+    credit => $credit,
+    debit => $debit,
+});
+
+Given a credit and a debit, this subroutine
+will pay the appropriate amount of the debit,
+update the debit's amount outstanding, the credit's
+amout remaining, and create the appropriate account
+offset.
+
+=cut
+
+sub CreditDebit {
+    my ($params) = @_;
+
+    my $credit = $params->{credit};
+    my $debit  = $params->{debit};
+
+    croak("Required parameter 'credit' not passed in!")
+      unless $credit;
+    croak("Required parameter 'debit' not passed in!")
+      unless $debit;
+
+    my $amount_to_pay =
+      ( $debit->amount_outstanding() > $credit->amount_remaining() )
+      ? $credit->amount_remaining()
+      : $debit->amount_outstanding();
+
+    if ( $amount_to_pay > 0 ) {
+        $debit->amount_outstanding(
+            $debit->amount_outstanding() - $amount_to_pay );
+        $debit->updated_on( get_timestamp() );
+        $debit->update();
+
+        $credit->amount_remaining(
+            $credit->amount_remaining() - $amount_to_pay );
+        $credit->updated_on( get_timestamp() );
+        $credit->update();
+
+        my $offset =
+          Koha::Database->new()->schema->resultset('AccountOffset')->create(
+            {
+                amount     => $amount_to_pay * -1,
+                debit_id   => $debit->id(),
+                credit_id  => $credit->id(),
+                created_on => get_timestamp(),
+            }
+          );
+
+        if ( C4::Context->preference("FinesLog") ) {
+            logaction( "FINES", "MODIFY", $offset->id,
+                Dumper( $offset->get_columns() ) );
+        }
+
+        return $offset;
+    }
+}
+
+=head2 RecalculateAccountBalance
+
+$account_balance = RecalculateAccountBalance({
+    borrower => $borrower
+});
+
+Recalculates a borrower's balance based on the
+sum of the amount outstanding for the borrower's
+debits minus the sum of the amount remaining for
+the borrowers credits.
+
+TODO: Would it be better to use af.amount_original - ap.amount_paid for any reason?
+      Or, perhaps calculate both and compare the two, for error checking purposes.
+=cut
+
+sub RecalculateAccountBalance {
+    my ($params) = @_;
+
+    my $borrower = $params->{borrower};
+    croak("Requred parameter 'borrower' not passed in!")
+      unless ($borrower);
+
+    my $debits =
+      Koha::Database->new()->schema->resultset('AccountDebit')
+      ->search( { borrowernumber => $borrower->borrowernumber() } );
+    my $amount_outstanding = $debits->get_column('amount_outstanding')->sum()
+      || 0;
+
+    my $credits =
+      Koha::Database->new()->schema->resultset('AccountCredit')
+      ->search( { borrowernumber => $borrower->borrowernumber() } );
+    my $amount_remaining = $credits->get_column('amount_remaining')->sum() || 0;
+
+    my $account_balance = $amount_outstanding - $amount_remaining;
+    $borrower->account_balance($account_balance);
+    $borrower->update();
+
+    return $account_balance;
+}
+
+=head2 VoidCredit
+    $account_balance = VoidCredit({ id => $credit_id });
+
+    Reverts a payment. All debits paid by this payment will be
+    updated such that the amount offset is reinstated for the debit.
+=cut
+
+sub VoidCredit {
+    my ($params) = @_;
+
+    my $id = $params->{id};
+
+    croak("No id passed in!") unless $id;
+
+    my $schema = Koha::Database->new()->schema();
+
+    my $credit = $schema->resultset('AccountCredit')->find($id);
+
+    foreach my $offset ( $credit->account_offsets() ) {
+        my $debit = $offset->debit();
+
+        $debit->amount_outstanding(
+            $debit->amount_outstanding() - $offset->amount() );
+        $debit->updated_on( get_timestamp() );
+        $debit->update();
+
+        Koha::Database->new()->schema->resultset('AccountOffset')->create(
+            {
+                amount     => $offset->amount() * -1,
+                debit_id   => $debit->id(),
+                credit_id  => $credit->id(),
+                created_on => get_timestamp(),
+                type       => Koha::Accounts::OffsetTypes::Void(),
+            }
+        );
+    }
+
+    $credit->amount_voided( $credit->amount_paid );
+    $credit->amount_paid(0);
+    $credit->amount_remaining(0);
+    $credit->updated_on( get_timestamp() );
+    $credit->update();
+
+    return RecalculateAccountBalance( { borrower => $credit->borrower() } );
+}
+
+=head2 NormalizeBalances
+
+    $account_balance = NormalizeBalances({ borrower => $borrower });
+
+    For a given borrower, this subroutine will find all debits
+    with an outstanding balance and all credits with an unused
+    amount remaining and will pay those debits with those credits.
+
+=cut
+
+sub NormalizeBalances {
+    my ($params) = @_;
+
+    my $borrower = $params->{borrower};
+
+    croak("Required param 'borrower' not passed in!") unless $borrower;
+
+    my $schema = Koha::Database->new()->schema();
+
+    my @credits = $schema->resultset('AccountCredit')->search(
+        {
+            borrowernumber   => $borrower->borrowernumber(),
+            amount_remaining => { '>' => '0' }
+        }
+    );
+
+    my @debits = $schema->resultset('AccountDebit')->search(
+        {
+            borrowernumber     => $borrower->borrowernumber(),
+            amount_outstanding => { '>' => '0' }
+        }
+    );
+
+    foreach my $credit (@credits) {
+        foreach my $debit (@debits) {
+            if (   $credit->amount_remaining()
+                && $debit->amount_outstanding() )
+            {
+                CreditDebit( { credit => $credit, debit => $debit } );
+            }
+        }
+    }
+
+    return RecalculateAccountBalance( { borrower => $borrower } );
+}
+
+1;
+__END__
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
diff --git a/Koha/Accounts/CreditTypes.pm b/Koha/Accounts/CreditTypes.pm
new file mode 100644
index 0000000..f061728
--- /dev/null
+++ b/Koha/Accounts/CreditTypes.pm
@@ -0,0 +1,125 @@
+package Koha::Accounts::CreditTypes;
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+=head1 NAME
+
+Koha::AccountsCreditTypes - Module representing the enumerated data types for account fees
+
+=head1 SYNOPSIS
+
+use Koha::Accounts::CreditTypes;
+
+my $type = Koha::Accounts::CreditTypes::Payment;
+
+=head1 DESCRIPTION
+
+The subroutines in this modules act as enumerated data types for the
+different credit types in Koha ( i.e. payments, writeoffs, etc. )
+
+=head1 FUNCTIONS
+
+=head2 IsValid
+
+This subroutine takes a given string and returns 1 if
+the string matches one of the data types, and 0 if not.
+
+FIXME: Perhaps we should use Class::Inspector instead of hard
+coding the subs? It seems like it would be a major trade off
+of speed just so we don't update something in two separate places
+in the same file.
+
+=cut
+
+sub IsValid {
+    my ($string) = @_;
+
+    my $is_valid =
+      (      $string eq Koha::Accounts::CreditTypes::Payment()
+          || $string eq Koha::Accounts::CreditTypes::WriteOff()
+          || $string eq Koha::Accounts::CreditTypes::Found()
+          || $string eq Koha::Accounts::CreditTypes::Credit()
+          || $string eq Koha::Accounts::CreditTypes::Forgiven() );
+
+    unless ($is_valid) {
+        $is_valid =
+          Koha::Database->new()->schema->resultset('AuthorisedValue')
+          ->count(
+            { category => 'MANUAL_CREDIT', authorised_value => $string } );
+    }
+
+    return $is_valid;
+}
+
+=head2 Credit
+
+=cut
+
+sub Credit {
+    return 'CREDIT';
+}
+
+=head2 Payment
+
+=cut
+
+sub Payment {
+    return 'PAYMENT';
+}
+
+=head2 Writeoff
+
+=cut
+
+sub WriteOff {
+    return 'WRITEOFF';
+}
+
+=head2 Writeoff
+
+=cut
+
+sub Found {
+    return 'FOUND';
+}
+
+=head2 Forgiven
+
+=cut
+
+sub Forgiven {
+    return 'FORGIVEN';
+}
+
+=head2 Fine reduction
+
+=cut
+
+sub FineReduction {
+    return 'FINE_REDUCTION';
+}
+
+1;
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
diff --git a/Koha/Accounts/DebitTypes.pm b/Koha/Accounts/DebitTypes.pm
new file mode 100644
index 0000000..435f6d6
--- /dev/null
+++ b/Koha/Accounts/DebitTypes.pm
@@ -0,0 +1,160 @@
+package Koha::Accounts::DebitTypes;
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+=head1 NAME
+
+Koha::Accounts::DebitTypes - Module representing an enumerated data type for account fees
+
+=head1 SYNOPSIS
+
+use Koha::Accounts::DebitTypes;
+
+my $type = Koha::Accounts::DebitTypes::Fine;
+
+=head1 DESCRIPTION
+
+The subroutines in this modules act as an enumerated data type
+for debit types ( stored in account_debits.type ) in Koha.
+
+=head1 FUNCTIONS
+
+=head2 IsValid
+
+This subroutine takes a given string and returns 1 if
+the string matches one of the data types, and 0 if not.
+
+=cut
+
+sub IsValid {
+    my ($string) = @_;
+
+    my $is_valid =
+      (      $string eq Koha::Accounts::DebitTypes::Fine()
+          || $string eq Koha::Accounts::DebitTypes::AccountManagementFee()
+          || $string eq Koha::Accounts::DebitTypes::Sundry()
+          || $string eq Koha::Accounts::DebitTypes::Lost()
+          || $string eq Koha::Accounts::DebitTypes::Hold()
+          || $string eq Koha::Accounts::DebitTypes::Rental()
+          || $string eq Koha::Accounts::DebitTypes::NewCard() );
+
+    unless ($is_valid) {
+        $is_valid =
+          Koha::Database->new()->schema->resultset('AuthorisedValue')
+          ->count( { category => 'MANUAL_INV', authorised_value => $string } );
+    }
+
+    return $is_valid;
+}
+
+=head2 Fine
+
+This data type represents a standard fine within Koha.
+
+A fine still accruing no longer needs to be differiated by type
+from a fine done accuring. Instead, that differentication is made
+by which table the fine exists in, account_fees_accruing vs account_fees_accrued.
+
+In addition, fines can be checked for correctness based on the issue_id
+they have. A fine in account_fees_accruing should always have a matching
+issue_id in the issues table. A fine done accruing will almost always have
+a matching issue_id in the old_issues table. However, in the case of an overdue
+item with fines that has been renewed, and becomes overdue again, you may have
+a case where a given issue may have a matching fine in account_fees_accruing and
+one or more matching fines in account_fees_accrued ( one for each for the first
+checkout and one each for any subsequent renewals )
+
+=cut
+
+sub Fine {
+    return 'FINE';
+}
+
+=head2 AccountManagementFee
+
+This fee type is usually reserved for payments for library cards,
+in cases where a library must charge a patron for the ability to
+check out items.
+
+=cut
+
+sub AccountManagementFee {
+    return 'ACCOUNT_MANAGEMENT_FEE';
+}
+
+=head2 Sundry
+
+This fee type is basically a 'misc' type, and should be used
+when no other fee type is more appropriate.
+
+=cut
+
+sub Sundry {
+    return 'SUNDRY';
+}
+
+=head2 Lost
+
+This fee type is used when a library charges for lost items.
+
+=cut
+
+sub Lost {
+    return 'LOST';
+}
+
+=head2 Hold
+
+This fee type is used when a library charges for holds.
+
+=cut
+
+sub Hold {
+    return 'HOLD';
+}
+
+=head2 Rental
+
+This fee type is used when a library charges a rental fee for the item type.
+
+=cut
+
+sub Rental {
+    return 'RENTAL';
+}
+
+=head2 NewCard
+
+This fee type is used when a library charges for replacement
+library cards.
+
+=cut
+
+sub NewCard {
+    return 'NEW_CARD';
+}
+
+1;
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
diff --git a/Koha/Accounts/OffsetTypes.pm b/Koha/Accounts/OffsetTypes.pm
new file mode 100644
index 0000000..8bf1021
--- /dev/null
+++ b/Koha/Accounts/OffsetTypes.pm
@@ -0,0 +1,83 @@
+package Koha::Accounts::OffsetTypes;
+
+# Copyright 2013 ByWater Solutions
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+=head1 NAME
+
+Koha::AccountsOffsetTypes - Module representing the enumerated data types for account fees
+
+=head1 SYNOPSIS
+
+use Koha::Accounts::OffsetTypes;
+
+my $type = Koha::Accounts::OffsetTypes::Dropbox;
+
+=head1 DESCRIPTION
+
+The subroutines in this modules act as enumerated data types for the
+different automatic offset types in Koha ( i.e. forgiveness, dropbox mode, etc )
+
+These types are used for account offsets that have no corrosponding account credit,
+e.g. automatic fine increments, dropbox mode, etc.
+
+=head1 FUNCTIONS
+
+=cut
+
+=head2 Dropbox
+
+Offset type for automatic fine reductions
+via dropbox mode.
+
+=cut
+
+sub Dropbox {
+    return 'DROPBOX';
+}
+
+=head2 Fine
+
+Indicates this offset was an automatically
+generated fine increment/decrement.
+
+=cut
+
+sub Fine {
+    return 'FINE';
+}
+
+=head2 VOID
+
+Indicates this offset was an automatically
+generated in response to a voided credit
+
+=cut
+
+sub Void {
+    return 'VOID';
+}
+
+1;
+
+=head1 AUTHOR
+
+Kyle M Hall <kyle@bywatersolutions.com>
+
+=cut
-- 
1.7.2.5