LP1335668 Modifications to ACQ Year End and Transfer Fund
authorLiam Whalen <liam.whalen@bc.libraries.coop>
Tue, 5 Aug 2014 19:17:43 +0000 (12:17 -0700)
committerLiam Whalen <liam.whalen@bc.libraries.coop>
Tue, 5 Aug 2014 19:23:47 +0000 (12:23 -0700)
These files modify how ACQ Year End opeartes, as well as the functioning
of the transfer_fund stored procedure.  The major changes rely upon a
change to acq.fund_allocation.  The change adds two new columns:
fund_amount and conversion_ratio.  fund_amount is the amount of money
allocated to the fund in the currency of the fund, and converson ratio
was the ratio used to make the conversion.  These columns are populated
by a new trigger on the acq.fund_allocation table.

The addition of the fund_amount column allows the exchange rates to be
modified without changing the values stored in each fund.  Currently, if
you modify exchange rates, the values already allocated to funds change.

The transfer_fund stored procedure has had some of its logic modified,
and a new stored procedure return_funds_to_source is added to simplify
the login in transfer_fund.  return_funds_to_source is used in cases
where the money is not being transferred to another fund, but, instead,
is being returned to the funding source.

As well, a small fix now allows funds with no money allocated to them to
be displayed as $0.00.

Finally, a number of PgTap tests are included to check these changes.

Signed-off-by: Liam Whalen <liam.whalen@bc.libraries.coop>
15 files changed:
Open-ILS/src/sql/Pg/200.schema.acq.sql
Open-ILS/src/sql/Pg/t/acq_fund_amount_test.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_fund_transfer.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_insert_fund_allocation_fund_amount.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_return_funds_to_source_test.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_rollover.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_rollover_distribution_formula.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only_and_distribution_formula.pg [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.add_fund_amount_and_conversion_ratio_to_fund_allocation.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.empty_fund_totals_now_0.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.insert_fund_allocation_fund_amount.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.return_funds_to_source.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.rollover_with_return_funds_to_source.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.transfer_fund.sql [new file with mode: 0644]

index 35be244..b66de13 100644 (file)
@@ -242,8 +242,41 @@ CREATE TABLE acq.fund_allocation (
     amount      NUMERIC NOT NULL,
     allocator   INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
     note        TEXT,
+    fund_amount NUMERIC,
+    conversion_ratio NUMERIC,
        create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
+
+-- Trigger to populate fund_amount and conversion ratio after INSERT on fund_allocation
+
+CREATE OR REPLACE FUNCTION acq.insert_fund_allocation_fund_amount() RETURNS TRIGGER AS $$
+
+DECLARE calculated_conversion_ratio                                NUMERIC;
+
+BEGIN
+       SELECT acq.exchange_ratio((SELECT currency_type FROM acq.funding_source WHERE id = NEW.funding_source), 
+               (SELECT currency_type FROM acq.fund WHERE id = NEW.fund)
+       )
+       INTO calculated_conversion_ratio;
+
+       UPDATE acq.fund_allocation
+       SET fund_amount = trunc((NEW.amount * calculated_conversion_ratio), 2)
+       WHERE fund = NEW.fund AND id = NEW.id;
+
+       UPDATE acq.fund_allocation
+       SET conversion_ratio = calculated_conversion_ratio
+       WHERE fund = NEW.fund AND id = NEW.id;
+
+       RETURN NEW;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER acq_insert_fund_allocation_fund_amount 
+       AFTER INSERT ON acq.fund_allocation
+       FOR EACH ROW
+       EXECUTE PROCEDURE acq.insert_fund_allocation_fund_amount();
+
 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
 
 CREATE TABLE acq.fund_allocation_percent
@@ -1221,22 +1254,364 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-CREATE OR REPLACE FUNCTION acq.transfer_fund(
-       old_fund   IN INT,
-       old_amount IN NUMERIC,     -- in currency of old fund
-       new_fund   IN INT,
-       new_amount IN NUMERIC,     -- in currency of new fund
+CREATE OR REPLACE FUNCTION acq.return_funds_to_source(
+       returning_fund_id   IN INT,
+       amount_to_return IN NUMERIC,                    -- in currency of returning
+                                                       -- fund
        user_id    IN INT,
-       xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
-       -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
+       xfer_note  IN TEXT,                             -- to be recorded in 
+                                                       -- acq.fund_transfer
+       return_funding_source IN INT DEFAULT NULL       -- if user wants to specify a
+                                                       -- funding source (see notes)
+) RETURNS VOID AS $$
+/* -------------------------------------------------------------------------------
+
+Function to return funds from a fund to a funding source or funding sources.
+
+A return is represented as a single entry in acq.fund_allocation, with a
+negative amount for the fund being debited.
+In some cases there may be more than one such pair of entries
+In order to pull the money from different funding sources, or more specifically
+from different funding source credits.  For each return there is also an
+entry in acq.fund_transfer.
+
+Since funding_source is a non-nullable column in acq.fund_allocation, we must
+choose a funding source for the returning money to return to.  This choice
+must meet two constraints, so far as possible:
+
+1. The amount returned to a given funding source must not exceed the
+amount allocated to the returning fund by the funding source.  To that end we
+compare the amount being returned to the amount allocated.
+
+2. We shouldn't return money that has already been spent or encumbered, as
+defined by the funding attribution process.  We attribute expenses to the
+oldest funding source credits first.  In order to avoid returning that
+attributed money, we reverse the priority, returning from the newest funding
+source credits first.  There can be no guarantee that this approach will
+avoid over returning from a fund, but no other approach can do any better.
+
+In this context the age of a funding source credit is defined by the
+deadline_date for credits with deadline_dates, and by the effective_date for
+credits without deadline_dates, with the proviso that credits with deadline_dates
+are all considered "older" than those without.
+
+----------
+
+In the signature for this function, there is one last parameter commented out,
+named "return_funding_soruce".  Correspondingly, the WHERE clause for the query
+driving the main loop has an OR clause commented out, which references the
+funding_source_in parameter.
+
+If these lines are uncommented, this function will allow the user optionally to
+restrict a fund return to a specified funding source.  If the source
+parameter is left NULL, then there will be no such restriction.
+
+Need to lock the acq.currency_rate table while this function is being
+executed.  Otherwise, the amount in new_amount may be less than an amount deducted
+if the currencies in the table fluctuate to a large degree while this is running.
+
+------------------------------------------------------------------------------- */ 
+DECLARE
+
+       returning_fund_currency                                         TEXT;
+
+       -- kept in the currency of return fund
+       funds_remaining_to_be_returned                                  NUMERIC;
+
+       -- kept in currency of funding source
+       current_amount_to_return                                        NUMERIC;
+
+       -- kept in currency of fund
+       fund_amount_to_return                                           NUMERIC;
+
+       -- in currency of funding source
+       funding_source_credit                                           NUMERIC;
+
+       -- kept in currency of returning fund
+       amount_allocated_by_funding_source                              NUMERIC;
+
+       -- kept in currency of returning fund
+       amount_currently_allocated_to_returning_fund                    NUMERIC;
+
+       -- a JOIN of acq.funding_source and acq.ordered_funding_source_credit
+       source                                                          RECORD;
+
+BEGIN
+       --
+       -- We need to lock this table because the exchange rates could be modifed
+       -- while we are doing a transfer, which would create unpredictable results
+       --
+       LOCK acq.exchange_rate
+       IN EXCLUSIVE MODE;
+       --
+       -- Sanity checks
+       --
+       IF returning_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: returning fund id is NULL';
+       END IF;
+       --
+       IF amount_to_return IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_soruce: amount to return is NULL';
+       END IF;
+       --
+       IF user_id IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: user id is NULL';
+       END IF;
+       --
+       IF amount_to_return = 0 THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source amount to return is 0';
+       END IF;
+
+       -- Ensure there is enough money in the fund to cover the amount to return
+       -- We are returning the balance plus any funds currently encumbered
+       -- So we use the Spent Balance because it includes the Total Encumbered
+       -- as well as the the Total Spent
+       SELECT amount
+       INTO amount_currently_allocated_to_returning_fund
+       FROM acq.fund_spent_balance
+       WHERE fund = returning_fund_id;
+
+       IF amount_to_return > amount_currently_allocated_to_returning_fund THEN
+           RAISE EXCEPTION 'Cannot return more money than is currently allocted to the fund. fund id: % has % allocated to it, and the acq.return_funds_to_source is trying to debit it for %', returning_fund_id, amount_currently_allocated_to_returning_fund, amount_to_return;
+       END IF;
+
+       --
+       -- Initialize the amounts to be returned, each denominated
+       -- in the currency of its respective fund.  They will be
+       -- reduced on each iteration of the loop.
+       --
+       funds_remaining_to_be_returned := amount_to_return;
+       --
+       -- Get the currency types of the old and new funds.
+       --
+       SELECT
+               currency_type
+       INTO
+               returning_fund_currency
+       FROM
+               acq.fund
+       WHERE
+               id = returning_fund_id;
+       --
+       IF returning_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: old fund currency is not defined for fund id: %', returning_fund_id;
+       END IF;
+       --
+       -- Identify the funding source(s) from which we want to transfer the money.
+       -- The principle is that we want to transfer the newest money first, because
+       -- we spend the oldest money first.  The priority for spending is defined
+       -- by a sort of the view acq.ordered_funding_source_credit.
+       --
+
+       -- There is no guarantee that the currency types in the funding source will be one of
+       -- the two types passed in to this function.  This needs to be fixed.
+
+       -- Add code to determine if there are enough funds left in the fund to transfer the 
+       -- amount requested.
+       FOR source IN
+               SELECT
+                       ofsc.id,
+                       ofsc.funding_source,
+                       ofsc.amount,
+                       ofsc.amount * acq.exchange_ratio(fs.currency_type, returning_fund_currency)
+                               AS amount_credited_to_funding_source,
+                       -- We store the amount to retreive when amount_to_receive IS NOT NULL and the
+                       -- fs.currency_type is the same currency as the amount to receive.  We use the
+                       -- inverted_conversion_ratio because we are concerned with having the amount in 
+                       -- the currency of the returning fund.  The AS amount_credited_to_fund value is
+                       -- converted using the source to returning fund currencies.  
+                       fs.currency_type
+               FROM
+                       acq.ordered_funding_source_credit AS ofsc,
+                       acq.funding_source fs
+               WHERE
+                       ofsc.funding_source = fs.id
+                       AND ofsc.funding_source IN
+                       (
+                               SELECT funding_source
+                               FROM acq.fund_allocation
+                               WHERE fund = returning_fund_id
+                       )
+                       AND
+                       (
+                               ofsc.funding_source = return_funding_source
+                               OR return_funding_source IS NULL
+                       )
+               ORDER BY
+                       ofsc.sort_priority DESC,
+                       ofsc.sort_date DESC,
+                       ofsc.id DESC
+       LOOP
+               --
+               -- Determine how much money the returning fund got from this funding source,
+               -- denominated in the currency types of the fund.
+               -- This result may reflect transfers from previous iterations.
+               -- Do not return negative values, the fact that they are negative
+               -- indicates that the amount has already been returned.
+               --
+               SELECT
+                       COALESCE( sum( amount ), 0 )
+               INTO
+                       amount_allocated_by_funding_source           -- in currency of the funding source
+               FROM
+                       acq.fund_allocation
+               WHERE
+                       fund = returning_fund_id
+                       AND funding_source = source.funding_source
+                       AND amount > 0;
+               --      
+               -- Determine how much to transfer from this credit, in the currency
+               -- of the funding source.   Begin with the amount remaining to be returned.
+               --
+               current_amount_to_return := funds_remaining_to_be_returned *
+                       acq.exchange_ratio(returning_fund_currency, source.currency_type);
+
+               --
+               -- Can't attribute more than was allocated to the fund:
+               --
+               IF current_amount_to_return > amount_allocated_by_funding_source THEN
+                       current_amount_to_return := amount_allocated_by_funding_source;
+               END IF;
+               --
+               -- Can't attribute more than the amount of the current credit from the funding source:
+               --
+               IF current_amount_to_return > source.amount THEN
+                       current_amount_to_return := source.amount;
+               END IF;
+               --
+               current_amount_to_return := trunc( current_amount_to_return, 2 );
+               --
+               -- current_amount_to_return is in the currency of the funding source
+               -- but funds_remaining_to_be_returned is in the currency of the fund.
+               -- So, convert current_amount_to_return into the value of the fund in
+               -- order to keep the funds_reminaing_to_be_retunred in the currency
+               -- of the fund.
+               --
+               fund_amount_to_return = current_amount_to_return *
+                       acq.exchange_ratio(source.currency_type, returning_fund_currency);
+
+               funds_remaining_to_be_returned := funds_remaining_to_be_returned - fund_amount_to_return;
+
+               --
+               -- Determine the amount to be deducted, if any,
+               -- from the old allocation.
+               --
+
+               --
+               -- Either the entire amount is being credited (the case where 
+               -- current_amount_to_return is less than the 
+               -- amount_allocated_by_funding_soruce and source.fund_amount), and we're using the whole allocation 
+               -- from the current
+               -- funding source credit, or the amount left allocated in this funding 
+               -- source credit if it is 
+               -- less than the original amount of the credit (due to previous debits), so 
+               -- use that amount 
+               -- directly.  In all these cases these values are represented by current_amount_to_return.
+               -- We need to translate the amount into the source.currency_type regardless of the 
+               -- condiitons above because we are crediting to the source not the fund.
+               --
+
+               funding_source_credit := trunc((- current_amount_to_return), 2);
+
+               IF returning_fund_currency IS NULL THEN
+                       RAISE EXCEPTION 'returning_fund_currency IS NULL';
+               END IF;
+
+               IF current_amount_to_return IS NULL THEN
+                       RAISE EXCEPTION 'current_amount_to_return IS NULL % - %', amount_allocated_by_funding_source, source.amount_credited_to_fund;
+               END IF;
+               -- Ensure the addition is less than 0
+               IF funding_source_credit < 0 THEN
+                       --
+                       -- Insert negative allocation for old fund in fund_allocation,
+                       -- converted into the currency of the funding source
+                       --
+                       INSERT INTO acq.fund_allocation (
+                               funding_source,
+                               fund,
+                               amount,
+                               allocator,
+                               note
+                       ) VALUES (
+                               source.funding_source,
+                               returning_fund_id,
+                               funding_source_credit,
+                               user_id,
+                               'Returning funds to the source'
+                       );
+               ELSE
+                       RAISE EXCEPTION 'funding_source_credit of % is greater than 0: % -- %', funding_source_credit, current_amount_to_return, funds_remaining_to_be_returned;
+               END IF;
+               --
+               IF trunc( current_amount_to_return, 2 ) > 0 THEN
+                       --
+                       -- Insert row in fund_transfer, using amounts in the currency of the fund
+                       --
+                       INSERT INTO acq.fund_transfer (
+                               src_fund,
+                               src_amount,
+                               dest_fund,
+                               dest_amount,
+                               transfer_user,
+                               note,
+                               funding_source_credit
+                       ) VALUES (
+                               returning_fund_id,
+                               trunc( fund_amount_to_return, 2 ),
+                               NULL,
+                               NULL,
+                               user_id,
+                               xfer_note,
+                               source.id
+                       );
+               END IF;
+               --
+               -- It should be impossible for funds_remaining_to_be_returned to be less than 0.
+               --
+               IF trunc(funds_remaining_to_be_returned, 2) = 0.00 THEN
+                       EXIT;                   -- Nothing more to be transferred
+               END IF;
+       END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+DROP FUNCTION acq.transfer_fund (INT, NUMERIC, INT, NUMERIC, INT, TEXT);
+
+CREATE OR REPLACE FUNCTION acq.transfer_fund(
+    transferring_fund_id            IN INT, 
+    amount_to_transfer              IN NUMERIC,             -- In currency of 
+                                                                   -- transferring fund
+    receiving_fund_id               IN INT,
+    user_id                         IN INT,                 -- User initiating 
+                                                                   -- the transfer
+    xfer_note                       IN TEXT,                -- To be recorded in 
+                                                                   -- acq.fund_transfer
+    amount_to_receive               IN NUMERIC DEFAULT NULL,-- In currency of
+                                                                   -- receiving fund.
+                                                                   -- It can be null in
+                                                                   -- cases where the
+                                                                   -- amount to transfer
+                                                                   -- will be calculated
+                                                                   -- within the function.
+    transferring_funding_source     IN INT DEFAULT NULL,    -- If user wants to 
+                                                                   -- specify a funding
+                                                                   -- source (see notes)
+    rollover_transfer               IN BOOLEAN DEFAULT FALSE-- If true, then
+                                                                   -- we make sure the
+                                                                   -- amount being
+                                                                   -- transferred is not
+                                                                   -- greater than the
+                                                                   -- spent balance as
+                                                                   -- oposed to the 
+                                                                   -- combined balance
 ) RETURNS VOID AS $$
 /* -------------------------------------------------------------------------------
 
 Function to transfer money from one fund to another.
 
 A transfer is represented as a pair of entries in acq.fund_allocation, with a
-negative amount for the old (losing) fund and a positive amount for the new
-(gaining) fund.  In some cases there may be more than one such pair of entries
+negative amount for the transferring fund and a positive amount for the 
+receiving fund.  In some cases there may be more than one such pair of entries
 in order to pull the money from different funding sources, or more specifically
 from different funding source credits.  For each such pair there is also an
 entry in acq.fund_transfer.
@@ -1264,210 +1639,414 @@ are all considered "older" than those without.
 ----------
 
 In the signature for this function, there is one last parameter commented out,
-named "funding_source_in".  Correspondingly, the WHERE clause for the query
+named "transferring_funding_source".  Correspondingly, the WHERE clause for the query
 driving the main loop has an OR clause commented out, which references the
-funding_source_in parameter.
+transferring_funding_source parameter.
 
 If these lines are uncommented, this function will allow the user optionally to
 restrict a fund transfer to a specified funding source.  If the source
 parameter is left NULL, then there will be no such restriction.
 
-------------------------------------------------------------------------------- */ 
+------------------------------------------------------------------------------- */
 DECLARE
-       same_currency      BOOLEAN;
-       currency_ratio     NUMERIC;
-       old_fund_currency  TEXT;
-       old_remaining      NUMERIC;  -- in currency of old fund
-       new_fund_currency  TEXT;
-       new_fund_active    BOOLEAN;
-       new_remaining      NUMERIC;  -- in currency of new fund
-       curr_old_amt       NUMERIC;  -- in currency of old fund
-       curr_new_amt       NUMERIC;  -- in currency of new fund
-       source_addition    NUMERIC;  -- in currency of funding source
-       source_deduction   NUMERIC;  -- in currency of funding source
-       orig_allocated_amt NUMERIC;  -- in currency of funding source
-       allocated_amt      NUMERIC;  -- in currency of fund
-       source             RECORD;
+       -- Are the transferring and receiving funds the same currency?
+       same_currency                                                   BOOLEAN;
+
+       -- The ratio between the transferring fund and the receiving 
+       -- fund.  Other ratios may be needed due to other currencies 
+       -- in the funding sources.
+       conversion_ratio                                                NUMERIC;
+
+       -- The ratio between the receiving fund and the transferring 
+       -- fund.  Other ratios may be needed due to other currencies 
+       -- in the funding sources.
+       inverted_conversion_ratio                                       NUMERIC;
+
+       transferring_fund_currency                                      TEXT;
+
+       -- Kept in the currency of transferring fund
+       funds_remaining_to_be_transferred                               NUMERIC;
+
+       -- The transferring fund must be active if the amount to 
+       -- transfer isnegative because it will have to accept funds
+       transferring_fund_active                                        BOOLEAN;
+
+       receiving_fund_currency                                         TEXT;
+
+       -- Kept in currency of recieving fund
+       funds_remaining_to_be_received                                  NUMERIC;
+
+       -- The receiving fund must be active to accept funds
+       receiving_fund_active                                           BOOLEAN;
+
+       -- Kept in currency of the funding source
+       current_amount_to_transfer                                      NUMERIC;
+
+       -- In the currency of the fund
+       fund_amount_to_transfer                                         NUMERIC;
+
+       -- In currency of recevinig fund
+       current_amount_to_receive                                       NUMERIC;
+
+       -- In currency of funding source
+       funding_source_credit                                           NUMERIC;
+
+       -- Ratio between the transferring fund and the source
+       source_credit_conversion_ratio                                  NUMERIC;  
+
+       -- In currency of funding source
+       funding_source_debit                                            NUMERIC;
+
+       -- Ratio between the receiving fund and the source 
+       source_debit_conversion_ratio                                   NUMERIC;
+
+       -- Kept in currency of transferring fund
+       amount_allocated_by_funding_source                              NUMERIC;
+
+       -- Kept in currency of the transferring fund
+       amount_credited                                                 NUMERIC;        
+
+       -- Kept in currency of transferring fund
+       amount_currently_allocated_to_transferring_fund                 NUMERIC;
+
+       -- A JOIN of acq.funding_source and 
+       -- acq.ordered_funding_source_credit
+       source                                                          RECORD;
+
+       -- Used to swap funds when a negative amount is supplied 
+       -- to amount_to_transfer
+       temp_fund_id                                                    INTEGER;
+
+       -- Used to sawp currencies when a negative amount is 
+       -- supplied to amount_to_transfer
+       temp_currency                                                   TEXT;
+
+       -- Used to swap the amount to transfer when negative
+       -- amounts are supplied to amount_to_transfer and
+       -- amount_to_receive
+       temp_amount                                                         NUMERIC;
 BEGIN
        --
-       -- Sanity checks
+       -- We need to lock this table because the exchange rates could be modifed
+       -- while we are doing a transfer, which would create unpredictable results
        --
-       IF old_fund IS NULL THEN
-               RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
-       END IF;
+       LOCK acq.exchange_rate
+       IN EXCLUSIVE MODE;
+       
        --
-       IF old_amount IS NULL THEN
-               RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
-       END IF;
-       --
-       -- The new fund and its amount must be both NULL or both not NULL.
+       -- Sanity checks
        --
-       IF new_fund IS NOT NULL AND new_amount IS NULL THEN
-               RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
+       IF transferring_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: transferring fund id is NULL';
        END IF;
        --
-       IF new_fund IS NULL AND new_amount IS NOT NULL THEN
-               RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
+       IF receiving_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: recieving fund id is NULL';
        END IF;
        --
+       IF amount_to_transfer IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
+       END IF;
+       -- 
        IF user_id IS NULL THEN
                RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
        END IF;
        --
-       -- Initialize the amounts to be transferred, each denominated
-       -- in the currency of its respective fund.  They will be
-       -- reduced on each iteration of the loop.
+       IF amount_to_transfer = 0 THEN
+               RAISE EXCEPTION 'acq.transfer_fund amount to transfer is 0';
+       END IF;
+
+       IF amount_to_receive IS NOT NULL THEN
+               IF (amount_to_transfer > 0 AND amount_to_receive < 0) OR
+                       (amount_to_transfer < 0 AND amount_to_receive > 0) THEN
+                       RAISE EXCEPTION 'acq.transfer_fund amount to transfer (%) and amount to receive (%) do not have the same sign', amount_to_transfer, amount_to_receive;
+               END IF;
+       END IF;
        --
-       old_remaining := old_amount;
-       new_remaining := new_amount;
+       -- Get the currency and active status  of the transferring fund
        --
-       -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
-       --      old_amount, old_fund, new_amount, new_fund;
+       SELECT
+               currency_type,
+               active
+       INTO
+               transferring_fund_currency,
+               transferring_fund_active
+       FROM
+               acq.fund
+       WHERE
+               id = transferring_fund_id;
+       
+       IF transferring_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: transferring fund currency is not defined for fund id: %', transferring_fund_id;
+       END IF;
        --
-       -- Get the currency types of the old and new funds.
+       -- Get the currency and active status of the receiving fund
        --
        SELECT
-               currency_type
+               currency_type,
+               active
        INTO
-               old_fund_currency
+               receiving_fund_currency,
+               receiving_fund_active
        FROM
                acq.fund
        WHERE
-               id = old_fund;
+               id = receiving_fund_id;
        --
-       IF old_fund_currency IS NULL THEN
-               RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
+       IF receiving_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: receiving fund currency is not defined for fund id: %', receiving_fund_id;
        END IF;
        --
-       IF new_fund IS NOT NULL THEN
-               SELECT
-                       currency_type,
-                       active
-               INTO
-                       new_fund_currency,
-                       new_fund_active
-               FROM
-                       acq.fund
-               WHERE
-                       id = new_fund;
+       -- If the amount to transfer is negative then swap the transferring and
+       -- receiving funds
+       --
+       IF amount_to_transfer < 0 THEN
                --
-               IF new_fund_currency IS NULL THEN
-                       RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
-               ELSIF NOT new_fund_active THEN
+               -- Because the amount is negative, we need to check if the transferring
+               -- fund is active, so we do not transfer to an inactive transferring fund
+               --
+               IF NOT transferring_fund_active THEN
                        --
                        -- No point in putting money into a fund from whence you can't spend it
                        --
-                       RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
+                       RAISE EXCEPTION 'acq.transfer_fund: Amount to transfer is negative, and transferring fund id % is inactive', transferring_fund_id;
+               END IF;
+
+               IF (transferring_fund_crrency != receiving_fund_currecy) AND 
+                       (amount_to_recevie IS NULL) THEN
+                       -- convert the funds amount to transfer from
+                       -- the currency of the transferring fund into the currecny of
+                       -- the receiving fund.  Only do this if there is no value in
+                       -- in amount_to_receive
+                       amount_to_transfer := trunc( amount_to_transfer * 
+                               acq.exchange_ratio(transferring_fund_currency, receiving_fund_currency), 2 );
                END IF;
+       
+               -- make the amount to transfer, and conditionally the amount to recieve,  positive
+               amount_to_transfer := amount_to_transfer * -1;
+
+               IF amount_to_receive IS NOT NULL THEN
+                       amount_to_receive := amount_to_receive * -1;
+               END IF;
+
+               temp_fund_id := transferring_fund_id;
+               temp_currency := transferring_fund_currency;
+               temp_amount = amount_to_transfer;
+               transferring_fund_id := receiving_fund_id;
+               transferring_fund_currency := receiving_fund_currency;
+               amount_to_transfer = amount_to_receive;
+               receiving_fund_id := temp_fund_id;
+               receiving_fund_currency := temp_currency;
+               amount_to_receive = temp_amount;
+       ELSIF NOT receiving_fund_active THEN
+               -- We need to check this after a possible swap and not before
+               -- because it is ok to transfer from an inactive fund, so
+               -- the fact that receiving fund has not been swapped means
+               -- we can now check to make sure it is active
+               --
+               -- No point in putting money into a fund from whence you can't spend it
+               --
+               RAISE EXCEPTION 'acq.transfer_fund: receiving fund id % is inactive', receiving_fund_id;
+       END IF;
+
+       -- RAISE EXCEPTION 'acq.transfer_fund test';
+
+       -- ensure there is enough money in the fund to cover the amount to transfer
+       -- we do this after checking if we need to swap funds to ensure we are
+       -- checking the correct fund for the necessary funds to transfer
+       -- If this is a rollover transfer then we check against the spent balance
+       -- which will be higher because it does not contain the encubmerance total.
+       -- Otherwise, makes sure we do not spend encumbered funds
+       IF rollover_transfer THEN
+               SELECT amount
+               INTO amount_currently_allocated_to_transferring_fund
+               FROM acq.fund_spent_balance
+               WHERE fund = transferring_fund_id; 
+       ELSE
+               SELECT amount
+               INTO amount_currently_allocated_to_transferring_fund
+               FROM acq.fund_combined_balance
+               WHERE fund = transferring_fund_id; 
+       END IF;
+
+       -- Ensure there is enough money left in this fund to fulfill the transfer
+       IF amount_to_transfer > amount_currently_allocated_to_transferring_fund THEN
+               RAISE EXCEPTION 'Cannot transfer more money than is currently allocted to the fund. fund id: % has % allocated to it, and acq.transfer_fund is trying to debit it for %', 
+                       transferring_fund_id, amount_currently_allocated_to_transferring_fund, amount_to_transfer;
+       END IF;
+
+       --
+       -- Initialize the amounts to be transferred, each denominated
+       -- in the currency of its respective fund.  They will be
+       -- reduced on each iteration of the loop.
+       --
+       funds_remaining_to_be_transferred := amount_to_transfer;
+       IF transferring_fund_currency = receiving_fund_currency THEN
+               same_currency := true;
+               conversion_ratio := 1;
+               inverted_conversion_ratio := 1;
+               funds_remaining_to_be_received := funds_remaining_to_be_transferred;
+       ELSE
+               --
+               -- We'll have to translate currency between funds.
+               -- In this version we are only using the value to be transfered
+               -- from the old fund.  We will convert in this funciton
                --
-               IF new_amount = old_amount THEN
-                       same_currency := true;
-                       currency_ratio := 1;
+               same_currency := false;
+
+               IF amount_to_receive IS NULL THEN
+                       conversion_ratio := acq.exchange_ratio(transferring_fund_currency, receiving_fund_currency);
+                       -- This is not needed yet, but I am setting it here because it can be calculated, which
+                       -- ensures that if the case where amount_to_receive is NULl requires the inversted_conversion_ratio
+                       -- then it will be set.e
+                       inverted_conversion_ratio = acq.exchange_ratio(receiving_fund_currency, transferring_fund_currency);
                ELSE
-                       --
-                       -- We'll have to translate currency between funds.  We presume that
-                       -- the calling code has already applied an appropriate exchange rate,
-                       -- so we'll apply the same conversion to each sub-transfer.
-                       --
-                       same_currency := false;
-                       currency_ratio := new_amount / old_amount;
+                       -- Because amount_to_receive has been speficied we will
+                       -- use whatever conversion ratio the system making this call used
+                       -- to calculate the amount_to_receive
+                       conversion_ratio := amount_to_receive/amount_to_transfer;
+                       inverted_conversion_ratio = amount_to_transfer/amount_to_receive;
                END IF;
+
+               funds_remaining_to_be_received := trunc( funds_remaining_to_be_transferred * conversion_ratio, 2 );
        END IF;
+
        --
        -- Identify the funding source(s) from which we want to transfer the money.
        -- The principle is that we want to transfer the newest money first, because
        -- we spend the oldest money first.  The priority for spending is defined
        -- by a sort of the view acq.ordered_funding_source_credit.
        --
-       FOR source in
+
+       FOR source IN
                SELECT
                        ofsc.id,
                        ofsc.funding_source,
                        ofsc.amount,
-                       ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
-                               AS converted_amt,
+                       ofsc.amount * acq.exchange_ratio(fs.currency_type, transferring_fund_currency)
+                               AS amount_credited_to_funding_source,
+                       -- We store the amount to retreive when amount_to_receive IS NOT NULL and the
+                       -- fs.currency_type is the same currency as the amount to receive.  We use the
+                       -- inverted_conversion_ratio because we are concerned with having the amount in 
+                       -- the currency of the transferring fund.  The AS amount_credited_to_fund value is
+                       -- converted using the source to transferring currencies.  However, if the soure
+                       -- is the same currency as the receiving currency, then we need to calculate this
+                       -- using the inversion of the ratio used to convert the currencies when the
+                       -- amount_to_receive is specified.
+                       ofsc.amount * inverted_conversion_ratio AS amount_credited_to_funding_source_at_supplied_ratio,
                        fs.currency_type
                FROM
                        acq.ordered_funding_source_credit AS ofsc,
                        acq.funding_source fs
                WHERE
                        ofsc.funding_source = fs.id
-                       and ofsc.funding_source IN
+                       AND ofsc.funding_source IN
                        (
                                SELECT funding_source
                                FROM acq.fund_allocation
-                               WHERE fund = old_fund
+                               WHERE fund = transferring_fund_id
+                       )
+                       AND
+                       (
+                               ofsc.funding_source = transferring_funding_source
+                               OR transferring_funding_source IS NULL
                        )
-                       -- and
-                       -- (
-                       --      ofsc.funding_source = funding_source_in
-                       --      OR funding_source_in IS NULL
-                       -- )
                ORDER BY
-                       ofsc.sort_priority desc,
-                       ofsc.sort_date desc,
-                       ofsc.id desc
+                       ofsc.sort_priority DESC,
+                       ofsc.sort_date DESC,
+                       ofsc.id DESC
        LOOP
                --
-               -- Determine how much money the old fund got from this funding source,
-               -- denominated in the currency types of the source and of the fund.
-               -- This result may reflect transfers from previous iterations.
+               -- Determine total amount of credits that this funding source has give this fund
+               -- Denominated in the currency types of the transferring fund.
+               -- Because we need to look at specific allocations,
+               -- this result may reflect transfers from previous iterations.
+               -- Do not transfer negative values, the fact that they are negative
+               -- indicates that the amount has already been transferred.
                --
                SELECT
-                       COALESCE( sum( amount ), 0 ),
-                       COALESCE( sum( amount )
-                               * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
+                       COALESCE( sum( amount ), 0 )
                INTO
-                       orig_allocated_amt,     -- in currency of the source
-                       allocated_amt           -- in currency of the old fund
+                       -- in currency of the funding source
+                       amount_allocated_by_funding_source
                FROM
                        acq.fund_allocation
                WHERE
-                       fund = old_fund
-                       and funding_source = source.funding_source;
-               --      
+                       fund = transferring_fund_id
+                       AND funding_source = source.funding_source
+                       AND amount > 0;
+
                -- Determine how much to transfer from this credit, in the currency
-               -- of the fund.   Begin with the amount remaining to be attributed:
+               -- of the funding source.   Begin with the amount remaining to be transferred
                --
-               curr_old_amt := old_remaining;
+               current_amount_to_transfer := funds_remaining_to_be_transferred *
+                       acq.exchange_ratio(transferring_fund_currency, source.currency_type);
+
                --
-               -- Can't attribute more than was allocated from the fund:
+               -- Can't attribute more than was allocated to the fund:
                --
-               IF curr_old_amt > allocated_amt THEN
-                       curr_old_amt := allocated_amt;
+
+               IF current_amount_to_transfer > amount_allocated_by_funding_source THEN
+                       current_amount_to_transfer := amount_allocated_by_funding_source;
                END IF;
+               
                --
-               -- Can't attribute more than the amount of the current credit:
+               -- Can't attribute more than the amount of the current credit from the funding source:
                --
-               IF curr_old_amt > source.converted_amt THEN
-                       curr_old_amt := source.converted_amt;
+               
+               IF (current_amount_to_transfer > source.amount) THEN
+                       current_amount_to_transfer := source.amount;
                END IF;
+               
                --
-               curr_old_amt := trunc( curr_old_amt, 2 );
+               current_amount_to_transfer := trunc( current_amount_to_transfer, 2 );
                --
-               old_remaining := old_remaining - curr_old_amt;
+               -- At some point funds_remaining_to_be_transferred value WILL become 0
+               -- because current_amount_to_transfer is set to funds_remaining_to_be_transferred above
+               -- and it is never increased above that.
                --
-               -- Determine the amount to be deducted, if any,
-               -- from the old allocation.
+               fund_amount_to_transfer = current_amount_to_transfer *
+                       acq.exchange_ratio(source.currency_type, transferring_fund_currency);
+
+               funds_remaining_to_be_transferred := funds_remaining_to_be_transferred - fund_amount_to_transfer;
+               
                --
-               IF old_remaining > 0 THEN
-                       --
-                       -- In this case we're using the whole allocation, so use that
-                       -- amount directly instead of applying a currency translation
-                       -- and thereby inviting round-off errors.
-                       --
-                       source_deduction := - orig_allocated_amt;
-               ELSE 
-                       source_deduction := trunc(
-                               ( - curr_old_amt ) *
-                                       acq.exchange_ratio( old_fund_currency, source.currency_type ),
-                               2 );
-               END IF;
+               -- Determine the amount to be credited, if any,
+               -- to the funding source.
+               --
+       
+               --
+               -- Either the entire amount is being deducted (the case where current_amount_to_transfer 
+               -- is less than the 
+               -- amount_allocated_by_funding_source and source.amount_credited_to_fund), we're 
+               -- are deducting the whole allocation from the current funding source credit, 
+               -- or we are deducting the amount allocated in this single unding source credit if it is 
+               -- less than the original amount of the credit (due to previous debits).
+               -- In all these cases these values are represented by current_amount_to_transfer.
+               -- We need to convert the amount into the source.currency_type regardless of the 
+               -- condiitons above because we are deducting from the source not the fund.
                --
-               IF source_deduction <> 0 THEN
+       
+               IF (amount_to_receive IS NULL) AND (receiving_fund_currency != source.currency_type) THEN
+                       source_credit_conversion_ratio := acq.exchange_ratio( transferring_fund_currency, source.currency_type ); 
+               ELSE
+                       -- use the calculated ratio when the amount_to_receive is specified     
+                       source_credit_conversion_ratio := conversion_ratio;
+               END IF;
+                  
+               funding_source_credit := trunc((- current_amount_to_transfer ), 2);
+               
+               -- Ensure the credit is less than or equal to 0
+               -- We insert the case where it is equal to 0 because
+               -- there may be a need for a corresponding 0 entry in acq.fund_transfer,
+               -- which is INSERTed near the end of this function.
+
+               -- transfering a negative amount from a fund is the same as
+               -- crediting it back to the funding source, which must
+               -- happen before the amount is transferred to the receiving fund
+               IF funding_source_credit <= 0 THEN
                        --
-                       -- Insert negative allocation for old fund in fund_allocation,
-                       -- converted into the currency of the funding source
+                       -- Insert negative (or 0) allocation for old fund in fund_allocation.
                        --
                        INSERT INTO acq.fund_allocation (
                                funding_source,
@@ -1477,79 +2056,70 @@ BEGIN
                                note
                        ) VALUES (
                                source.funding_source,
-                               old_fund,
-                               source_deduction,
+                               transferring_fund_id,
+                               funding_source_credit,
                                user_id,
-                               'Transfer to fund ' || new_fund
+                               'Transfer to fund ' || receiving_fund_id
                        );
+               ELSE
+                       RAISE EXCEPTION 'funding_source_credit of % is greater than 0', funding_source_credit;
                END IF;
                --
-               IF new_fund IS NOT NULL THEN
-                       --
-                       -- Determine how much to add to the new fund, in
-                       -- its currency, and how much remains to be added:
-                       --
-                       IF same_currency THEN
-                               curr_new_amt := curr_old_amt;
-                       ELSE
-                               IF old_remaining = 0 THEN
-                                       --
-                                       -- This is the last iteration, so nothing should be left
-                                       --
-                                       curr_new_amt := new_remaining;
-                                       new_remaining := 0;
-                               ELSE
-                                       curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
-                                       new_remaining := new_remaining - curr_new_amt;
-                               END IF;
-                       END IF;
+               --
+               -- Determine how much to add to the receiving fund, in
+               -- its currency, and how much remains to be added:
+               --
+               current_amount_to_receive := current_amount_to_transfer;
+
+               --
+               -- Determine how much to add, if any,
+               -- to the receiving fund's allocation.
+               --
+               
+               funding_source_debit := current_amount_to_receive;
+               --
+               -- Ensure the debit is greater than or equal to 0
+               -- Similar to funding_source_credit there may be a corresponding
+               -- entry in acq.fund_transfer.
+               --
+               IF funding_source_debit >= 0 THEN
                        --
-                       -- Determine how much to add, if any,
-                       -- to the new fund's allocation.
+                       -- Insert positive allocation (or 0) for new fund in fund_allocation,
+                       -- in the currency of the founding source
                        --
-                       IF old_remaining > 0 THEN
-                               --
-                               -- In this case we're using the whole allocation, so use that amount
-                               -- amount directly instead of applying a currency translation and
-                               -- thereby inviting round-off errors.
-                               --
-                               source_addition := orig_allocated_amt;
-                       ELSIF source.currency_type = old_fund_currency THEN
-                               --
-                               -- In this case we don't need a round trip currency translation,
-                               -- thereby inviting round-off errors:
-                               --
-                               source_addition := curr_old_amt;
-                       ELSE 
-                               source_addition := trunc(
-                                       curr_new_amt *
-                                               acq.exchange_ratio( new_fund_currency, source.currency_type ),
-                                       2 );
-                       END IF;
-                       --
-                       IF source_addition <> 0 THEN
-                               --
-                               -- Insert positive allocation for new fund in fund_allocation,
-                               -- converted to the currency of the founding source
-                               --
-                               INSERT INTO acq.fund_allocation (
-                                       funding_source,
-                                       fund,
-                                       amount,
-                                       allocator,
-                                       note
-                               ) VALUES (
-                                       source.funding_source,
-                                       new_fund,
-                                       source_addition,
-                                       user_id,
-                                       'Transfer from fund ' || old_fund
-                               );
-                       END IF;
+                       INSERT INTO acq.fund_allocation (
+                               funding_source,
+                               fund,
+                               amount,
+                               allocator,
+                               note
+                       ) VALUES (
+                               source.funding_source,
+                               receiving_fund_id,
+                               funding_source_debit,
+                               user_id,
+                               'Transfer from fund ' || transferring_fund_id
+                       );
+               ELSE
+                       RAISE EXCEPTION 'acq.fund_transfer: funding_source_debit % is less than 0', funding_source_debit;
                END IF;
                --
-               IF trunc( curr_old_amt, 2 ) <> 0
-               OR trunc( curr_new_amt, 2 ) <> 0 THEN
+               -- Either of these can be greater than 0.  
+               -- current_amount_to_transfer is the value to be transferred.  current_amount_to_receive is that
+               -- value converted into the new funds currency
+               -- If the entire amount can be taken from a single source credit. Then
+               -- this is a simple calculation.
+               -- Otherwise, current_amount_to_transfer is set to the amount left in funds_remaining_to_be_transferred
+               -- after a loop or the values in the current funding source credit.
+               -- And, current_amount_to_receive is set to the converted amount from current_amount_to_transfer.
+               -- Finally, once funds_remaining_to_be_transferred = 0 curr_new_amount is set to funds_remaining_to_be_received,
+               -- and new remaining will be 0 after that. Because funds_remaining_to_be_received
+               -- is only calculated once (after that it is only debited), this should
+               -- ensure that they both arrive at 0 together
+               -- There may be some conversion issues that make this not true, but I
+               -- cannot think of any at the moment.  The code needs further examination.
+               IF trunc( current_amount_to_transfer, 2 ) > 0
+               OR trunc( current_amount_to_receive, 2 ) > 0 THEN
                        --
                        -- Insert row in fund_transfer, using amounts in the currency of the funds
                        --
@@ -1562,20 +2132,35 @@ BEGIN
                                note,
                                funding_source_credit
                        ) VALUES (
-                               old_fund,
-                               trunc( curr_old_amt, 2 ),
-                               new_fund,
-                               trunc( curr_new_amt, 2 ),
+                               transferring_fund_id,
+                               trunc(current_amount_to_transfer *
+                                       acq.exchange_ratio(source.currency_type, transferring_fund_currency), 2),
+                               receiving_fund_id,
+                               trunc(current_amount_to_receive *
+                                       acq.exchange_ratio(source.currency_type, receiving_fund_currency), 2),
                                user_id,
                                xfer_note,
                                source.id
                        );
                END IF;
                --
-               if old_remaining <= 0 THEN
+               -- It should be impossible for funds_remaining_to_be_transferred to be less than 0.
+               --
+               IF funds_remaining_to_be_transferred = 0 THEN
                        EXIT;                   -- Nothing more to be transferred
+               ELSIF funds_remaining_to_be_transferred < 0 THEN
+                       RAISE EXCEPTION 'acq.transfer_fund: funds_remaining_to_be_transferred is less thant 0: % FIND OUT WHY',
+                               funds_remaining_to_be_transferred;
                END IF;
        END LOOP;
+
+       --
+       -- This should not be possible any more, but we can leave it in just in case there
+       -- is a case that has not been thought of.
+       --
+       IF trunc(funds_remaining_to_be_transferred, 2) > 0 THEN
+               RAISE EXCEPTION 'not all of funds_remaining_to_be_transferred were transfered.  There must not have been enough funds in the funding sources';
+       END IF;
 END;
 $$ LANGUAGE plpgsql;
 
@@ -2022,8 +2607,8 @@ CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
        old_year INTEGER,
        user_id INTEGER,
        org_unit_id INTEGER,
-    encumb_only BOOL DEFAULT FALSE,
-    include_desc BOOL DEFAULT TRUE
+       encumb_only BOOL DEFAULT FALSE,
+       include_desc BOOL DEFAULT TRUE
 ) RETURNS VOID AS $$
 DECLARE
 --
@@ -2043,8 +2628,8 @@ BEGIN
        --
        IF old_year IS NULL THEN
                RAISE EXCEPTION 'Input year argument is NULL';
-    ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
-        RAISE EXCEPTION 'Input year is out of range';
+       ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
+               RAISE EXCEPTION 'Input year is out of range';
        END IF;
        --
        IF user_id IS NULL THEN
@@ -2080,24 +2665,25 @@ BEGIN
        --
        FOR roll_fund in
        SELECT
-           oldf.id AS old_fund,
-           oldf.org,
-           oldf.name,
-           oldf.currency_type,
-           oldf.code,
+               oldf.id AS old_fund,
+               oldf.org,
+               oldf.name,
+               oldf.currency_type,
+               oldf.code,
                oldf.rollover,
-           newf.id AS new_fund_id
+               newf.id AS new_fund_id
        FROM
-       acq.fund AS oldf
-       LEFT JOIN acq.fund AS newf
-               ON ( oldf.code = newf.code )
+               acq.fund AS oldf
+               LEFT JOIN acq.fund AS newf
+               ON ( oldf.code = newf.code )
        WHERE
-                   oldf.year = old_year
+               oldf.year = old_year
                AND oldf.propagate
-        AND newf.year = new_year
+               AND newf.year = new_year
                AND ( ( include_desc AND oldf.org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) )
-                OR (NOT include_desc AND oldf.org = org_unit_id ) )
+                   OR (NOT include_desc AND oldf.org = org_unit_id ) )
        LOOP
+               --
                --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
                --
                IF roll_fund.new_fund_id IS NULL THEN
@@ -2130,41 +2716,43 @@ BEGIN
                        new_fund = roll_fund.new_fund_id;
                END IF;
                --
-               -- Determine the amount to transfer
+               --
+               -- Determine the amount to transfer or return
                --
                SELECT amount
                INTO xfer_amount
                FROM acq.fund_spent_balance
                WHERE fund = roll_fund.old_fund;
-               --
-               IF xfer_amount <> 0 THEN
-                       IF NOT encumb_only AND roll_fund.rollover THEN
-                               --
-                               -- Transfer balance from old fund to new
-                               --
-                               --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
-                               --
+
+               IF NOT encumb_only AND roll_fund.rollover THEN
+                       -- Transfer balance from old fund to new
+                       --
+                       -- RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
+                       --
+                       IF xfer_amount > 0 THEN
                                PERFORM acq.transfer_fund(
                                        roll_fund.old_fund,
                                        xfer_amount,
                                        new_fund,
-                                       xfer_amount,
                                        user_id,
-                                       'Rollover'
+                                       'Rollover',
+                                       xfer_amount,
+                                       NULL,
+                                       TRUE
                                );
-                       ELSE
-                               --
-                               -- Transfer balance from old fund to the void
-                               --
-                               -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
-                               --
-                               PERFORM acq.transfer_fund(
+                       END IF;
+               ELSE
+                       --
+                       -- Transfer balance from old fund to the void
+                       --
+                       -- RAISE NOTICE 'Returning % from fund % to its source(s)', xfer_amount, roll_fund.old_fund;
+                       --
+                       IF xfer_amount > 0 THEN
+                               PERFORM acq.return_funds_to_source(
                                        roll_fund.old_fund,
                                        xfer_amount,
-                                       NULL,
-                                       NULL,
                                        user_id,
-                                       'Rollover into the void'
+                                       'Rollover - return funds to source'
                                );
                        END IF;
                END IF;
@@ -2180,23 +2768,23 @@ BEGIN
                        UPDATE acq.lineitem_detail
                        SET fund = new_fund
                        WHERE
-                       fund = roll_fund.old_fund -- this condition may be redundant
-                       AND fund_debit in
-                       (
-                               SELECT id
-                               FROM acq.fund_debit
-                               WHERE
-                               fund = roll_fund.old_fund
-                               AND encumbrance
-                       );
+                               fund = roll_fund.old_fund -- this condition may be redundant
+                               AND fund_debit IN
+                               (
+                                       SELECT id
+                                       FROM acq.fund_debit
+                                       WHERE
+                                       fund = roll_fund.old_fund
+                                       AND encumbrance
+                               );
                        --
                        -- Move encumbrance debits from the old fund to the new fund
                        --
                        UPDATE acq.fund_debit
                        SET fund = new_fund
                        wHERE
-                               fund = roll_fund.old_fund
-                               AND encumbrance;
+                       fund = roll_fund.old_fund
+                       AND encumbrance;
                END IF;
 
                -- Rollover distribution formulae funds
@@ -2207,8 +2795,8 @@ BEGIN
 
                IF roll_distrib_forms THEN
                        UPDATE acq.distribution_formula_entry 
-                               SET fund = roll_fund.new_fund_id
-                               WHERE fund = roll_fund.old_fund;
+                       SET fund = roll_fund.new_fund_id
+                       WHERE fund = roll_fund.old_fund;
                END IF;
 
                --
@@ -2221,10 +2809,8 @@ BEGIN
 END;
 $$ LANGUAGE plpgsql;
 
-
-
 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit( old_year INTEGER, user_id INTEGER, org_unit_id INTEGER, encumb_only BOOL DEFAULT FALSE ) RETURNS VOID AS $$
-    SELECT acq.rollover_funds_by_org_tree( $1, $2, $3, $4, FALSE );
+       SELECT acq.rollover_funds_by_org_tree( $1, $2, $3, $4, FALSE );
 $$ LANGUAGE SQL;
 
 CREATE OR REPLACE VIEW acq.funding_source_credit_total AS
@@ -2247,12 +2833,12 @@ CREATE OR REPLACE VIEW acq.funding_source_balance AS
       GROUP BY 1;
 
 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
-    SELECT  fund,
-            SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
-    FROM acq.fund_allocation a
-         JOIN acq.fund f ON (a.fund = f.id)
-         JOIN acq.funding_source s ON (a.funding_source = s.id)
-    GROUP BY 1;
+       SELECT  af.id AS fund, 
+               COALESCE(sum(afa.amount * acq.exchange_ratio(COALESCE(afs.currency_type, af.currency_type), COALESCE(af.currency_type, afs.currency_type)))::NUMERIC(100,2), 0.00) AS amount
+       FROM acq.fund_allocation AS afa
+               RIGHT JOIN acq.fund AS af ON afa.fund = af.id
+               LEFT JOIN acq.funding_source AS afs ON afa.funding_source = afs.id
+       GROUP BY af.id;
 
 CREATE OR REPLACE VIEW acq.fund_debit_total AS
     SELECT  fund.id AS fund, 
diff --git a/Open-ILS/src/sql/Pg/t/acq_fund_amount_test.pg b/Open-ILS/src/sql/Pg/t/acq_fund_amount_test.pg
new file mode 100644 (file)
index 0000000..8eb37ca
--- /dev/null
@@ -0,0 +1,437 @@
+BEGIN;
+
+\set year 'date_part(''year'', NOW())'
+\set next_year 'date_part(''year'', NOW() + ''1 YEAR''::INTERVAL)'
+\set funding_source_1 '''FS1'''
+\set funding_source_2 '''FS2'''
+\set funding_source_3 '''FS3'''
+\set fund_1 '''F1'''
+\set fund_2 '''F2'''
+\set fund_3 '''F3'''
+\set org 701
+\set currency_1 '''CAN'''
+\set currency_2 '''USD'''
+\set same_currency_exchange_rate 1.00
+\set currency_1_to_currency_2_exchange_rate .50
+\set currency_2_to_currency_1_exchange_rate 2.00
+\set owner 1
+\set funding_source_1_credit_1 3000.00
+\set funding_source_1_credit_2 2000.00
+\set funding_source_1_credit_3 100.00
+\set funding_source_2_credit_1 5.00
+\set funding_source_2_credit_2 450.00
+\set funding_source_2_credit_3 1500.00
+\set funding_source_3_credit_1 675.00
+\set funding_source_3_credit_2 10000.00
+\set funding_source_3_credit_3 934.00
+\set source_1_fund_1_allocation_1 500.00
+\set source_1_fund_1_allocation_2 200.00
+\set source_1_fund_1_allocation_3 0.00
+\set source_1_fund_2_allocation_1 155.00
+\set source_1_fund_2_allocation_2 300.00
+\set source_1_fund_2_allocation_3 400.00
+\set source_1_fund_3_allocation_1 45.00
+\set source_1_fund_3_allocation_2 200.00
+\set source_1_fund_3_allocation_3 1000.00
+\set source_2_fund_1_allocation_1 100.00
+\set source_2_fund_1_allocation_2 50.00
+\set source_2_fund_1_allocation_3 -400.00
+\set source_2_fund_2_allocation_1 35.00
+\set source_2_fund_2_allocation_2 200.00
+\set source_2_fund_2_allocation_3 200.00
+\set source_2_fund_3_allocation_1 400.00
+\set source_2_fund_3_allocation_2 -25.00
+\set source_2_fund_3_allocation_3 122.00
+\set source_3_fund_1_allocation_1 5000.00
+\set source_3_fund_1_allocation_2 135.00
+\set source_3_fund_1_allocation_3 300.00
+\set source_3_fund_2_allocation_1 2000.00
+\set source_3_fund_2_allocation_2 22.00
+\set source_3_fund_2_allocation_3 100.00
+\set source_3_fund_3_allocation_1 333.00
+\set source_3_fund_3_allocation_2 200.00
+\set source_3_fund_3_allocation_3 500.00
+
+SELECT plan(11);
+
+-- Update the exchange ratios to known values for the test
+UPDATE acq.exchange_rate
+SET ratio = :currency_1_to_currency_2_exchange_rate
+WHERE from_currency = 'CAN' AND to_currency = 'USD';
+
+UPDATE acq.exchange_rate
+SET ratio = :currency_2_to_currency_1_exchange_rate
+WHERE from_currency = 'USD' AND to_currency = 'CAN';
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_1, :owner, :currency_1, :funding_source_1);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_2, :owner, :currency_2, :funding_source_2);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_3, :owner, :currency_2, :funding_source_3);
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_1,
+            NOW() + '1 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_2,
+            NULL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           );
+
+INSERT INTO acq.fund (org, name, year, currency_type, code, active, rollover, propagate)
+    VALUES  (:org, :fund_1, :year, :currency_1, :fund_1, TRUE, TRUE, TRUE),
+            (:org, :fund_2, :year, :currency_2, :fund_2, TRUE, TRUE, TRUE),
+            (:org, :fund_3, :year, :currency_2, :fund_3, TRUE, TRUE, TRUE);
+
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, allocator, note)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_3, :owner, 'test'
+           );
+
+-- Make sure the exchange rates are working
+
+SELECT has_trigger('acq', 'fund_allocation', 'acq_insert_fund_allocation_fund_amount',
+                    'The table acq.fund_allocation has the trigger acq_insert_fund_allocation_fund_amount.');
+
+SELECT has_function('acq', 'insert_fund_allocation_fund_amount', 'The function insert_fund_allocation_fund_amount exists.');
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_1 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || :source_1_fund_1_allocation_1 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_1_fund_1_allocation_2 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_1_fund_1_allocation_3 || ', ' || :same_currency_exchange_rate || ')'
+    ,
+        'Fund ' || :fund_1 || ' was transferred ' || (:source_1_fund_1_allocation_1 + :source_1_fund_1_allocation_2
+            + :source_1_fund_1_allocation_3) || ' from Funding Source '
+        || :funding_source_1 || ' and it should have ' ||
+            (SELECT (:source_1_fund_1_allocation_1 + :source_1_fund_1_allocation_2 + :source_1_fund_1_allocation_3) *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_1),
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_1 and year = :year))) ||
+            ' due to the exchange rate of ' || :same_currency_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_1 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || (:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || '),
+        (' || (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || '),
+        (' || (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || ')'
+    ,
+    'Fund ' || :fund_2 || ' was transferred ' || (:source_1_fund_2_allocation_1 + :source_1_fund_2_allocation_2
+        + :source_1_fund_2_allocation_3) || ' from Funding Source '
+    || :funding_source_1 || ' and it should have ' ||
+        (SELECT (:source_1_fund_2_allocation_1 + :source_1_fund_2_allocation_2 + :source_1_fund_2_allocation_3) *
+            acq.exchange_ratio(
+                (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_1),
+                (SELECT currency_type FROM acq.fund WHERE name = :fund_2 AND year = :year))) ||
+        ' due to the exchange rate of ' || :currency_1_to_currency_2_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_1 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || (:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || '),
+        (' || (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || '),
+        (' || (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || ')'
+    ,
+    'Fund ' || :fund_3 || ' was transferred ' || (:source_1_fund_3_allocation_1 + :source_1_fund_3_allocation_2
+        + :source_1_fund_3_allocation_3) || ' from Funding Source '
+    || :funding_source_1 || ' and it should have ' ||
+        (SELECT (:source_1_fund_3_allocation_1 + :source_1_fund_3_allocation_2 + :source_1_fund_3_allocation_3) *
+            acq.exchange_ratio(
+                (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_1),
+                (SELECT currency_type FROM acq.fund WHERE name = :fund_3 AND year = :year))) ||
+        ' due to the exchange rate of ' || :currency_1_to_currency_2_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_2 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate)
+            || ', ' || :currency_2_to_currency_1_exchange_rate || '),
+        (' || (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate)
+            || ', ' || :currency_2_to_currency_1_exchange_rate || '),
+        (' || (:source_2_fund_1_allocation_3 * :currency_1_to_currency_2_exchange_rate)
+            || ', ' || :currency_1_to_currency_2_exchange_rate || ')'
+    ,
+    'Fund ' || :fund_1 || ' was transferred ' || :source_2_fund_1_allocation_1 || ', ' ||
+        :source_2_fund_1_allocation_2 || ', and ' ||  :source_2_fund_1_allocation_3 || ' from Funding Source '
+    || :funding_source_2 || ' and it should have ' ||
+        (SELECT ((:source_2_fund_1_allocation_1 + :source_2_fund_1_allocation_2) *
+            acq.exchange_ratio(
+                (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_2),
+                (SELECT currency_type FROM acq.fund WHERE name = :fund_1 AND year = :year))  +
+            (:source_2_fund_1_allocation_3 *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_1 AND year = :year),
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_2))))
+            ) ||
+        ' due to the exchange rates of ' || :currency_2_to_currency_1_exchange_rate || ', ' ||
+        :currency_2_to_currency_1_exchange_rate || ' and ' ||
+        :currency_1_to_currency_2_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_2 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || :source_2_fund_2_allocation_1 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_2_fund_2_allocation_2 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_2_fund_2_allocation_3 || ', ' || :same_currency_exchange_rate || ')'
+    ,
+        'Fund ' || :fund_2 || ' was transferred ' || (:source_2_fund_2_allocation_1 + :source_2_fund_2_allocation_2
+            + :source_2_fund_2_allocation_3) || ' from Funding Source '
+        || :funding_source_2 || ' and it should have ' ||
+            (SELECT (:source_2_fund_2_allocation_1 + :source_2_fund_2_allocation_2 + :source_2_fund_2_allocation_3) *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_2),
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_2 and year = :year))) ||
+            ' due to the exchange rate of ' || :same_currency_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_2 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || :source_2_fund_3_allocation_1 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_2_fund_3_allocation_2 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_2_fund_3_allocation_3 || ', ' || :same_currency_exchange_rate || ')'
+    ,
+        'Fund ' || :fund_3 || ' was transferred ' || (:source_2_fund_3_allocation_1 + :source_2_fund_3_allocation_2
+            + :source_2_fund_3_allocation_3) || ' from Funding Source '
+        || :funding_source_2 || ' and it should have ' ||
+            (SELECT (:source_2_fund_3_allocation_1 + :source_2_fund_3_allocation_2 + :source_2_fund_3_allocation_3) *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_2),
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_3 and year = :year))) ||
+            ' due to the exchange rate of ' || :same_currency_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_3 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate)
+            || ', ' || :currency_2_to_currency_1_exchange_rate || '),
+        (' || (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate)
+            || ', ' || :currency_2_to_currency_1_exchange_rate || '),
+        (' || (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate)
+            || ', ' || :currency_2_to_currency_1_exchange_rate || ')'
+    ,
+    'Fund ' || :fund_1 || ' was transferred ' || (:source_3_fund_1_allocation_1 + :source_3_fund_1_allocation_2
+        + :source_3_fund_1_allocation_3) || ' from Funding Source '
+    || :funding_source_3 || ' and it should have ' ||
+        (SELECT (:source_3_fund_1_allocation_1 + :source_3_fund_1_allocation_2 + :source_3_fund_1_allocation_3) *
+            acq.exchange_ratio(
+                (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_3),
+                (SELECT currency_type FROM acq.fund WHERE name = :fund_1 AND year = :year))) ||
+        ' due to the exchange rate of ' || :currency_2_to_currency_1_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_3 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || :source_3_fund_2_allocation_1 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_3_fund_2_allocation_2 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_3_fund_2_allocation_3 || ', ' || :same_currency_exchange_rate || ')'
+    ,
+        'Fund ' || :fund_2 || ' was transferred ' || (:source_3_fund_2_allocation_1 + :source_3_fund_2_allocation_2
+            + :source_3_fund_2_allocation_3) || ' from Funding Source '
+        || :funding_source_3 || ' and it should have ' ||
+            (SELECT (:source_3_fund_2_allocation_1 + :source_3_fund_2_allocation_2 + :source_3_fund_2_allocation_3) *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_3),
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_2 and year = :year))) ||
+            ' due to the exchange rate of ' || :same_currency_exchange_rate || '.'
+    );
+
+SELECT results_eq(
+        'SELECT fund_amount, conversion_ratio
+        FROM acq.fund_allocation
+        WHERE funding_source = (SELECT id FROM acq.funding_source WHERE name = ''' || :funding_source_3 || ''')
+        AND fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || ''' AND year = ' || :year || ')'
+    ,
+        'VALUES (' || :source_3_fund_3_allocation_1 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_3_fund_3_allocation_2 || ', ' || :same_currency_exchange_rate || '),
+        (' || :source_3_fund_3_allocation_3 || ', ' || :same_currency_exchange_rate || ')'
+    ,
+        'Fund ' || :fund_3 || ' was transferred ' || (:source_3_fund_3_allocation_1 + :source_3_fund_3_allocation_2
+            + :source_3_fund_3_allocation_3) || ' from Funding Source '
+        || :funding_source_3 || ' and it should have ' ||
+            (SELECT (:source_3_fund_3_allocation_1 + :source_3_fund_3_allocation_2 + :source_3_fund_3_allocation_3) *
+                acq.exchange_ratio(
+                    (SELECT currency_type FROM acq.funding_source WHERE name = :funding_source_3),
+                    (SELECT currency_type FROM acq.fund WHERE name = :fund_3 and year = :year))) ||
+            ' due to the exchange rate of ' || :same_currency_exchange_rate || '.'
+    );
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_fund_transfer.pg b/Open-ILS/src/sql/Pg/t/acq_fund_transfer.pg
new file mode 100644 (file)
index 0000000..f510072
--- /dev/null
@@ -0,0 +1,75 @@
+BEGIN;
+
+SELECT plan(2);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES ('FS1', 1, 'USD', 'FS1');
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date )
+VALUES
+    (
+        (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+        300,
+        NOW() + '1 DAY'::INTERVAL
+    ), (
+        (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+        500,
+        NULL -- no deadline_date ensures this credit will be used first
+             -- for transfers, giving us a predictable (testable) outcome.
+    );
+
+INSERT INTO acq.fund (org, name, year, currency_type, active) VALUES
+    (1, 'F1', '2014', 'USD', TRUE),
+    (1, 'F2', '2014', 'USD', TRUE);
+
+-- LP#800478
+-- allocation must exceed transfer amount but be less than total credits
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, fund_amount, allocator, note)
+VALUES (
+    (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+    (SELECT id FROM acq.fund WHERE name = 'F1'),
+    700 , 700, 1, 'test'
+);
+
+-- LP#800478
+-- transfer amount must exceed any one funding source credit but be less
+-- than the allocation amount
+SELECT acq.transfer_fund(
+    (SELECT id FROM acq.fund WHERE name = 'F1'), 600::NUMERIC,
+    (SELECT id FROM acq.fund WHERE name = 'F2'), 1, 'test'::TEXT, 600::NUMERIC
+);
+
+-- fund transfer should show 600 moved between funds
+-- 500 from the 500 credit, 100 from the 300 credit
+SELECT is(
+    (
+        SELECT ARRAY_AGG(dest_amount) FROM (
+            SELECT dest_amount FROM acq.fund_transfer
+            WHERE
+                src_fund = (SELECT id FROM acq.fund WHERE name = 'F1') AND
+                dest_fund = (SELECT id FROM acq.fund WHERE name = 'F2')
+            ORDER BY dest_amount
+        ) dest_amount
+    ),
+    '{100.00,500.00}',
+    'Transfer amount should be 100 and 500'
+);
+
+-- destination fund should have a 600 allocation
+SELECT is(
+    (
+        SELECT ARRAY_AGG(amount) FROM (
+            SELECT amount FROM acq.fund_allocation
+            WHERE
+                funding_source =
+                    (SELECT id FROM acq.funding_source WHERE name = 'FS1') AND
+                fund = (SELECT id FROM acq.fund WHERE name = 'F2')
+            ORDER BY amount
+        ) amount
+    ),
+    '{100.00,500.00}',
+    'Allocation amount should be 100 and 500'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_insert_fund_allocation_fund_amount.pg b/Open-ILS/src/sql/Pg/t/acq_insert_fund_allocation_fund_amount.pg
new file mode 100644 (file)
index 0000000..fab6742
--- /dev/null
@@ -0,0 +1,4 @@
+BEGIN;
+
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_return_funds_to_source_test.pg b/Open-ILS/src/sql/Pg/t/acq_return_funds_to_source_test.pg
new file mode 100644 (file)
index 0000000..9d270df
--- /dev/null
@@ -0,0 +1,67 @@
+BEGIN;
+
+SELECT plan(2);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES ('FS1', 1, 'USD', 'FS1');
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date )
+VALUES
+    (
+        (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+        300,
+        NOW() + '1 DAY'::INTERVAL
+    ), (
+        (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+        500,
+        NULL -- no deadline_date ensures this credit will be used first
+             -- for transfers, giving us a predictable (testable) outcome.
+    );
+
+INSERT INTO acq.fund (org, name, year, currency_type, active) VALUES
+    (1, 'F1', '2014', 'USD', TRUE);
+
+-- LP#800478
+-- allocation must exceed transfer amount but be less than total credits
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, fund_amount, allocator, note)
+VALUES (
+    (SELECT id FROM acq.funding_source WHERE name = 'FS1'),
+    (SELECT id FROM acq.fund WHERE name = 'F1'),
+    700 , 700, 1, 'test'
+);
+
+-- LP#800478
+-- transfer amount must exceed any one funding source credit but be less
+-- than the allocation amount
+SELECT acq.return_funds_to_source(
+    (SELECT id FROM acq.fund WHERE name = 'F1'), 600::NUMERIC,
+    1, 'test'::TEXT
+);
+
+-- acq.fund_allocation should show 500 and 100 returned to funding source
+SELECT is(
+    (
+        SELECT ARRAY_AGG(amount) FROM (
+            SELECT amount FROM acq.fund_allocation
+            WHERE
+                funding_source = (SELECT id FROM acq.funding_source WHERE name = 'FS1') AND
+                fund = (SELECT id FROM acq.fund WHERE name = 'F1')
+            ORDER BY amount
+        ) AS amount
+    ),
+    '{-500.00,-100.00,700}',
+    'Should have returned 100 and 500 to the funding source, with an inital credit of 700'
+);
+
+SELECT is(
+    (
+        SELECT ARRAY_AGG(src_amount)
+        FROM acq.fund_transfer
+        WHERE src_fund = (SELECT id FROM acq.fund WHERE name = 'F1')
+    ),
+    '{500.00,100.00}',
+    'Two fund transfer: 500.00 and 100.00'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_rollover.pg b/Open-ILS/src/sql/Pg/t/acq_rollover.pg
new file mode 100644 (file)
index 0000000..e075119
--- /dev/null
@@ -0,0 +1,1017 @@
+BEGIN;
+
+\set year 'date_part(''year'', NOW())'
+\set next_year 'date_part(''year'', NOW() + ''1 YEAR''::INTERVAL)'
+\set funding_source_1 '''FS1'''
+\set funding_source_2 '''FS2'''
+\set funding_source_3 '''FS3'''
+\set fund_1 '''F1'''
+\set fund_2 '''F2'''
+\set fund_3 '''F3'''
+\set org 1
+\set currency_1 '''NAC'''
+\set currency_1_label '''NAC Dollars'''
+\set currency_2 '''DSU'''
+\set currency_2_label '''DSU Dollars'''
+\set same_currency_exchange_rate 1.00
+\set currency_1_to_currency_2_exchange_rate .50
+\set currency_2_to_currency_1_exchange_rate 2.00
+\set owner 1
+
+\set funding_source_1_credit_1 3000.00
+\set funding_source_1_credit_2 2000.00
+\set funding_source_1_credit_3 100.00
+
+\set funding_source_2_credit_1 5.00
+\set funding_source_2_credit_2 450.00
+\set funding_source_2_credit_3 1500.00
+
+\set funding_source_3_credit_1 675.00
+\set funding_source_3_credit_2 10000.00
+\set funding_source_3_credit_3 934.00
+
+\set source_1_fund_1_allocation_1 500.00
+\set source_1_fund_1_allocation_2 200.00
+\set source_1_fund_1_allocation_3 0.00
+\set source_1_fund_2_allocation_1 155.00
+\set source_1_fund_2_allocation_2 300.00
+\set source_1_fund_2_allocation_3 400.00
+\set source_1_fund_3_allocation_1 45.00
+\set source_1_fund_3_allocation_2 200.00
+\set source_1_fund_3_allocation_3 1000.00
+
+\set source_2_fund_1_allocation_1 100.00
+\set source_2_fund_1_allocation_2 50.00
+\set source_2_fund_1_allocation_3 -400.00
+\set source_2_fund_2_allocation_1 35.00
+\set source_2_fund_2_allocation_2 200.00
+\set source_2_fund_2_allocation_3 200.00
+\set source_2_fund_3_allocation_1 400.00
+\set source_2_fund_3_allocation_2 -25.00
+\set source_2_fund_3_allocation_3 122.00
+
+\set source_3_fund_1_allocation_1 5000.00
+\set source_3_fund_1_allocation_2 135.00
+\set source_3_fund_1_allocation_3 300.00
+\set source_3_fund_2_allocation_1 2000.00
+\set source_3_fund_2_allocation_2 22.00
+\set source_3_fund_2_allocation_3 100.00
+\set source_3_fund_3_allocation_1 333.00
+\set source_3_fund_3_allocation_2 200.00
+\set source_3_fund_3_allocation_3 500.00
+
+\set fund_1_debit_1 200.00
+\set fund_1_debit_2 150.00
+\set fund_1_debit_3 25.98
+
+\set fund_2_debit_1 555.55
+\set fund_2_debit_2 0.00
+
+\set fund_3_debit_1 1000.22
+
+\set fund_1_encumbrance_1 2234.54
+
+\set fund_2_encumbrance_1 123.45
+\set fund_2_encumbrance_2 111.11
+
+\set fund_3_encumbrance_1 435.43
+\set fund_3_encumbrance_2 10.14
+\set fund_3_encumbrance_3 0.00
+
+\set claim_policy_1 '''4 Week Phone'''
+\set claim_policy_1_description '''Phone the provider after 4 Weeks'''
+\set claim_policy_2 '''4 Week Email'''
+\set claim_policy_2_description '''Email the provider after 4 Weeks'''
+\set claim_policy_3 '''4 Week Fax'''
+\set claim_policy_3_description '''Fax the provider after 4 Weeks'''
+
+\set provider_1_name '''Test Provider'''
+\set provider_1_owner :owner
+\set provider_1_currency_type :currency_1
+\set provider_1_code '''TESTP'''
+
+\set edi_account_1_label '''Test Account'''
+\set edi_account_1_host  '''ftp://ftp.test.com'''
+\set edi_account_1_username '''username'''
+\set edi_account_1_password '''password'''
+\set edi_account_1_path '''/path/.*'''
+\set edi_account_1_owner :owner
+\set edi_account_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set edi_account_1_in_dir '''/in'''
+
+\set purchase_order_1_owner :owner
+\set purchase_order_1_creator :owner
+\set purchase_order_1_editor :owner
+\set purchase_order_1_ordering_agency :org
+\set purchase_order_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set purchase_order_1_name '''PO1'''
+
+\set lineitem_1_creator :owner
+\set lineitem_1_editor :owner
+\set lineitem_1_selector :owner
+\set lineitem_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_1_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_1_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_1_eg_bib_id 1
+\set lineitem_1_source_label '''native-evergreen-catalog'''
+\set lineitem_1_estimated_unit_price :fund_1_encumbrance_1
+\set lineitem_1_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_1_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_1_eg_bib_id)'
+\set lineitem_detail_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_1_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_1_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_encumbrance_1)'
+
+\set lineitem_2_creator :owner
+\set lineitem_2_editor :owner
+\set lineitem_2_selector :owner
+\set lineitem_2_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_2_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_2_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_2_eg_bib_id 2
+\set lineitem_2_source_label '''native-evergreen-catalog'''
+\set lineitem_2_estimated_unit_price :fund_2_encumbrance_1
+\set lineitem_2_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_2_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_2_eg_bib_id)'
+\set lineitem_detail_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_2_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_2_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_1)'
+
+\set lineitem_3_creator :owner
+\set lineitem_3_editor :owner
+\set lineitem_3_selector :owner
+\set lineitem_3_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_3_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_3_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_3_eg_bib_id 3
+\set lineitem_3_source_label '''native-evergreen-catalog'''
+\set lineitem_3_estimated_unit_price :fund_2_encumbrance_2
+\set lineitem_3_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_3_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_3_eg_bib_id)'
+\set lineitem_detail_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_3_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_3_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_2)'
+
+\set lineitem_4_creator :owner
+\set lineitem_4_editor :owner
+\set lineitem_4_selector :owner
+\set lineitem_4_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_4_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_4_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_4_eg_bib_id 4
+\set lineitem_4_source_label '''native-evergreen-catalog'''
+\set lineitem_4_estimated_unit_price :fund_3_encumbrance_1
+\set lineitem_4_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_4_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_4_eg_bib_id)'
+\set lineitem_detail_4_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_4_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_4_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_1)'
+
+\set lineitem_5_creator :owner
+\set lineitem_5_editor :owner
+\set lineitem_5_selector :owner
+\set lineitem_5_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_5_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_5_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_5_eg_bib_id 5
+\set lineitem_5_source_label '''native-evergreen-catalog'''
+\set lineitem_5_estimated_unit_price :fund_3_encumbrance_2
+\set lineitem_5_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_5_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_5_eg_bib_id)'
+\set lineitem_detail_5_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_5_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_5_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_2)'
+
+\set lineitem_6_creator :owner
+\set lineitem_6_editor :owner
+\set lineitem_6_selector :owner
+\set lineitem_6_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_6_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_6_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_6_eg_bib_id 6
+\set lineitem_6_source_label '''native-evergreen-catalog'''
+\set lineitem_6_estimated_unit_price :fund_3_encumbrance_3
+\set lineitem_6_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_6_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_6_eg_bib_id)'
+\set lineitem_detail_6_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_6_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_6_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_3)'
+
+\set lineitem_7_creator :owner
+\set lineitem_7_editor :owner
+\set lineitem_7_selector :owner
+\set lineitem_7_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_7_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_7_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_7_eg_bib_id 7
+\set lineitem_7_source_label '''native-evergreen-catalog'''
+\set lineitem_7_estimated_unit_price :fund_1_debit_1
+\set lineitem_7_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_7_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_7_eg_bib_id)'
+\set lineitem_detail_7_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_7_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_7_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_8_creator :owner
+\set lineitem_8_editor :owner
+\set lineitem_8_selector :owner
+\set lineitem_8_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_8_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_8_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_8_eg_bib_id 8
+\set lineitem_8_source_label '''native-evergreen-catalog'''
+\set lineitem_8_estimated_unit_price :fund_1_debit_2
+\set lineitem_8_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_8_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_8_eg_bib_id)'
+\set lineitem_detail_8_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_8_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_8_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_9_creator :owner
+\set lineitem_9_editor :owner
+\set lineitem_9_selector :owner
+\set lineitem_9_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_9_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_9_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_9_eg_bib_id 9
+\set lineitem_9_source_label '''native-evergreen-catalog'''
+\set lineitem_9_estimated_unit_price :fund_1_debit_3
+\set lineitem_9_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_9_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_9_eg_bib_id)'
+\set lineitem_detail_9_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_9_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_9_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_3)'
+
+\set lineitem_10_creator :owner
+\set lineitem_10_editor :owner
+\set lineitem_10_selector :owner
+\set lineitem_10_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_10_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_10_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_10_eg_bib_id 10
+\set lineitem_10_source_label '''native-evergreen-catalog'''
+\set lineitem_10_estimated_unit_price :fund_2_debit_1
+\set lineitem_10_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_10_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_10_eg_bib_id)'
+\set lineitem_detail_10_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_10_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_10_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_1)'
+
+\set lineitem_11_creator :owner
+\set lineitem_11_editor :owner
+\set lineitem_11_selector :owner
+\set lineitem_11_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_11_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_11_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_11_eg_bib_id 11
+\set lineitem_11_source_label '''native-evergreen-catalog'''
+\set lineitem_11_estimated_unit_price :fund_2_debit_2
+\set lineitem_11_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_11_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_11_eg_bib_id)'
+\set lineitem_detail_11_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_11_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_11_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_2)'
+
+\set lineitem_12_creator :owner
+\set lineitem_12_editor :owner
+\set lineitem_12_selector :owner
+\set lineitem_12_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_12_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_12_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_12_eg_bib_id 12
+\set lineitem_12_source_label '''native-evergreen-catalog'''
+\set lineitem_12_estimated_unit_price :fund_3_debit_1
+\set lineitem_12_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_12_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_12_eg_bib_id)'
+\set lineitem_detail_12_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_12_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_12_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_debit_1)'
+
+SELECT plan(18);
+
+-- Set up the org to rollover all funds
+
+UPDATE actor.org_unit_setting
+SET value = false
+WHERE name = 'acq.fund.allow_rollover_without_money'
+AND org_unit = :org;
+
+-- Set up the claim poilicies
+
+INSERT INTO acq.claim_policy(org_unit, name, description)
+    VALUES (:org, :claim_policy_1, :claim_policy_1_description),
+            (:org, :claim_policy_2, :claim_policy_2_description),
+            (:org, :claim_policy_3, :claim_policy_3_description);
+
+-- Set up currency types
+INSERT INTO acq.currency_type(code, label)
+    VALUES (:currency_1, :currency_1_label),
+            (:currency_2, :currency_2_label);
+
+-- Set up provider
+INSERT INTO acq.provider(name, owner, currency_type, code)
+    VALUES(:provider_1_name, :provider_1_owner, :provider_1_currency_type, :provider_1_code);
+
+--Set up EDI account
+INSERT INTO acq.edi_account(label, host, username, password, path, owner, provider, in_dir)
+    VALUES(:edi_account_1_label, :edi_account_1_host, :edi_account_1_username,
+            :edi_account_1_password, :edi_account_1_path, :edi_account_1_owner,
+            :edi_account_1_provider, :edi_account_1_in_dir);
+
+--Set up Purchase Order
+INSERT INTO acq.purchase_order(owner, creator, editor, ordering_agency, provider, name)
+    VALUES(:purchase_order_1_owner, :purchase_order_1_creator, :purchase_order_1_editor,
+            :purchase_order_1_ordering_agency, :purchase_order_1_provider, :purchase_order_1_name);
+
+-- Insert the exchange ratios to known values for the test
+INSERT INTO acq.exchange_rate(from_currency, to_currency, ratio)
+    VALUES(:currency_1, :currency_2, :currency_1_to_currency_2_exchange_rate),
+            (:currency_2, :currency_1, :currency_2_to_currency_1_exchange_rate);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_1, :owner, :currency_1, :funding_source_1
+        ), (:funding_source_2, :owner, :currency_2, :funding_source_2
+        ), (:funding_source_3, :owner, :currency_2, :funding_source_3
+        );
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_1,
+            NOW() + '1 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_2,
+            NULL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           );
+
+INSERT INTO acq.fund (org, name, year, currency_type, code, active, rollover, propagate)
+    VALUES  (:org, :fund_1, :year, :currency_1, :fund_1, TRUE, TRUE, TRUE),
+            (:org, :fund_2, :year, :currency_2, :fund_2, TRUE, TRUE, TRUE),
+            (:org, :fund_3, :year, :currency_2, :fund_3, TRUE, TRUE, TRUE);
+
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, allocator, note)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_3, :owner, 'test'
+           );
+
+INSERT INTO acq.fund_debit(fund, origin_amount, origin_currency_type, amount, encumbrance, debit_type)
+        VALUES (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_1,
+                :currency_1,
+                :fund_1_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_2,
+                :currency_1,
+                :fund_1_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_3,
+                :currency_1,
+                :fund_1_debit_3,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_1,
+                :currency_2,
+                :fund_2_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_2,
+                :currency_2,
+                :fund_2_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_debit_1,
+                :currency_2,
+                :fund_3_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_encumbrance_1,
+                :currency_1,
+                :fund_1_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_1,
+                :currency_1,
+                :fund_2_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_2,
+                :currency_1,
+                :fund_2_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_1,
+                :currency_2,
+                :fund_3_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_2,
+                :currency_2,
+                :fund_3_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_3,
+                :currency_2,
+                :fund_3_encumbrance_3,
+                TRUE,
+                'purchase'
+        );
+
+--Insert lineitems and Insert lineitem details
+INSERT INTO acq.lineitem(creator, editor, selector, provider, purchase_order, marc, eg_bib_id, source_label,
+                        estimated_unit_price, claim_policy)
+    VALUES(:lineitem_1_creator, :lineitem_1_editor, :lineitem_1_selector, :lineitem_1_provider,
+            :lineitem_1_purchase_order, :lineitem_1_marc, :lineitem_1_eg_bib_id, :lineitem_1_source_label,
+            :lineitem_1_estimated_unit_price, :lineitem_1_claim_policy
+        ), (:lineitem_2_creator, :lineitem_2_editor, :lineitem_2_selector, :lineitem_2_provider,
+            :lineitem_2_purchase_order, :lineitem_2_marc, :lineitem_2_eg_bib_id, :lineitem_2_source_label,
+            :lineitem_2_estimated_unit_price, :lineitem_2_claim_policy
+        ), (:lineitem_3_creator, :lineitem_3_editor, :lineitem_3_selector, :lineitem_3_provider,
+            :lineitem_3_purchase_order, :lineitem_3_marc, :lineitem_3_eg_bib_id, :lineitem_3_source_label,
+            :lineitem_3_estimated_unit_price, :lineitem_3_claim_policy
+        ), (:lineitem_4_creator, :lineitem_4_editor, :lineitem_4_selector, :lineitem_4_provider,
+            :lineitem_4_purchase_order, :lineitem_4_marc, :lineitem_4_eg_bib_id, :lineitem_4_source_label,
+            :lineitem_4_estimated_unit_price, :lineitem_4_claim_policy
+        ), (:lineitem_5_creator, :lineitem_5_editor, :lineitem_5_selector, :lineitem_5_provider,
+            :lineitem_5_purchase_order, :lineitem_5_marc, :lineitem_5_eg_bib_id, :lineitem_5_source_label,
+            :lineitem_5_estimated_unit_price, :lineitem_5_claim_policy
+        ), (:lineitem_6_creator, :lineitem_6_editor, :lineitem_6_selector, :lineitem_6_provider,
+            :lineitem_6_purchase_order, :lineitem_6_marc, :lineitem_6_eg_bib_id, :lineitem_6_source_label,
+            :lineitem_6_estimated_unit_price, :lineitem_6_claim_policy
+        ), (:lineitem_7_creator, :lineitem_7_editor, :lineitem_7_selector, :lineitem_7_provider,
+            :lineitem_7_purchase_order, :lineitem_7_marc, :lineitem_7_eg_bib_id, :lineitem_7_source_label,
+            :lineitem_7_estimated_unit_price, :lineitem_7_claim_policy
+        ), (:lineitem_8_creator, :lineitem_8_editor, :lineitem_8_selector, :lineitem_8_provider,
+            :lineitem_8_purchase_order, :lineitem_8_marc, :lineitem_8_eg_bib_id, :lineitem_8_source_label,
+            :lineitem_8_estimated_unit_price, :lineitem_8_claim_policy
+        ), (:lineitem_9_creator, :lineitem_9_editor, :lineitem_9_selector, :lineitem_9_provider,
+            :lineitem_9_purchase_order, :lineitem_9_marc, :lineitem_9_eg_bib_id, :lineitem_9_source_label,
+            :lineitem_9_estimated_unit_price, :lineitem_9_claim_policy
+        ), (:lineitem_10_creator, :lineitem_10_editor, :lineitem_10_selector, :lineitem_10_provider,
+            :lineitem_10_purchase_order, :lineitem_10_marc, :lineitem_10_eg_bib_id, :lineitem_10_source_label,
+            :lineitem_10_estimated_unit_price, :lineitem_10_claim_policy
+        ), (:lineitem_11_creator, :lineitem_11_editor, :lineitem_11_selector, :lineitem_11_provider,
+            :lineitem_11_purchase_order, :lineitem_11_marc, :lineitem_11_eg_bib_id, :lineitem_11_source_label,
+            :lineitem_11_estimated_unit_price, :lineitem_11_claim_policy
+        ), (:lineitem_12_creator, :lineitem_12_editor, :lineitem_12_selector, :lineitem_12_provider,
+            :lineitem_12_purchase_order, :lineitem_12_marc, :lineitem_12_eg_bib_id, :lineitem_12_source_label,
+            :lineitem_12_estimated_unit_price, :lineitem_12_claim_policy
+        );
+
+INSERT INTO acq.lineitem_detail(lineitem, fund, fund_debit)
+    VALUES(:lineitem_detail_1_lineitem, :lineitem_detail_1_fund, :lineitem_detail_1_fund_debit
+        ), (:lineitem_detail_2_lineitem, :lineitem_detail_2_fund, :lineitem_detail_2_fund_debit
+        ), (:lineitem_detail_3_lineitem, :lineitem_detail_3_fund, :lineitem_detail_3_fund_debit
+        ), (:lineitem_detail_4_lineitem, :lineitem_detail_4_fund, :lineitem_detail_4_fund_debit
+        ), (:lineitem_detail_5_lineitem, :lineitem_detail_5_fund, :lineitem_detail_5_fund_debit
+        ), (:lineitem_detail_6_lineitem, :lineitem_detail_6_fund, :lineitem_detail_6_fund_debit
+        ), (:lineitem_detail_7_lineitem, :lineitem_detail_7_fund, :lineitem_detail_7_fund_debit
+        ), (:lineitem_detail_8_lineitem, :lineitem_detail_8_fund, :lineitem_detail_8_fund_debit
+        ), (:lineitem_detail_9_lineitem, :lineitem_detail_9_fund, :lineitem_detail_9_fund_debit
+        ), (:lineitem_detail_10_lineitem, :lineitem_detail_10_fund, :lineitem_detail_10_fund_debit
+        ), (:lineitem_detail_11_lineitem, :lineitem_detail_11_fund, :lineitem_detail_11_fund_debit
+        ), (:lineitem_detail_12_lineitem, :lineitem_detail_12_fund, :lineitem_detail_12_fund_debit
+        );
+
+-- Test if the funds are propagated
+
+SELECT acq.propagate_funds_by_org_unit(:year::INT, :owner, :org);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_1 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_1 || ''', ' || :year || '), (''' || :fund_1 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_1 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_2 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_2 || ''', ' || :year || '), (''' || :fund_2 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_2 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_3 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_3 || ''', ' || :year || '), (''' || :fund_3 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_3 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT acq.rollover_funds_by_org_unit(:year::INT, :owner, :org, FALSE);
+
+-- Test if funds were transferred to next year
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' has ' ||
+            :source_1_fund_1_allocation_1 +
+            :source_1_fund_1_allocation_2 +
+            :source_1_fund_1_allocation_3 +
+            (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+            :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3 ||
+        ' funds transferred to next year.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        (:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' has ' ||
+            (:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+            :source_2_fund_2_allocation_1 +
+            :source_2_fund_2_allocation_3 +
+            :source_2_fund_2_allocation_2 +
+            :source_3_fund_2_allocation_1 +
+            :source_3_fund_2_allocation_2 +
+            :source_3_fund_2_allocation_3 -
+            :fund_2_debit_1 - :fund_2_debit_2 ||
+        ' funds transferred to next year.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        (:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' has ' ||
+            (:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+            :source_2_fund_3_allocation_1 +
+            :source_2_fund_3_allocation_3 +
+            :source_2_fund_3_allocation_2 +
+            :source_3_fund_3_allocation_1 +
+            :source_3_fund_3_allocation_2 +
+            :source_3_fund_3_allocation_3 -
+            :fund_3_debit_1 ||
+        ' funds transferred to next year.'
+);
+
+-- Test that the transferred funds were recorded in fund_transfer
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)::TEXT || ' ' ||
+        TRUNC((:source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC((:source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3), 2)::TEXT
+    ,
+        'The fund ' || :fund_1 || ' transfers were recorded in fund_transfer.'
+);
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2), 2)::TEXT
+    ,
+        'The fund ' || :fund_2 || ' transfers were recorded in fund_transfer.'
+);
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1), 2)::TEXT
+    ,
+        'The fund ' || :fund_3 || ' transfers were recorded in fund_transfer.'
+);
+
+-- Test that current year has debited funds allocated to it
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+        )
+    ,
+        :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' ' || :year || ' has ' || :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+        )
+    ,
+        :fund_2_debit_1 + :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :year || ' has ' || :fund_2_debit_1 + :fund_2_debit_2 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+        )
+    ,
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' ' || :year || ' has ' || :fund_3_debit_1 || ' allocated to it.'
+);
+
+-- Test if encumbered lineitem_details are moved to new funds
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_1_lineitem || ', ' || :lineitem_detail_1_fund_next_year || ')'
+    ,
+        'A single lineitem for Fund ' || :fund_1 || ' was moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_2_lineitem || ', ' || :lineitem_detail_2_fund_next_year || '),
+        (' || :lineitem_detail_3_lineitem || ', ' || :lineitem_detail_3_fund_next_year || ')'
+    ,
+        'Two lineitems for Fund ' || :fund_2 || ' were moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_4_lineitem || ', ' || :lineitem_detail_4_fund_next_year || '),
+        (' || :lineitem_detail_5_lineitem || ', ' || :lineitem_detail_5_fund_next_year || '),
+        (' || :lineitem_detail_6_lineitem || ', ' || :lineitem_detail_6_fund_next_year || ')'
+    ,
+        'Three lineitems for Fund ' || :fund_3 || ' were moved to ' || :next_year || '.'
+);
+
+-- Test to ensure encumbered debits were transferred
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :fund_1_encumbrance_1
+    ,
+        'Fund ' || :fund_1 || ' ' || :next_year ||  ' has ' || :fund_1_encumbrance_1 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        :fund_2_encumbrance_1 + :fund_2_encumbrance_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :next_year || ' has ' || :fund_2_encumbrance_1 + :fund_2_encumbrance_2 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3
+    ,
+        'Fund ' || :fund_3 || ' ' || :next_year || ' has ' || :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3 || ' encumbered funds allocated to it.'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_rollover_distribution_formula.pg b/Open-ILS/src/sql/Pg/t/acq_rollover_distribution_formula.pg
new file mode 100644 (file)
index 0000000..def1f3c
--- /dev/null
@@ -0,0 +1,1105 @@
+BEGIN;
+
+\set year 'date_part(''year'', NOW())'
+\set next_year 'date_part(''year'', NOW() + ''1 YEAR''::INTERVAL)'
+\set funding_source_1 '''FS1'''
+\set funding_source_2 '''FS2'''
+\set funding_source_3 '''FS3'''
+\set fund_1 '''F1'''
+\set fund_2 '''F2'''
+\set fund_3 '''F3'''
+\set org 1
+\set currency_1 '''NAC'''
+\set currency_1_label '''NAC Dollars'''
+\set currency_2 '''DSU'''
+\set currency_2_label '''DSU Dollars'''
+\set same_currency_exchange_rate 1.00
+\set currency_1_to_currency_2_exchange_rate .50
+\set currency_2_to_currency_1_exchange_rate 2.00
+\set owner 1
+
+\set funding_source_1_credit_1 3000.00
+\set funding_source_1_credit_2 2000.00
+\set funding_source_1_credit_3 100.00
+
+\set funding_source_2_credit_1 5.00
+\set funding_source_2_credit_2 450.00
+\set funding_source_2_credit_3 1500.00
+
+\set funding_source_3_credit_1 675.00
+\set funding_source_3_credit_2 10000.00
+\set funding_source_3_credit_3 934.00
+
+\set source_1_fund_1_allocation_1 500.00
+\set source_1_fund_1_allocation_2 200.00
+\set source_1_fund_1_allocation_3 0.00
+\set source_1_fund_2_allocation_1 155.00
+\set source_1_fund_2_allocation_2 300.00
+\set source_1_fund_2_allocation_3 400.00
+\set source_1_fund_3_allocation_1 45.00
+\set source_1_fund_3_allocation_2 200.00
+\set source_1_fund_3_allocation_3 1000.00
+
+\set source_2_fund_1_allocation_1 100.00
+\set source_2_fund_1_allocation_2 50.00
+\set source_2_fund_1_allocation_3 -400.00
+\set source_2_fund_2_allocation_1 35.00
+\set source_2_fund_2_allocation_2 200.00
+\set source_2_fund_2_allocation_3 200.00
+\set source_2_fund_3_allocation_1 400.00
+\set source_2_fund_3_allocation_2 -25.00
+\set source_2_fund_3_allocation_3 122.00
+
+\set source_3_fund_1_allocation_1 5000.00
+\set source_3_fund_1_allocation_2 135.00
+\set source_3_fund_1_allocation_3 300.00
+\set source_3_fund_2_allocation_1 2000.00
+\set source_3_fund_2_allocation_2 22.00
+\set source_3_fund_2_allocation_3 100.00
+\set source_3_fund_3_allocation_1 333.00
+\set source_3_fund_3_allocation_2 200.00
+\set source_3_fund_3_allocation_3 500.00
+
+\set fund_1_debit_1 200.00
+\set fund_1_debit_2 150.00
+\set fund_1_debit_3 25.98
+
+\set fund_2_debit_1 555.55
+\set fund_2_debit_2 0.00
+
+\set fund_3_debit_1 1000.22
+
+\set fund_1_encumbrance_1 2234.54
+
+\set fund_2_encumbrance_1 123.45
+\set fund_2_encumbrance_2 111.11
+
+\set fund_3_encumbrance_1 435.43
+\set fund_3_encumbrance_2 10.14
+\set fund_3_encumbrance_3 0.00
+
+\set claim_policy_1 '''4 Week Phone'''
+\set claim_policy_1_description '''Phone the provider after 4 Weeks'''
+\set claim_policy_2 '''4 Week Email'''
+\set claim_policy_2_description '''Email the provider after 4 Weeks'''
+\set claim_policy_3 '''4 Week Fax'''
+\set claim_policy_3_description '''Fax the provider after 4 Weeks'''
+
+\set provider_1_name '''Test Provider'''
+\set provider_1_owner :owner
+\set provider_1_currency_type :currency_1
+\set provider_1_code '''TESTP'''
+
+\set edi_account_1_label '''Test Account'''
+\set edi_account_1_host  '''ftp://ftp.test.com'''
+\set edi_account_1_username '''username'''
+\set edi_account_1_password '''password'''
+\set edi_account_1_path '''/path/.*'''
+\set edi_account_1_owner :owner
+\set edi_account_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set edi_account_1_in_dir '''/in'''
+
+\set purchase_order_1_owner :owner
+\set purchase_order_1_creator :owner
+\set purchase_order_1_editor :owner
+\set purchase_order_1_ordering_agency :org
+\set purchase_order_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set purchase_order_1_name '''PO1'''
+
+\set lineitem_1_creator :owner
+\set lineitem_1_editor :owner
+\set lineitem_1_selector :owner
+\set lineitem_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_1_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_1_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_1_eg_bib_id 1
+\set lineitem_1_source_label '''native-evergreen-catalog'''
+\set lineitem_1_estimated_unit_price :fund_1_encumbrance_1
+\set lineitem_1_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_1_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_1_eg_bib_id)'
+\set lineitem_detail_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_1_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_1_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_encumbrance_1)'
+
+\set lineitem_2_creator :owner
+\set lineitem_2_editor :owner
+\set lineitem_2_selector :owner
+\set lineitem_2_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_2_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_2_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_2_eg_bib_id 2
+\set lineitem_2_source_label '''native-evergreen-catalog'''
+\set lineitem_2_estimated_unit_price :fund_2_encumbrance_1
+\set lineitem_2_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_2_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_2_eg_bib_id)'
+\set lineitem_detail_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_2_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_2_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_1)'
+
+\set lineitem_3_creator :owner
+\set lineitem_3_editor :owner
+\set lineitem_3_selector :owner
+\set lineitem_3_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_3_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_3_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_3_eg_bib_id 3
+\set lineitem_3_source_label '''native-evergreen-catalog'''
+\set lineitem_3_estimated_unit_price :fund_2_encumbrance_2
+\set lineitem_3_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_3_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_3_eg_bib_id)'
+\set lineitem_detail_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_3_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_3_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_2)'
+
+\set lineitem_4_creator :owner
+\set lineitem_4_editor :owner
+\set lineitem_4_selector :owner
+\set lineitem_4_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_4_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_4_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_4_eg_bib_id 4
+\set lineitem_4_source_label '''native-evergreen-catalog'''
+\set lineitem_4_estimated_unit_price :fund_3_encumbrance_1
+\set lineitem_4_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_4_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_4_eg_bib_id)'
+\set lineitem_detail_4_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_4_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_4_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_1)'
+
+\set lineitem_5_creator :owner
+\set lineitem_5_editor :owner
+\set lineitem_5_selector :owner
+\set lineitem_5_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_5_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_5_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_5_eg_bib_id 5
+\set lineitem_5_source_label '''native-evergreen-catalog'''
+\set lineitem_5_estimated_unit_price :fund_3_encumbrance_2
+\set lineitem_5_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_5_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_5_eg_bib_id)'
+\set lineitem_detail_5_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_5_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_5_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_2)'
+
+\set lineitem_6_creator :owner
+\set lineitem_6_editor :owner
+\set lineitem_6_selector :owner
+\set lineitem_6_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_6_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_6_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_6_eg_bib_id 6
+\set lineitem_6_source_label '''native-evergreen-catalog'''
+\set lineitem_6_estimated_unit_price :fund_3_encumbrance_3
+\set lineitem_6_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_6_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_6_eg_bib_id)'
+\set lineitem_detail_6_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_6_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_6_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_3)'
+
+\set lineitem_7_creator :owner
+\set lineitem_7_editor :owner
+\set lineitem_7_selector :owner
+\set lineitem_7_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_7_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_7_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_7_eg_bib_id 7
+\set lineitem_7_source_label '''native-evergreen-catalog'''
+\set lineitem_7_estimated_unit_price :fund_1_debit_1
+\set lineitem_7_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_7_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_7_eg_bib_id)'
+\set lineitem_detail_7_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_7_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_7_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_8_creator :owner
+\set lineitem_8_editor :owner
+\set lineitem_8_selector :owner
+\set lineitem_8_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_8_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_8_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_8_eg_bib_id 8
+\set lineitem_8_source_label '''native-evergreen-catalog'''
+\set lineitem_8_estimated_unit_price :fund_1_debit_2
+\set lineitem_8_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_8_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_8_eg_bib_id)'
+\set lineitem_detail_8_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_8_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_8_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_9_creator :owner
+\set lineitem_9_editor :owner
+\set lineitem_9_selector :owner
+\set lineitem_9_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_9_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_9_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_9_eg_bib_id 9
+\set lineitem_9_source_label '''native-evergreen-catalog'''
+\set lineitem_9_estimated_unit_price :fund_1_debit_3
+\set lineitem_9_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_9_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_9_eg_bib_id)'
+\set lineitem_detail_9_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_9_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_9_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_3)'
+
+\set lineitem_10_creator :owner
+\set lineitem_10_editor :owner
+\set lineitem_10_selector :owner
+\set lineitem_10_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_10_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_10_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_10_eg_bib_id 10
+\set lineitem_10_source_label '''native-evergreen-catalog'''
+\set lineitem_10_estimated_unit_price :fund_2_debit_1
+\set lineitem_10_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_10_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_10_eg_bib_id)'
+\set lineitem_detail_10_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_10_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_10_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_1)'
+
+\set lineitem_11_creator :owner
+\set lineitem_11_editor :owner
+\set lineitem_11_selector :owner
+\set lineitem_11_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_11_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_11_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_11_eg_bib_id 11
+\set lineitem_11_source_label '''native-evergreen-catalog'''
+\set lineitem_11_estimated_unit_price :fund_2_debit_2
+\set lineitem_11_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_11_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_11_eg_bib_id)'
+\set lineitem_detail_11_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_11_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_11_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_2)'
+
+\set lineitem_12_creator :owner
+\set lineitem_12_editor :owner
+\set lineitem_12_selector :owner
+\set lineitem_12_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_12_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_12_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_12_eg_bib_id 12
+\set lineitem_12_source_label '''native-evergreen-catalog'''
+\set lineitem_12_estimated_unit_price :fund_3_debit_1
+\set lineitem_12_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_12_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_12_eg_bib_id)'
+\set lineitem_detail_12_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_12_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_12_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_debit_1)'
+
+\set distribution_formula_1_owner :owner
+\set distribution_formula_1_name '''Formula 1'''
+\set distribution_formula_2_owner :owner
+\set distribution_formula_2_name '''Formula 2'''
+\set distribution_formula_3_owner :owner
+\set distribution_formula_3_name '''Formula 3'''
+
+\set distribution_formula_entry_1_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_1_name)'
+\set distribution_formula_entry_1_position 1
+\set distribution_formula_entry_1_item_count 1
+\set distribution_formula_entry_1_owning_lib :org
+\set distribution_formula_entry_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+
+\set distribution_formula_entry_2_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_2_name)'
+\set distribution_formula_entry_2_position 2
+\set distribution_formula_entry_2_item_count 2
+\set distribution_formula_entry_2_owning_lib :org
+\set distribution_formula_entry_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+
+\set distribution_formula_entry_3_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_3_name)'
+\set distribution_formula_entry_3_position 3
+\set distribution_formula_entry_3_item_count 3
+\set distribution_formula_entry_3_owning_lib :org
+\set distribution_formula_entry_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+
+SELECT plan(21);
+
+-- Set up the org to rollover all funds
+
+DELETE FROM actor.org_unit_setting
+WHERE org_unit = :org AND name = 'acq.fund.allow_rollover_without_money';
+
+INSERT INTO actor.org_unit_setting(org_unit, name, value)
+    VALUES(:org, 'acq.fund.allow_rollover_without_money', false);
+
+-- Set up formula to rollover
+
+DELETE FROM actor.org_unit_setting
+WHERE org_unit = :org AND name = 'acq.fund.rollover_distrib_forms';
+
+INSERT INTO actor.org_unit_setting(org_unit, name, value)
+    VALUES(:org, 'acq.fund.rollover_distrib_forms', true);
+
+-- Set up the claim poilicies
+
+INSERT INTO acq.claim_policy(org_unit, name, description)
+    VALUES (:org, :claim_policy_1, :claim_policy_1_description),
+            (:org, :claim_policy_2, :claim_policy_2_description),
+            (:org, :claim_policy_3, :claim_policy_3_description);
+
+-- Set up currency types
+INSERT INTO acq.currency_type(code, label)
+    VALUES (:currency_1, :currency_1_label),
+            (:currency_2, :currency_2_label);
+
+-- Set up provider
+INSERT INTO acq.provider(name, owner, currency_type, code)
+    VALUES(:provider_1_name, :provider_1_owner, :provider_1_currency_type, :provider_1_code);
+
+--Set up EDI account
+INSERT INTO acq.edi_account(label, host, username, password, path, owner, provider, in_dir)
+    VALUES(:edi_account_1_label, :edi_account_1_host, :edi_account_1_username,
+            :edi_account_1_password, :edi_account_1_path, :edi_account_1_owner,
+            :edi_account_1_provider, :edi_account_1_in_dir);
+
+--Set up Purchase Order
+INSERT INTO acq.purchase_order(owner, creator, editor, ordering_agency, provider, name)
+    VALUES(:purchase_order_1_owner, :purchase_order_1_creator, :purchase_order_1_editor,
+            :purchase_order_1_ordering_agency, :purchase_order_1_provider, :purchase_order_1_name);
+
+-- Insert the exchange ratios to known values for the test
+INSERT INTO acq.exchange_rate(from_currency, to_currency, ratio)
+    VALUES(:currency_1, :currency_2, :currency_1_to_currency_2_exchange_rate),
+            (:currency_2, :currency_1, :currency_2_to_currency_1_exchange_rate);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_1, :owner, :currency_1, :funding_source_1
+        ), (:funding_source_2, :owner, :currency_2, :funding_source_2
+        ), (:funding_source_3, :owner, :currency_2, :funding_source_3
+        );
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_1,
+            NOW() + '1 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_2,
+            NULL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           );
+
+INSERT INTO acq.fund (org, name, year, currency_type, code, active, rollover, propagate)
+    VALUES  (:org, :fund_1, :year, :currency_1, :fund_1, TRUE, TRUE, TRUE),
+            (:org, :fund_2, :year, :currency_2, :fund_2, TRUE, TRUE, TRUE),
+            (:org, :fund_3, :year, :currency_2, :fund_3, TRUE, TRUE, TRUE);
+
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, allocator, note)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_3, :owner, 'test'
+           );
+
+INSERT INTO acq.fund_debit(fund, origin_amount, origin_currency_type, amount, encumbrance, debit_type)
+        VALUES (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_1,
+                :currency_1,
+                :fund_1_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_2,
+                :currency_1,
+                :fund_1_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_3,
+                :currency_1,
+                :fund_1_debit_3,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_1,
+                :currency_2,
+                :fund_2_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_2,
+                :currency_2,
+                :fund_2_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_debit_1,
+                :currency_2,
+                :fund_3_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_encumbrance_1,
+                :currency_1,
+                :fund_1_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_1,
+                :currency_1,
+                :fund_2_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_2,
+                :currency_1,
+                :fund_2_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_1,
+                :currency_2,
+                :fund_3_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_2,
+                :currency_2,
+                :fund_3_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_3,
+                :currency_2,
+                :fund_3_encumbrance_3,
+                TRUE,
+                'purchase'
+        );
+
+--Insert lineitems and Insert lineitem details
+INSERT INTO acq.lineitem(creator, editor, selector, provider, purchase_order, marc, eg_bib_id, source_label,
+                        estimated_unit_price, claim_policy)
+    VALUES(:lineitem_1_creator, :lineitem_1_editor, :lineitem_1_selector, :lineitem_1_provider,
+            :lineitem_1_purchase_order, :lineitem_1_marc, :lineitem_1_eg_bib_id, :lineitem_1_source_label,
+            :lineitem_1_estimated_unit_price, :lineitem_1_claim_policy
+        ), (:lineitem_2_creator, :lineitem_2_editor, :lineitem_2_selector, :lineitem_2_provider,
+            :lineitem_2_purchase_order, :lineitem_2_marc, :lineitem_2_eg_bib_id, :lineitem_2_source_label,
+            :lineitem_2_estimated_unit_price, :lineitem_2_claim_policy
+        ), (:lineitem_3_creator, :lineitem_3_editor, :lineitem_3_selector, :lineitem_3_provider,
+            :lineitem_3_purchase_order, :lineitem_3_marc, :lineitem_3_eg_bib_id, :lineitem_3_source_label,
+            :lineitem_3_estimated_unit_price, :lineitem_3_claim_policy
+        ), (:lineitem_4_creator, :lineitem_4_editor, :lineitem_4_selector, :lineitem_4_provider,
+            :lineitem_4_purchase_order, :lineitem_4_marc, :lineitem_4_eg_bib_id, :lineitem_4_source_label,
+            :lineitem_4_estimated_unit_price, :lineitem_4_claim_policy
+        ), (:lineitem_5_creator, :lineitem_5_editor, :lineitem_5_selector, :lineitem_5_provider,
+            :lineitem_5_purchase_order, :lineitem_5_marc, :lineitem_5_eg_bib_id, :lineitem_5_source_label,
+            :lineitem_5_estimated_unit_price, :lineitem_5_claim_policy
+        ), (:lineitem_6_creator, :lineitem_6_editor, :lineitem_6_selector, :lineitem_6_provider,
+            :lineitem_6_purchase_order, :lineitem_6_marc, :lineitem_6_eg_bib_id, :lineitem_6_source_label,
+            :lineitem_6_estimated_unit_price, :lineitem_6_claim_policy
+        ), (:lineitem_7_creator, :lineitem_7_editor, :lineitem_7_selector, :lineitem_7_provider,
+            :lineitem_7_purchase_order, :lineitem_7_marc, :lineitem_7_eg_bib_id, :lineitem_7_source_label,
+            :lineitem_7_estimated_unit_price, :lineitem_7_claim_policy
+        ), (:lineitem_8_creator, :lineitem_8_editor, :lineitem_8_selector, :lineitem_8_provider,
+            :lineitem_8_purchase_order, :lineitem_8_marc, :lineitem_8_eg_bib_id, :lineitem_8_source_label,
+            :lineitem_8_estimated_unit_price, :lineitem_8_claim_policy
+        ), (:lineitem_9_creator, :lineitem_9_editor, :lineitem_9_selector, :lineitem_9_provider,
+            :lineitem_9_purchase_order, :lineitem_9_marc, :lineitem_9_eg_bib_id, :lineitem_9_source_label,
+            :lineitem_9_estimated_unit_price, :lineitem_9_claim_policy
+        ), (:lineitem_10_creator, :lineitem_10_editor, :lineitem_10_selector, :lineitem_10_provider,
+            :lineitem_10_purchase_order, :lineitem_10_marc, :lineitem_10_eg_bib_id, :lineitem_10_source_label,
+            :lineitem_10_estimated_unit_price, :lineitem_10_claim_policy
+        ), (:lineitem_11_creator, :lineitem_11_editor, :lineitem_11_selector, :lineitem_11_provider,
+            :lineitem_11_purchase_order, :lineitem_11_marc, :lineitem_11_eg_bib_id, :lineitem_11_source_label,
+            :lineitem_11_estimated_unit_price, :lineitem_11_claim_policy
+        ), (:lineitem_12_creator, :lineitem_12_editor, :lineitem_12_selector, :lineitem_12_provider,
+            :lineitem_12_purchase_order, :lineitem_12_marc, :lineitem_12_eg_bib_id, :lineitem_12_source_label,
+            :lineitem_12_estimated_unit_price, :lineitem_12_claim_policy
+        );
+
+INSERT INTO acq.lineitem_detail(lineitem, fund, fund_debit)
+    VALUES(:lineitem_detail_1_lineitem, :lineitem_detail_1_fund, :lineitem_detail_1_fund_debit
+        ), (:lineitem_detail_2_lineitem, :lineitem_detail_2_fund, :lineitem_detail_2_fund_debit
+        ), (:lineitem_detail_3_lineitem, :lineitem_detail_3_fund, :lineitem_detail_3_fund_debit
+        ), (:lineitem_detail_4_lineitem, :lineitem_detail_4_fund, :lineitem_detail_4_fund_debit
+        ), (:lineitem_detail_5_lineitem, :lineitem_detail_5_fund, :lineitem_detail_5_fund_debit
+        ), (:lineitem_detail_6_lineitem, :lineitem_detail_6_fund, :lineitem_detail_6_fund_debit
+        ), (:lineitem_detail_7_lineitem, :lineitem_detail_7_fund, :lineitem_detail_7_fund_debit
+        ), (:lineitem_detail_8_lineitem, :lineitem_detail_8_fund, :lineitem_detail_8_fund_debit
+        ), (:lineitem_detail_9_lineitem, :lineitem_detail_9_fund, :lineitem_detail_9_fund_debit
+        ), (:lineitem_detail_10_lineitem, :lineitem_detail_10_fund, :lineitem_detail_10_fund_debit
+        ), (:lineitem_detail_11_lineitem, :lineitem_detail_11_fund, :lineitem_detail_11_fund_debit
+        ), (:lineitem_detail_12_lineitem, :lineitem_detail_12_fund, :lineitem_detail_12_fund_debit
+        );
+
+-- Insert distribution formulae
+INSERT INTO acq.distribution_formula(owner, name)
+    VALUES(:distribution_formula_1_owner, :distribution_formula_1_name
+        ), (:distribution_formula_2_owner, :distribution_formula_2_name
+        ), (:distribution_formula_3_owner, :distribution_formula_3_name
+        );
+
+INSERT INTO acq.distribution_formula_entry(formula, position, item_count, owning_lib, fund)
+    VALUES(:distribution_formula_entry_1_formula, :distribution_formula_entry_1_position,
+            :distribution_formula_entry_1_item_count, :distribution_formula_entry_1_owning_lib,
+            :distribution_formula_entry_1_fund
+        ), (:distribution_formula_entry_2_formula, :distribution_formula_entry_2_position,
+            :distribution_formula_entry_2_item_count, :distribution_formula_entry_2_owning_lib,
+            :distribution_formula_entry_2_fund
+        ), (:distribution_formula_entry_3_formula, :distribution_formula_entry_3_position,
+            :distribution_formula_entry_3_item_count, :distribution_formula_entry_3_owning_lib,
+            :distribution_formula_entry_3_fund
+        );
+
+-- Test if the funds are propagated
+
+SELECT acq.propagate_funds_by_org_unit(:year::INT, :owner, :org);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_1 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_1 || ''', ' || :year || '), (''' || :fund_1 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_1 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_2 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_2 || ''', ' || :year || '), (''' || :fund_2 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_2 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_3 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_3 || ''', ' || :year || '), (''' || :fund_3 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_3 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT acq.rollover_funds_by_org_unit(:year::INT, :owner, :org, FALSE);
+
+-- Test if funds were transferred to next year
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' has ' ||
+            :source_1_fund_1_allocation_1 +
+            :source_1_fund_1_allocation_2 +
+            :source_1_fund_1_allocation_3 +
+            (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+            (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+            :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3 ||
+        ' funds transferred to next year.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        (:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' has ' ||
+            (:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+            :source_2_fund_2_allocation_1 +
+            :source_2_fund_2_allocation_3 +
+            :source_2_fund_2_allocation_2 +
+            :source_3_fund_2_allocation_1 +
+            :source_3_fund_2_allocation_2 +
+            :source_3_fund_2_allocation_3 -
+            :fund_2_debit_1 - :fund_2_debit_2 ||
+        ' funds transferred to next year.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        (:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' has ' ||
+            (:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+            (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+            :source_2_fund_3_allocation_1 +
+            :source_2_fund_3_allocation_3 +
+            :source_2_fund_3_allocation_2 +
+            :source_3_fund_3_allocation_1 +
+            :source_3_fund_3_allocation_2 +
+            :source_3_fund_3_allocation_3 -
+            :fund_3_debit_1 ||
+        ' funds transferred to next year.'
+);
+
+-- Test that the transferred funds were recorded in fund_transfer
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)::TEXT || ' ' ||
+        TRUNC((:source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC((:source_1_fund_1_allocation_1 +
+        :source_1_fund_1_allocation_2 +
+        :source_1_fund_1_allocation_3 +
+        (:source_2_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_2_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_1 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_2 * :currency_2_to_currency_1_exchange_rate) +
+        (:source_3_fund_1_allocation_3 * :currency_2_to_currency_1_exchange_rate) -
+        :fund_1_debit_1 - :fund_1_debit_2 - :fund_1_debit_3), 2)::TEXT
+    ,
+        'The fund ' || :fund_1 || ' transfers were recorded in fund_transfer.'
+);
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_2_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_2_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_2_allocation_1 +
+        :source_2_fund_2_allocation_2 +
+        :source_2_fund_2_allocation_3 +
+        :source_3_fund_2_allocation_1 +
+        :source_3_fund_2_allocation_2 +
+        :source_3_fund_2_allocation_3 -
+        :fund_2_debit_1 - :fund_2_debit_2), 2)::TEXT
+    ,
+        'The fund ' || :fund_2 || ' transfers were recorded in fund_transfer.'
+);
+
+SELECT is(
+        (
+            SELECT src_fund::TEXT || ' ' || SUM(src_amount)::TEXT || ' ' ||  dest_fund::TEXT || ' ' || SUM(dest_amount)::TEXT
+            FROM acq.fund_transfer
+            WHERE src_fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+            AND dest_fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+            GROUP BY src_fund, dest_fund
+        )
+    ,
+        (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1), 2)::TEXT || ' ' ||
+        (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)::TEXT || ' ' ||
+        TRUNC(((:source_1_fund_3_allocation_1 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_2 * :currency_1_to_currency_2_exchange_rate) +
+        (:source_1_fund_3_allocation_3 * :currency_1_to_currency_2_exchange_rate) +
+        :source_2_fund_3_allocation_1 +
+        :source_2_fund_3_allocation_2 +
+        :source_2_fund_3_allocation_3 +
+        :source_3_fund_3_allocation_1 +
+        :source_3_fund_3_allocation_2 +
+        :source_3_fund_3_allocation_3 -
+        :fund_3_debit_1), 2)::TEXT
+    ,
+        'The fund ' || :fund_3 || ' transfers were recorded in fund_transfer.'
+);
+
+-- Test that current year has debited funds allocated to it
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+        )
+    ,
+        :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' ' || :year || ' has ' || :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+        )
+    ,
+        :fund_2_debit_1 + :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :year || ' has ' || :fund_2_debit_1 + :fund_2_debit_2 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+        )
+    ,
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' ' || :year || ' has ' || :fund_3_debit_1 || ' allocated to it.'
+);
+
+-- Test if encumbered lineitem_details are moved to new funds
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_1_lineitem || ', ' || :lineitem_detail_1_fund_next_year || ')'
+    ,
+        'A single lineitem for Fund ' || :fund_1 || ' was moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_2_lineitem || ', ' || :lineitem_detail_2_fund_next_year || '),
+        (' || :lineitem_detail_3_lineitem || ', ' || :lineitem_detail_3_fund_next_year || ')'
+    ,
+        'Two lineitems for Fund ' || :fund_2 || ' were moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_4_lineitem || ', ' || :lineitem_detail_4_fund_next_year || '),
+        (' || :lineitem_detail_5_lineitem || ', ' || :lineitem_detail_5_fund_next_year || '),
+        (' || :lineitem_detail_6_lineitem || ', ' || :lineitem_detail_6_fund_next_year || ')'
+    ,
+        'Three lineitems for Fund ' || :fund_3 || ' were moved to ' || :next_year || '.'
+);
+
+-- Test to ensure encumbered debits were transferred
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :fund_1_encumbrance_1
+    ,
+        'Fund ' || :fund_1 || ' ' || :next_year ||  ' has ' || :fund_1_encumbrance_1 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        :fund_2_encumbrance_1 + :fund_2_encumbrance_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :next_year || ' has ' || :fund_2_encumbrance_1 + :fund_2_encumbrance_2 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3
+    ,
+        'Fund ' || :fund_3 || ' ' || :next_year || ' has ' || :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3 || ' encumbered funds allocated to it.'
+);
+
+-- Ensure the distribution forumlae were rolled over
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_1_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_1 || ' are rolled over.'
+);
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_2_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_2 || ' are rolled over.'
+);
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_3_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_3 || ' are rolled over.'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only.pg b/Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only.pg
new file mode 100644 (file)
index 0000000..ad5ddcb
--- /dev/null
@@ -0,0 +1,850 @@
+BEGIN;
+
+\set year 'date_part(''year'', NOW())'
+\set next_year 'date_part(''year'', NOW() + ''1 YEAR''::INTERVAL)'
+\set funding_source_1 '''FS1'''
+\set funding_source_2 '''FS2'''
+\set funding_source_3 '''FS3'''
+\set fund_1 '''F1'''
+\set fund_2 '''F2'''
+\set fund_3 '''F3'''
+\set org 1
+\set currency_1 '''NAC'''
+\set currency_1_label '''NAC Dollars'''
+\set currency_2 '''DSU'''
+\set currency_2_label '''DSU Dollars'''
+\set same_currency_exchange_rate 1.00
+\set currency_1_to_currency_2_exchange_rate .50
+\set currency_2_to_currency_1_exchange_rate 2.00
+\set owner 1
+
+\set funding_source_1_credit_1 3000.00
+\set funding_source_1_credit_2 2000.00
+\set funding_source_1_credit_3 100.00
+
+\set funding_source_2_credit_1 5.00
+\set funding_source_2_credit_2 450.00
+\set funding_source_2_credit_3 1500.00
+
+\set funding_source_3_credit_1 675.00
+\set funding_source_3_credit_2 10000.00
+\set funding_source_3_credit_3 934.00
+
+\set source_1_fund_1_allocation_1 500.00
+\set source_1_fund_1_allocation_2 200.00
+\set source_1_fund_1_allocation_3 0.00
+\set source_1_fund_2_allocation_1 155.00
+\set source_1_fund_2_allocation_2 300.00
+\set source_1_fund_2_allocation_3 400.00
+\set source_1_fund_3_allocation_1 45.00
+\set source_1_fund_3_allocation_2 200.00
+\set source_1_fund_3_allocation_3 1000.00
+
+\set source_2_fund_1_allocation_1 100.00
+\set source_2_fund_1_allocation_2 50.00
+\set source_2_fund_1_allocation_3 -400.00
+\set source_2_fund_2_allocation_1 35.00
+\set source_2_fund_2_allocation_2 200.00
+\set source_2_fund_2_allocation_3 200.00
+\set source_2_fund_3_allocation_1 400.00
+\set source_2_fund_3_allocation_2 -25.00
+\set source_2_fund_3_allocation_3 122.00
+
+\set source_3_fund_1_allocation_1 5000.00
+\set source_3_fund_1_allocation_2 135.00
+\set source_3_fund_1_allocation_3 300.00
+\set source_3_fund_2_allocation_1 2000.00
+\set source_3_fund_2_allocation_2 22.00
+\set source_3_fund_2_allocation_3 100.00
+\set source_3_fund_3_allocation_1 333.00
+\set source_3_fund_3_allocation_2 200.00
+\set source_3_fund_3_allocation_3 500.00
+
+\set fund_1_debit_1 200.00
+\set fund_1_debit_2 150.00
+\set fund_1_debit_3 25.98
+
+\set fund_2_debit_1 555.55
+\set fund_2_debit_2 0.00
+
+\set fund_3_debit_1 1000.22
+
+\set fund_1_encumbrance_1 2234.54
+
+\set fund_2_encumbrance_1 123.45
+\set fund_2_encumbrance_2 111.11
+
+\set fund_3_encumbrance_1 435.43
+\set fund_3_encumbrance_2 10.14
+\set fund_3_encumbrance_3 0.00
+
+\set claim_policy_1 '''4 Week Phone'''
+\set claim_policy_1_description '''Phone the provider after 4 Weeks'''
+\set claim_policy_2 '''4 Week Email'''
+\set claim_policy_2_description '''Email the provider after 4 Weeks'''
+\set claim_policy_3 '''4 Week Fax'''
+\set claim_policy_3_description '''Fax the provider after 4 Weeks'''
+
+\set provider_1_name '''Test Provider'''
+\set provider_1_owner :owner
+\set provider_1_currency_type :currency_1
+\set provider_1_code '''TESTP'''
+
+\set edi_account_1_label '''Test Account'''
+\set edi_account_1_host  '''ftp://ftp.test.com'''
+\set edi_account_1_username '''username'''
+\set edi_account_1_password '''password'''
+\set edi_account_1_path '''/path/.*'''
+\set edi_account_1_owner :owner
+\set edi_account_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set edi_account_1_in_dir '''/in'''
+
+\set purchase_order_1_owner :owner
+\set purchase_order_1_creator :owner
+\set purchase_order_1_editor :owner
+\set purchase_order_1_ordering_agency :org
+\set purchase_order_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set purchase_order_1_name '''PO1'''
+
+\set lineitem_1_creator :owner
+\set lineitem_1_editor :owner
+\set lineitem_1_selector :owner
+\set lineitem_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_1_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_1_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_1_eg_bib_id 1
+\set lineitem_1_source_label '''native-evergreen-catalog'''
+\set lineitem_1_estimated_unit_price :fund_1_encumbrance_1
+\set lineitem_1_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_1_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_1_eg_bib_id)'
+\set lineitem_detail_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_1_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_1_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_encumbrance_1)'
+
+\set lineitem_2_creator :owner
+\set lineitem_2_editor :owner
+\set lineitem_2_selector :owner
+\set lineitem_2_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_2_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_2_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_2_eg_bib_id 2
+\set lineitem_2_source_label '''native-evergreen-catalog'''
+\set lineitem_2_estimated_unit_price :fund_2_encumbrance_1
+\set lineitem_2_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_2_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_2_eg_bib_id)'
+\set lineitem_detail_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_2_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_2_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_1)'
+
+\set lineitem_3_creator :owner
+\set lineitem_3_editor :owner
+\set lineitem_3_selector :owner
+\set lineitem_3_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_3_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_3_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_3_eg_bib_id 3
+\set lineitem_3_source_label '''native-evergreen-catalog'''
+\set lineitem_3_estimated_unit_price :fund_2_encumbrance_2
+\set lineitem_3_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_3_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_3_eg_bib_id)'
+\set lineitem_detail_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_3_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_3_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_2)'
+
+\set lineitem_4_creator :owner
+\set lineitem_4_editor :owner
+\set lineitem_4_selector :owner
+\set lineitem_4_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_4_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_4_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_4_eg_bib_id 4
+\set lineitem_4_source_label '''native-evergreen-catalog'''
+\set lineitem_4_estimated_unit_price :fund_3_encumbrance_1
+\set lineitem_4_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_4_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_4_eg_bib_id)'
+\set lineitem_detail_4_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_4_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_4_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_1)'
+
+\set lineitem_5_creator :owner
+\set lineitem_5_editor :owner
+\set lineitem_5_selector :owner
+\set lineitem_5_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_5_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_5_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_5_eg_bib_id 5
+\set lineitem_5_source_label '''native-evergreen-catalog'''
+\set lineitem_5_estimated_unit_price :fund_3_encumbrance_2
+\set lineitem_5_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_5_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_5_eg_bib_id)'
+\set lineitem_detail_5_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_5_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_5_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_2)'
+
+\set lineitem_6_creator :owner
+\set lineitem_6_editor :owner
+\set lineitem_6_selector :owner
+\set lineitem_6_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_6_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_6_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_6_eg_bib_id 6
+\set lineitem_6_source_label '''native-evergreen-catalog'''
+\set lineitem_6_estimated_unit_price :fund_3_encumbrance_3
+\set lineitem_6_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_6_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_6_eg_bib_id)'
+\set lineitem_detail_6_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_6_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_6_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_3)'
+
+\set lineitem_7_creator :owner
+\set lineitem_7_editor :owner
+\set lineitem_7_selector :owner
+\set lineitem_7_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_7_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_7_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_7_eg_bib_id 7
+\set lineitem_7_source_label '''native-evergreen-catalog'''
+\set lineitem_7_estimated_unit_price :fund_1_debit_1
+\set lineitem_7_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_7_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_7_eg_bib_id)'
+\set lineitem_detail_7_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_7_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_7_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_8_creator :owner
+\set lineitem_8_editor :owner
+\set lineitem_8_selector :owner
+\set lineitem_8_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_8_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_8_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_8_eg_bib_id 8
+\set lineitem_8_source_label '''native-evergreen-catalog'''
+\set lineitem_8_estimated_unit_price :fund_1_debit_2
+\set lineitem_8_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_8_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_8_eg_bib_id)'
+\set lineitem_detail_8_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_8_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_8_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_9_creator :owner
+\set lineitem_9_editor :owner
+\set lineitem_9_selector :owner
+\set lineitem_9_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_9_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_9_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_9_eg_bib_id 9
+\set lineitem_9_source_label '''native-evergreen-catalog'''
+\set lineitem_9_estimated_unit_price :fund_1_debit_3
+\set lineitem_9_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_9_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_9_eg_bib_id)'
+\set lineitem_detail_9_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_9_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_9_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_3)'
+
+\set lineitem_10_creator :owner
+\set lineitem_10_editor :owner
+\set lineitem_10_selector :owner
+\set lineitem_10_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_10_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_10_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_10_eg_bib_id 10
+\set lineitem_10_source_label '''native-evergreen-catalog'''
+\set lineitem_10_estimated_unit_price :fund_2_debit_1
+\set lineitem_10_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_10_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_10_eg_bib_id)'
+\set lineitem_detail_10_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_10_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_10_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_1)'
+
+\set lineitem_11_creator :owner
+\set lineitem_11_editor :owner
+\set lineitem_11_selector :owner
+\set lineitem_11_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_11_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_11_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_11_eg_bib_id 11
+\set lineitem_11_source_label '''native-evergreen-catalog'''
+\set lineitem_11_estimated_unit_price :fund_2_debit_2
+\set lineitem_11_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_11_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_11_eg_bib_id)'
+\set lineitem_detail_11_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_11_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_11_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_2)'
+
+\set lineitem_12_creator :owner
+\set lineitem_12_editor :owner
+\set lineitem_12_selector :owner
+\set lineitem_12_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_12_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_12_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_12_eg_bib_id 12
+\set lineitem_12_source_label '''native-evergreen-catalog'''
+\set lineitem_12_estimated_unit_price :fund_3_debit_1
+\set lineitem_12_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_12_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_12_eg_bib_id)'
+\set lineitem_detail_12_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_12_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_12_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_debit_1)'
+
+SELECT plan(15);
+
+-- Set up the org to allow encumbrance only rollover
+
+UPDATE actor.org_unit_setting
+SET value = true
+WHERE name = 'acq.fund.allow_rollover_without_money'
+AND org_unit = :org;
+
+-- Set up the claim poilicies
+
+INSERT INTO acq.claim_policy(org_unit, name, description)
+    VALUES (:org, :claim_policy_1, :claim_policy_1_description),
+            (:org, :claim_policy_2, :claim_policy_2_description),
+            (:org, :claim_policy_3, :claim_policy_3_description);
+
+-- Set up currency types
+INSERT INTO acq.currency_type(code, label)
+    VALUES (:currency_1, :currency_1_label),
+            (:currency_2, :currency_2_label);
+
+-- Set up provider
+INSERT INTO acq.provider(name, owner, currency_type, code)
+    VALUES(:provider_1_name, :provider_1_owner, :provider_1_currency_type, :provider_1_code);
+
+--Set up EDI account
+INSERT INTO acq.edi_account(label, host, username, password, path, owner, provider, in_dir)
+    VALUES(:edi_account_1_label, :edi_account_1_host, :edi_account_1_username,
+            :edi_account_1_password, :edi_account_1_path, :edi_account_1_owner,
+            :edi_account_1_provider, :edi_account_1_in_dir);
+
+--Set up Purchase Order
+INSERT INTO acq.purchase_order(owner, creator, editor, ordering_agency, provider, name)
+    VALUES(:purchase_order_1_owner, :purchase_order_1_creator, :purchase_order_1_editor,
+            :purchase_order_1_ordering_agency, :purchase_order_1_provider, :purchase_order_1_name);
+
+-- Insert the exchange ratios to known values for the test
+INSERT INTO acq.exchange_rate(from_currency, to_currency, ratio)
+    VALUES(:currency_1, :currency_2, :currency_1_to_currency_2_exchange_rate),
+            (:currency_2, :currency_1, :currency_2_to_currency_1_exchange_rate);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_1, :owner, :currency_1, :funding_source_1
+        ), (:funding_source_2, :owner, :currency_2, :funding_source_2
+        ), (:funding_source_3, :owner, :currency_2, :funding_source_3
+        );
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_1,
+            NOW() + '1 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_2,
+            NULL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           );
+
+INSERT INTO acq.fund (org, name, year, currency_type, code, active, rollover, propagate)
+    VALUES  (:org, :fund_1, :year, :currency_1, :fund_1, TRUE, TRUE, TRUE),
+            (:org, :fund_2, :year, :currency_2, :fund_2, TRUE, TRUE, TRUE),
+            (:org, :fund_3, :year, :currency_2, :fund_3, TRUE, TRUE, TRUE);
+
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, allocator, note)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_3, :owner, 'test'
+           );
+
+INSERT INTO acq.fund_debit(fund, origin_amount, origin_currency_type, amount, encumbrance, debit_type)
+        VALUES (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_1,
+                :currency_1,
+                :fund_1_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_2,
+                :currency_1,
+                :fund_1_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_3,
+                :currency_1,
+                :fund_1_debit_3,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_1,
+                :currency_2,
+                :fund_2_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_2,
+                :currency_2,
+                :fund_2_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_debit_1,
+                :currency_2,
+                :fund_3_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_encumbrance_1,
+                :currency_1,
+                :fund_1_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_1,
+                :currency_1,
+                :fund_2_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_2,
+                :currency_1,
+                :fund_2_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_1,
+                :currency_2,
+                :fund_3_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_2,
+                :currency_2,
+                :fund_3_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_3,
+                :currency_2,
+                :fund_3_encumbrance_3,
+                TRUE,
+                'purchase'
+        );
+
+--Insert lineitems and Insert lineitem details
+INSERT INTO acq.lineitem(creator, editor, selector, provider, purchase_order, marc, eg_bib_id, source_label,
+                        estimated_unit_price, claim_policy)
+    VALUES(:lineitem_1_creator, :lineitem_1_editor, :lineitem_1_selector, :lineitem_1_provider,
+            :lineitem_1_purchase_order, :lineitem_1_marc, :lineitem_1_eg_bib_id, :lineitem_1_source_label,
+            :lineitem_1_estimated_unit_price, :lineitem_1_claim_policy
+        ), (:lineitem_2_creator, :lineitem_2_editor, :lineitem_2_selector, :lineitem_2_provider,
+            :lineitem_2_purchase_order, :lineitem_2_marc, :lineitem_2_eg_bib_id, :lineitem_2_source_label,
+            :lineitem_2_estimated_unit_price, :lineitem_2_claim_policy
+        ), (:lineitem_3_creator, :lineitem_3_editor, :lineitem_3_selector, :lineitem_3_provider,
+            :lineitem_3_purchase_order, :lineitem_3_marc, :lineitem_3_eg_bib_id, :lineitem_3_source_label,
+            :lineitem_3_estimated_unit_price, :lineitem_3_claim_policy
+        ), (:lineitem_4_creator, :lineitem_4_editor, :lineitem_4_selector, :lineitem_4_provider,
+            :lineitem_4_purchase_order, :lineitem_4_marc, :lineitem_4_eg_bib_id, :lineitem_4_source_label,
+            :lineitem_4_estimated_unit_price, :lineitem_4_claim_policy
+        ), (:lineitem_5_creator, :lineitem_5_editor, :lineitem_5_selector, :lineitem_5_provider,
+            :lineitem_5_purchase_order, :lineitem_5_marc, :lineitem_5_eg_bib_id, :lineitem_5_source_label,
+            :lineitem_5_estimated_unit_price, :lineitem_5_claim_policy
+        ), (:lineitem_6_creator, :lineitem_6_editor, :lineitem_6_selector, :lineitem_6_provider,
+            :lineitem_6_purchase_order, :lineitem_6_marc, :lineitem_6_eg_bib_id, :lineitem_6_source_label,
+            :lineitem_6_estimated_unit_price, :lineitem_6_claim_policy
+        ), (:lineitem_7_creator, :lineitem_7_editor, :lineitem_7_selector, :lineitem_7_provider,
+            :lineitem_7_purchase_order, :lineitem_7_marc, :lineitem_7_eg_bib_id, :lineitem_7_source_label,
+            :lineitem_7_estimated_unit_price, :lineitem_7_claim_policy
+        ), (:lineitem_8_creator, :lineitem_8_editor, :lineitem_8_selector, :lineitem_8_provider,
+            :lineitem_8_purchase_order, :lineitem_8_marc, :lineitem_8_eg_bib_id, :lineitem_8_source_label,
+            :lineitem_8_estimated_unit_price, :lineitem_8_claim_policy
+        ), (:lineitem_9_creator, :lineitem_9_editor, :lineitem_9_selector, :lineitem_9_provider,
+            :lineitem_9_purchase_order, :lineitem_9_marc, :lineitem_9_eg_bib_id, :lineitem_9_source_label,
+            :lineitem_9_estimated_unit_price, :lineitem_9_claim_policy
+        ), (:lineitem_10_creator, :lineitem_10_editor, :lineitem_10_selector, :lineitem_10_provider,
+            :lineitem_10_purchase_order, :lineitem_10_marc, :lineitem_10_eg_bib_id, :lineitem_10_source_label,
+            :lineitem_10_estimated_unit_price, :lineitem_10_claim_policy
+        ), (:lineitem_11_creator, :lineitem_11_editor, :lineitem_11_selector, :lineitem_11_provider,
+            :lineitem_11_purchase_order, :lineitem_11_marc, :lineitem_11_eg_bib_id, :lineitem_11_source_label,
+            :lineitem_11_estimated_unit_price, :lineitem_11_claim_policy
+        ), (:lineitem_12_creator, :lineitem_12_editor, :lineitem_12_selector, :lineitem_12_provider,
+            :lineitem_12_purchase_order, :lineitem_12_marc, :lineitem_12_eg_bib_id, :lineitem_12_source_label,
+            :lineitem_12_estimated_unit_price, :lineitem_12_claim_policy
+        );
+
+INSERT INTO acq.lineitem_detail(lineitem, fund, fund_debit)
+    VALUES(:lineitem_detail_1_lineitem, :lineitem_detail_1_fund, :lineitem_detail_1_fund_debit
+        ), (:lineitem_detail_2_lineitem, :lineitem_detail_2_fund, :lineitem_detail_2_fund_debit
+        ), (:lineitem_detail_3_lineitem, :lineitem_detail_3_fund, :lineitem_detail_3_fund_debit
+        ), (:lineitem_detail_4_lineitem, :lineitem_detail_4_fund, :lineitem_detail_4_fund_debit
+        ), (:lineitem_detail_5_lineitem, :lineitem_detail_5_fund, :lineitem_detail_5_fund_debit
+        ), (:lineitem_detail_6_lineitem, :lineitem_detail_6_fund, :lineitem_detail_6_fund_debit
+        ), (:lineitem_detail_7_lineitem, :lineitem_detail_7_fund, :lineitem_detail_7_fund_debit
+        ), (:lineitem_detail_8_lineitem, :lineitem_detail_8_fund, :lineitem_detail_8_fund_debit
+        ), (:lineitem_detail_9_lineitem, :lineitem_detail_9_fund, :lineitem_detail_9_fund_debit
+        ), (:lineitem_detail_10_lineitem, :lineitem_detail_10_fund, :lineitem_detail_10_fund_debit
+        ), (:lineitem_detail_11_lineitem, :lineitem_detail_11_fund, :lineitem_detail_11_fund_debit
+        ), (:lineitem_detail_12_lineitem, :lineitem_detail_12_fund, :lineitem_detail_12_fund_debit
+        );
+
+-- Test if the funds are propagated
+
+SELECT acq.propagate_funds_by_org_unit(:year::INT, :owner, :org);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_1 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_1 || ''', ' || :year || '), (''' || :fund_1 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_1 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_2 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_2 || ''', ' || :year || '), (''' || :fund_2 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_2 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_3 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_3 || ''', ' || :year || '), (''' || :fund_3 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_3 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT acq.rollover_funds_by_org_unit(:year::INT, :owner, :org, TRUE);
+
+-- Test if funds were returned to their source
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+        )
+    ,
+        :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' ' || :year || ' has ' || :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+        )
+    ,
+        :fund_2_debit_1 + :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :year || ' has ' || :fund_2_debit_1 + :fund_2_debit_2 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+        )
+    ,
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' ' || :year || ' has ' || :fund_3_debit_1 || ' allocated to it.'
+);
+
+-- Test that no funds were transferred to next year
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_1 || ' has nothing allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_2 || ' has nothing allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_3 || ' has nothing allocated to it.'
+);
+
+-- Test if encumbered lineitem_details are moved to new funds
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_1_lineitem || ', ' || :lineitem_detail_1_fund_next_year || ')'
+    ,
+        'A single lineitem for Fund ' || :fund_1 || ' was moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_2_lineitem || ', ' || :lineitem_detail_2_fund_next_year || '),
+        (' || :lineitem_detail_3_lineitem || ', ' || :lineitem_detail_3_fund_next_year || ')'
+    ,
+        'Two lineitems for Fund ' || :fund_2 || ' were moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_4_lineitem || ', ' || :lineitem_detail_4_fund_next_year || '),
+        (' || :lineitem_detail_5_lineitem || ', ' || :lineitem_detail_5_fund_next_year || '),
+        (' || :lineitem_detail_6_lineitem || ', ' || :lineitem_detail_6_fund_next_year || ')'
+    ,
+        'Three lineitems for Fund ' || :fund_3 || ' were moved to ' || :next_year || '.'
+);
+
+-- Test to ensure encumbered debits were transferred
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :fund_1_encumbrance_1
+    ,
+        'Fund ' || :fund_1 || ' ' || :next_year ||  ' has ' || :fund_1_encumbrance_1 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        :fund_2_encumbrance_1 + :fund_2_encumbrance_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :next_year || ' has ' || :fund_2_encumbrance_1 + :fund_2_encumbrance_2 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3
+    ,
+        'Fund ' || :fund_3 || ' ' || :next_year || ' has ' || :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3 || ' encumbered funds allocated to it.'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only_and_distribution_formula.pg b/Open-ILS/src/sql/Pg/t/acq_rollover_encumbrance_only_and_distribution_formula.pg
new file mode 100644 (file)
index 0000000..9d1e8f5
--- /dev/null
@@ -0,0 +1,938 @@
+BEGIN;
+
+\set year 'date_part(''year'', NOW())'
+\set next_year 'date_part(''year'', NOW() + ''1 YEAR''::INTERVAL)'
+\set funding_source_1 '''FS1'''
+\set funding_source_2 '''FS2'''
+\set funding_source_3 '''FS3'''
+\set fund_1 '''F1'''
+\set fund_2 '''F2'''
+\set fund_3 '''F3'''
+\set org 1
+\set currency_1 '''NAC'''
+\set currency_1_label '''NAC Dollars'''
+\set currency_2 '''DSU'''
+\set currency_2_label '''DSU Dollars'''
+\set same_currency_exchange_rate 1.00
+\set currency_1_to_currency_2_exchange_rate .50
+\set currency_2_to_currency_1_exchange_rate 2.00
+\set owner 1
+
+\set funding_source_1_credit_1 3000.00
+\set funding_source_1_credit_2 2000.00
+\set funding_source_1_credit_3 100.00
+
+\set funding_source_2_credit_1 5.00
+\set funding_source_2_credit_2 450.00
+\set funding_source_2_credit_3 1500.00
+
+\set funding_source_3_credit_1 675.00
+\set funding_source_3_credit_2 10000.00
+\set funding_source_3_credit_3 934.00
+
+\set source_1_fund_1_allocation_1 500.00
+\set source_1_fund_1_allocation_2 200.00
+\set source_1_fund_1_allocation_3 0.00
+\set source_1_fund_2_allocation_1 155.00
+\set source_1_fund_2_allocation_2 300.00
+\set source_1_fund_2_allocation_3 400.00
+\set source_1_fund_3_allocation_1 45.00
+\set source_1_fund_3_allocation_2 200.00
+\set source_1_fund_3_allocation_3 1000.00
+
+\set source_2_fund_1_allocation_1 100.00
+\set source_2_fund_1_allocation_2 50.00
+\set source_2_fund_1_allocation_3 -400.00
+\set source_2_fund_2_allocation_1 35.00
+\set source_2_fund_2_allocation_2 200.00
+\set source_2_fund_2_allocation_3 200.00
+\set source_2_fund_3_allocation_1 400.00
+\set source_2_fund_3_allocation_2 -25.00
+\set source_2_fund_3_allocation_3 122.00
+
+\set source_3_fund_1_allocation_1 5000.00
+\set source_3_fund_1_allocation_2 135.00
+\set source_3_fund_1_allocation_3 300.00
+\set source_3_fund_2_allocation_1 2000.00
+\set source_3_fund_2_allocation_2 22.00
+\set source_3_fund_2_allocation_3 100.00
+\set source_3_fund_3_allocation_1 333.00
+\set source_3_fund_3_allocation_2 200.00
+\set source_3_fund_3_allocation_3 500.00
+
+\set fund_1_debit_1 200.00
+\set fund_1_debit_2 150.00
+\set fund_1_debit_3 25.98
+
+\set fund_2_debit_1 555.55
+\set fund_2_debit_2 0.00
+
+\set fund_3_debit_1 1000.22
+
+\set fund_1_encumbrance_1 2234.54
+
+\set fund_2_encumbrance_1 123.45
+\set fund_2_encumbrance_2 111.11
+
+\set fund_3_encumbrance_1 435.43
+\set fund_3_encumbrance_2 10.14
+\set fund_3_encumbrance_3 0.00
+
+\set claim_policy_1 '''4 Week Phone'''
+\set claim_policy_1_description '''Phone the provider after 4 Weeks'''
+\set claim_policy_2 '''4 Week Email'''
+\set claim_policy_2_description '''Email the provider after 4 Weeks'''
+\set claim_policy_3 '''4 Week Fax'''
+\set claim_policy_3_description '''Fax the provider after 4 Weeks'''
+
+\set provider_1_name '''Test Provider'''
+\set provider_1_owner :owner
+\set provider_1_currency_type :currency_1
+\set provider_1_code '''TESTP'''
+
+\set edi_account_1_label '''Test Account'''
+\set edi_account_1_host  '''ftp://ftp.test.com'''
+\set edi_account_1_username '''username'''
+\set edi_account_1_password '''password'''
+\set edi_account_1_path '''/path/.*'''
+\set edi_account_1_owner :owner
+\set edi_account_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set edi_account_1_in_dir '''/in'''
+
+\set purchase_order_1_owner :owner
+\set purchase_order_1_creator :owner
+\set purchase_order_1_editor :owner
+\set purchase_order_1_ordering_agency :org
+\set purchase_order_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set purchase_order_1_name '''PO1'''
+
+\set lineitem_1_creator :owner
+\set lineitem_1_editor :owner
+\set lineitem_1_selector :owner
+\set lineitem_1_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_1_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_1_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_1_eg_bib_id 1
+\set lineitem_1_source_label '''native-evergreen-catalog'''
+\set lineitem_1_estimated_unit_price :fund_1_encumbrance_1
+\set lineitem_1_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_1_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_1_eg_bib_id)'
+\set lineitem_detail_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_1_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_1_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_encumbrance_1)'
+
+\set lineitem_2_creator :owner
+\set lineitem_2_editor :owner
+\set lineitem_2_selector :owner
+\set lineitem_2_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_2_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_2_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_2_eg_bib_id 2
+\set lineitem_2_source_label '''native-evergreen-catalog'''
+\set lineitem_2_estimated_unit_price :fund_2_encumbrance_1
+\set lineitem_2_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_2_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_2_eg_bib_id)'
+\set lineitem_detail_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_2_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_2_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_1)'
+
+\set lineitem_3_creator :owner
+\set lineitem_3_editor :owner
+\set lineitem_3_selector :owner
+\set lineitem_3_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_3_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_3_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_3_eg_bib_id 3
+\set lineitem_3_source_label '''native-evergreen-catalog'''
+\set lineitem_3_estimated_unit_price :fund_2_encumbrance_2
+\set lineitem_3_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_3_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_3_eg_bib_id)'
+\set lineitem_detail_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_3_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_3_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_encumbrance_2)'
+
+\set lineitem_4_creator :owner
+\set lineitem_4_editor :owner
+\set lineitem_4_selector :owner
+\set lineitem_4_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_4_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_4_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_4_eg_bib_id 4
+\set lineitem_4_source_label '''native-evergreen-catalog'''
+\set lineitem_4_estimated_unit_price :fund_3_encumbrance_1
+\set lineitem_4_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_4_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_4_eg_bib_id)'
+\set lineitem_detail_4_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_4_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_4_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_1)'
+
+\set lineitem_5_creator :owner
+\set lineitem_5_editor :owner
+\set lineitem_5_selector :owner
+\set lineitem_5_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_5_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_5_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_5_eg_bib_id 5
+\set lineitem_5_source_label '''native-evergreen-catalog'''
+\set lineitem_5_estimated_unit_price :fund_3_encumbrance_2
+\set lineitem_5_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_5_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_5_eg_bib_id)'
+\set lineitem_detail_5_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_5_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_5_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_2)'
+
+\set lineitem_6_creator :owner
+\set lineitem_6_editor :owner
+\set lineitem_6_selector :owner
+\set lineitem_6_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_6_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_6_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_6_eg_bib_id 6
+\set lineitem_6_source_label '''native-evergreen-catalog'''
+\set lineitem_6_estimated_unit_price :fund_3_encumbrance_3
+\set lineitem_6_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_6_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_6_eg_bib_id)'
+\set lineitem_detail_6_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_6_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_6_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_encumbrance_3)'
+
+\set lineitem_7_creator :owner
+\set lineitem_7_editor :owner
+\set lineitem_7_selector :owner
+\set lineitem_7_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_7_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_7_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_7_eg_bib_id 7
+\set lineitem_7_source_label '''native-evergreen-catalog'''
+\set lineitem_7_estimated_unit_price :fund_1_debit_1
+\set lineitem_7_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_3)'
+\set lineitem_detail_7_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_7_eg_bib_id)'
+\set lineitem_detail_7_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_7_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_7_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_8_creator :owner
+\set lineitem_8_editor :owner
+\set lineitem_8_selector :owner
+\set lineitem_8_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_8_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_8_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_8_eg_bib_id 8
+\set lineitem_8_source_label '''native-evergreen-catalog'''
+\set lineitem_8_estimated_unit_price :fund_1_debit_2
+\set lineitem_8_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_2)'
+\set lineitem_detail_8_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_8_eg_bib_id)'
+\set lineitem_detail_8_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_8_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_8_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_1)'
+
+\set lineitem_9_creator :owner
+\set lineitem_9_editor :owner
+\set lineitem_9_selector :owner
+\set lineitem_9_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_9_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_9_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_9_eg_bib_id 9
+\set lineitem_9_source_label '''native-evergreen-catalog'''
+\set lineitem_9_estimated_unit_price :fund_1_debit_3
+\set lineitem_9_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_9_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_9_eg_bib_id)'
+\set lineitem_detail_9_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+\set lineitem_detail_9_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)'
+\set lineitem_detail_9_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year) AND amount = :fund_1_debit_3)'
+
+\set lineitem_10_creator :owner
+\set lineitem_10_editor :owner
+\set lineitem_10_selector :owner
+\set lineitem_10_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_10_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_10_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_10_eg_bib_id 10
+\set lineitem_10_source_label '''native-evergreen-catalog'''
+\set lineitem_10_estimated_unit_price :fund_2_debit_1
+\set lineitem_10_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_10_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_10_eg_bib_id)'
+\set lineitem_detail_10_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_10_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_10_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_1)'
+
+\set lineitem_11_creator :owner
+\set lineitem_11_editor :owner
+\set lineitem_11_selector :owner
+\set lineitem_11_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_11_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_11_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_11_eg_bib_id 11
+\set lineitem_11_source_label '''native-evergreen-catalog'''
+\set lineitem_11_estimated_unit_price :fund_2_debit_2
+\set lineitem_11_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_11_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_11_eg_bib_id)'
+\set lineitem_detail_11_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+\set lineitem_detail_11_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)'
+\set lineitem_detail_11_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year) AND amount = :fund_2_debit_2)'
+
+\set lineitem_12_creator :owner
+\set lineitem_12_editor :owner
+\set lineitem_12_selector :owner
+\set lineitem_12_provider '(SELECT id FROM acq.provider WHERE name = :provider_1_name)'
+\set lineitem_12_purchase_order '(SELECT id FROM acq.purchase_order WHERE name = :purchase_order_1_name)'
+\set lineitem_12_marc '''<bogus_marc>bogus</bogus_marc>'''
+\set lineitem_12_eg_bib_id 12
+\set lineitem_12_source_label '''native-evergreen-catalog'''
+\set lineitem_12_estimated_unit_price :fund_3_debit_1
+\set lineitem_12_claim_policy '(SELECT id FROM acq.claim_policy WHERE name = :claim_policy_1)'
+\set lineitem_detail_12_lineitem '(SELECT id FROM acq.lineitem WHERE eg_bib_id = :lineitem_12_eg_bib_id)'
+\set lineitem_detail_12_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+\set lineitem_detail_12_fund_next_year '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)'
+\set lineitem_detail_12_fund_debit '(SELECT id FROM acq.fund_debit WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year) AND amount = :fund_3_debit_1)'
+
+\set distribution_formula_1_owner :owner
+\set distribution_formula_1_name '''Formula 1'''
+\set distribution_formula_2_owner :owner
+\set distribution_formula_2_name '''Formula 2'''
+\set distribution_formula_3_owner :owner
+\set distribution_formula_3_name '''Formula 3'''
+
+\set distribution_formula_entry_1_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_1_name)'
+\set distribution_formula_entry_1_position 1
+\set distribution_formula_entry_1_item_count 1
+\set distribution_formula_entry_1_owning_lib :org
+\set distribution_formula_entry_1_fund '(SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)'
+
+\set distribution_formula_entry_2_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_2_name)'
+\set distribution_formula_entry_2_position 2
+\set distribution_formula_entry_2_item_count 2
+\set distribution_formula_entry_2_owning_lib :org
+\set distribution_formula_entry_2_fund '(SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)'
+
+\set distribution_formula_entry_3_formula '(SELECT id FROM acq.distribution_formula WHERE name = :distribution_formula_3_name)'
+\set distribution_formula_entry_3_position 3
+\set distribution_formula_entry_3_item_count 3
+\set distribution_formula_entry_3_owning_lib :org
+\set distribution_formula_entry_3_fund '(SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)'
+
+SELECT plan(18);
+
+-- Set up the org to rollover all funds
+
+DELETE FROM actor.org_unit_setting
+WHERE org_unit = :org AND name = 'acq.fund.allow_rollover_without_money';
+
+INSERT INTO actor.org_unit_setting(org_unit, name, value)
+    VALUES(:org, 'acq.fund.allow_rollover_without_money', true);
+
+-- Set up formula to rollover
+
+DELETE FROM actor.org_unit_setting
+WHERE org_unit = :org AND name = 'acq.fund.rollover_distrib_forms';
+
+INSERT INTO actor.org_unit_setting(org_unit, name, value)
+    VALUES(:org, 'acq.fund.rollover_distrib_forms', true);
+
+-- Set up the claim poilicies
+
+INSERT INTO acq.claim_policy(org_unit, name, description)
+    VALUES (:org, :claim_policy_1, :claim_policy_1_description),
+            (:org, :claim_policy_2, :claim_policy_2_description),
+            (:org, :claim_policy_3, :claim_policy_3_description);
+
+-- Set up currency types
+INSERT INTO acq.currency_type(code, label)
+    VALUES (:currency_1, :currency_1_label),
+            (:currency_2, :currency_2_label);
+
+-- Set up provider
+INSERT INTO acq.provider(name, owner, currency_type, code)
+    VALUES(:provider_1_name, :provider_1_owner, :provider_1_currency_type, :provider_1_code);
+
+--Set up EDI account
+INSERT INTO acq.edi_account(label, host, username, password, path, owner, provider, in_dir)
+    VALUES(:edi_account_1_label, :edi_account_1_host, :edi_account_1_username,
+            :edi_account_1_password, :edi_account_1_path, :edi_account_1_owner,
+            :edi_account_1_provider, :edi_account_1_in_dir);
+
+--Set up Purchase Order
+INSERT INTO acq.purchase_order(owner, creator, editor, ordering_agency, provider, name)
+    VALUES(:purchase_order_1_owner, :purchase_order_1_creator, :purchase_order_1_editor,
+            :purchase_order_1_ordering_agency, :purchase_order_1_provider, :purchase_order_1_name);
+
+-- Insert the exchange ratios to known values for the test
+INSERT INTO acq.exchange_rate(from_currency, to_currency, ratio)
+    VALUES(:currency_1, :currency_2, :currency_1_to_currency_2_exchange_rate),
+            (:currency_2, :currency_1, :currency_2_to_currency_1_exchange_rate);
+
+INSERT INTO acq.funding_source (name, owner, currency_type, code)
+    VALUES (:funding_source_1, :owner, :currency_1, :funding_source_1
+        ), (:funding_source_2, :owner, :currency_2, :funding_source_2
+        ), (:funding_source_3, :owner, :currency_2, :funding_source_3
+        );
+
+INSERT INTO acq.funding_source_credit (funding_source, amount, deadline_date)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_1,
+            NOW() + '1 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_2,
+            NULL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            :funding_source_1_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            :funding_source_2_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_1,
+            NOW() + '3 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_2,
+            NOW() + '2 DAY'::INTERVAL
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            :funding_source_3_credit_3,
+            NOW() + '2 DAY'::INTERVAL
+           );
+
+INSERT INTO acq.fund (org, name, year, currency_type, code, active, rollover, propagate)
+    VALUES  (:org, :fund_1, :year, :currency_1, :fund_1, TRUE, TRUE, TRUE),
+            (:org, :fund_2, :year, :currency_2, :fund_2, TRUE, TRUE, TRUE),
+            (:org, :fund_3, :year, :currency_2, :fund_3, TRUE, TRUE, TRUE);
+
+INSERT INTO acq.fund_allocation
+    (funding_source, fund, amount, allocator, note)
+    VALUES (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_1_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_1_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_1),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_1_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_2_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_2_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_2),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_2_fund_3_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_1),
+            :source_3_fund_1_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_2),
+            :source_3_fund_2_allocation_3, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_1, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_2, :owner, 'test'
+           ), (
+            (SELECT id FROM acq.funding_source WHERE name = :funding_source_3),
+            (SELECT id FROM acq.fund WHERE name = :fund_3),
+            :source_3_fund_3_allocation_3, :owner, 'test'
+           );
+
+INSERT INTO acq.fund_debit(fund, origin_amount, origin_currency_type, amount, encumbrance, debit_type)
+        VALUES (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_1,
+                :currency_1,
+                :fund_1_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_2,
+                :currency_1,
+                :fund_1_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_debit_3,
+                :currency_1,
+                :fund_1_debit_3,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_1,
+                :currency_2,
+                :fund_2_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_debit_2,
+                :currency_2,
+                :fund_2_debit_2,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_debit_1,
+                :currency_2,
+                :fund_3_debit_1,
+                FALSE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_1 and year = :year),
+                :fund_1_encumbrance_1,
+                :currency_1,
+                :fund_1_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_1,
+                :currency_1,
+                :fund_2_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_2 and year = :year),
+                :fund_2_encumbrance_2,
+                :currency_1,
+                :fund_2_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_1,
+                :currency_2,
+                :fund_3_encumbrance_1,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_2,
+                :currency_2,
+                :fund_3_encumbrance_2,
+                TRUE,
+                'purchase'
+        ), (
+                (SELECT id FROM acq.fund WHERE name = :fund_3 and year = :year),
+                :fund_3_encumbrance_3,
+                :currency_2,
+                :fund_3_encumbrance_3,
+                TRUE,
+                'purchase'
+        );
+
+--Insert lineitems and Insert lineitem details
+INSERT INTO acq.lineitem(creator, editor, selector, provider, purchase_order, marc, eg_bib_id, source_label,
+                        estimated_unit_price, claim_policy)
+    VALUES(:lineitem_1_creator, :lineitem_1_editor, :lineitem_1_selector, :lineitem_1_provider,
+            :lineitem_1_purchase_order, :lineitem_1_marc, :lineitem_1_eg_bib_id, :lineitem_1_source_label,
+            :lineitem_1_estimated_unit_price, :lineitem_1_claim_policy
+        ), (:lineitem_2_creator, :lineitem_2_editor, :lineitem_2_selector, :lineitem_2_provider,
+            :lineitem_2_purchase_order, :lineitem_2_marc, :lineitem_2_eg_bib_id, :lineitem_2_source_label,
+            :lineitem_2_estimated_unit_price, :lineitem_2_claim_policy
+        ), (:lineitem_3_creator, :lineitem_3_editor, :lineitem_3_selector, :lineitem_3_provider,
+            :lineitem_3_purchase_order, :lineitem_3_marc, :lineitem_3_eg_bib_id, :lineitem_3_source_label,
+            :lineitem_3_estimated_unit_price, :lineitem_3_claim_policy
+        ), (:lineitem_4_creator, :lineitem_4_editor, :lineitem_4_selector, :lineitem_4_provider,
+            :lineitem_4_purchase_order, :lineitem_4_marc, :lineitem_4_eg_bib_id, :lineitem_4_source_label,
+            :lineitem_4_estimated_unit_price, :lineitem_4_claim_policy
+        ), (:lineitem_5_creator, :lineitem_5_editor, :lineitem_5_selector, :lineitem_5_provider,
+            :lineitem_5_purchase_order, :lineitem_5_marc, :lineitem_5_eg_bib_id, :lineitem_5_source_label,
+            :lineitem_5_estimated_unit_price, :lineitem_5_claim_policy
+        ), (:lineitem_6_creator, :lineitem_6_editor, :lineitem_6_selector, :lineitem_6_provider,
+            :lineitem_6_purchase_order, :lineitem_6_marc, :lineitem_6_eg_bib_id, :lineitem_6_source_label,
+            :lineitem_6_estimated_unit_price, :lineitem_6_claim_policy
+        ), (:lineitem_7_creator, :lineitem_7_editor, :lineitem_7_selector, :lineitem_7_provider,
+            :lineitem_7_purchase_order, :lineitem_7_marc, :lineitem_7_eg_bib_id, :lineitem_7_source_label,
+            :lineitem_7_estimated_unit_price, :lineitem_7_claim_policy
+        ), (:lineitem_8_creator, :lineitem_8_editor, :lineitem_8_selector, :lineitem_8_provider,
+            :lineitem_8_purchase_order, :lineitem_8_marc, :lineitem_8_eg_bib_id, :lineitem_8_source_label,
+            :lineitem_8_estimated_unit_price, :lineitem_8_claim_policy
+        ), (:lineitem_9_creator, :lineitem_9_editor, :lineitem_9_selector, :lineitem_9_provider,
+            :lineitem_9_purchase_order, :lineitem_9_marc, :lineitem_9_eg_bib_id, :lineitem_9_source_label,
+            :lineitem_9_estimated_unit_price, :lineitem_9_claim_policy
+        ), (:lineitem_10_creator, :lineitem_10_editor, :lineitem_10_selector, :lineitem_10_provider,
+            :lineitem_10_purchase_order, :lineitem_10_marc, :lineitem_10_eg_bib_id, :lineitem_10_source_label,
+            :lineitem_10_estimated_unit_price, :lineitem_10_claim_policy
+        ), (:lineitem_11_creator, :lineitem_11_editor, :lineitem_11_selector, :lineitem_11_provider,
+            :lineitem_11_purchase_order, :lineitem_11_marc, :lineitem_11_eg_bib_id, :lineitem_11_source_label,
+            :lineitem_11_estimated_unit_price, :lineitem_11_claim_policy
+        ), (:lineitem_12_creator, :lineitem_12_editor, :lineitem_12_selector, :lineitem_12_provider,
+            :lineitem_12_purchase_order, :lineitem_12_marc, :lineitem_12_eg_bib_id, :lineitem_12_source_label,
+            :lineitem_12_estimated_unit_price, :lineitem_12_claim_policy
+        );
+
+INSERT INTO acq.lineitem_detail(lineitem, fund, fund_debit)
+    VALUES(:lineitem_detail_1_lineitem, :lineitem_detail_1_fund, :lineitem_detail_1_fund_debit
+        ), (:lineitem_detail_2_lineitem, :lineitem_detail_2_fund, :lineitem_detail_2_fund_debit
+        ), (:lineitem_detail_3_lineitem, :lineitem_detail_3_fund, :lineitem_detail_3_fund_debit
+        ), (:lineitem_detail_4_lineitem, :lineitem_detail_4_fund, :lineitem_detail_4_fund_debit
+        ), (:lineitem_detail_5_lineitem, :lineitem_detail_5_fund, :lineitem_detail_5_fund_debit
+        ), (:lineitem_detail_6_lineitem, :lineitem_detail_6_fund, :lineitem_detail_6_fund_debit
+        ), (:lineitem_detail_7_lineitem, :lineitem_detail_7_fund, :lineitem_detail_7_fund_debit
+        ), (:lineitem_detail_8_lineitem, :lineitem_detail_8_fund, :lineitem_detail_8_fund_debit
+        ), (:lineitem_detail_9_lineitem, :lineitem_detail_9_fund, :lineitem_detail_9_fund_debit
+        ), (:lineitem_detail_10_lineitem, :lineitem_detail_10_fund, :lineitem_detail_10_fund_debit
+        ), (:lineitem_detail_11_lineitem, :lineitem_detail_11_fund, :lineitem_detail_11_fund_debit
+        ), (:lineitem_detail_12_lineitem, :lineitem_detail_12_fund, :lineitem_detail_12_fund_debit
+        );
+
+-- Insert distribution formulae
+INSERT INTO acq.distribution_formula(owner, name)
+    VALUES(:distribution_formula_1_owner, :distribution_formula_1_name
+        ), (:distribution_formula_2_owner, :distribution_formula_2_name
+        ), (:distribution_formula_3_owner, :distribution_formula_3_name
+        );
+
+INSERT INTO acq.distribution_formula_entry(formula, position, item_count, owning_lib, fund)
+    VALUES(:distribution_formula_entry_1_formula, :distribution_formula_entry_1_position,
+            :distribution_formula_entry_1_item_count, :distribution_formula_entry_1_owning_lib,
+            :distribution_formula_entry_1_fund
+        ), (:distribution_formula_entry_2_formula, :distribution_formula_entry_2_position,
+            :distribution_formula_entry_2_item_count, :distribution_formula_entry_2_owning_lib,
+            :distribution_formula_entry_2_fund
+        ), (:distribution_formula_entry_3_formula, :distribution_formula_entry_3_position,
+            :distribution_formula_entry_3_item_count, :distribution_formula_entry_3_owning_lib,
+            :distribution_formula_entry_3_fund
+        );
+
+-- Test if the funds are propagated
+
+SELECT acq.propagate_funds_by_org_unit(:year::INT, :owner, :org);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_1 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_1 || ''', ' || :year || '), (''' || :fund_1 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_1 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_2 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_2 || ''', ' || :year || '), (''' || :fund_2 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_2 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT results_eq(
+        'SELECT name, year
+        FROM (
+            SELECT name, year
+            FROM acq.fund
+            WHERE name = ''' || :fund_3 || '''
+            ORDER BY year ASC
+        ) fund_years'
+    ,
+        'VALUES (''' || :fund_3 || ''', ' || :year || '), (''' || :fund_3 || ''', ' || :next_year || ')'
+    ,
+        'There are two ' || :fund_3 || ' funds belonging to ' ||
+        (SELECT :year::TEXT)
+        || ' and ' ||
+        (SELECT :next_year::TEXT)
+        || ' respectively'
+);
+
+SELECT acq.rollover_funds_by_org_unit(:year::INT, :owner, :org, TRUE);
+
+-- Test if funds were returned to their source
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :year)
+        )
+    ,
+        :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3
+    ,
+        'Fund ' || :fund_1 || ' ' || :year || ' has ' || :fund_1_debit_1 + :fund_1_debit_2 + :fund_1_debit_3 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :year)
+        )
+    ,
+        :fund_2_debit_1 + :fund_2_debit_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :year || ' has ' || :fund_2_debit_1 + :fund_2_debit_2 || ' allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :year)
+        )
+    ,
+        :fund_3_debit_1
+    ,
+        'Fund ' || :fund_3 || ' ' || :year || ' has ' || :fund_3_debit_1 || ' allocated to it.'
+);
+
+-- Test that no funds were transferred to next year
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_1 || ' has nothing allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_2 || ' has nothing allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(fund_amount), 2)
+            FROM acq.fund_allocation
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        NULL
+    ,
+        'Rolled over fund ' || :fund_3 || ' has nothing allocated to it.'
+);
+
+-- Test if encumbered lineitem_details are moved to new funds
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_1_lineitem || ', ' || :lineitem_detail_1_fund_next_year || ')'
+    ,
+        'A single lineitem for Fund ' || :fund_1 || ' was moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_2_lineitem || ', ' || :lineitem_detail_2_fund_next_year || '),
+        (' || :lineitem_detail_3_lineitem || ', ' || :lineitem_detail_3_fund_next_year || ')'
+    ,
+        'Two lineitems for Fund ' || :fund_2 || ' were moved to ' || :next_year || '.'
+);
+
+SELECT results_eq(
+        'SELECT lineitem, fund
+        FROM acq.lineitem_detail
+        WHERE fund = ' || '(SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || '''
+        AND year = ' || :next_year || ')
+        ORDER BY lineitem ASC'
+    ,
+        'VALUES(' || :lineitem_detail_4_lineitem || ', ' || :lineitem_detail_4_fund_next_year || '),
+        (' || :lineitem_detail_5_lineitem || ', ' || :lineitem_detail_5_fund_next_year || '),
+        (' || :lineitem_detail_6_lineitem || ', ' || :lineitem_detail_6_fund_next_year || ')'
+    ,
+        'Three lineitems for Fund ' || :fund_3 || ' were moved to ' || :next_year || '.'
+);
+
+-- Test to ensure encumbered debits were transferred
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_1 AND year = :next_year)
+        )
+    ,
+        :fund_1_encumbrance_1
+    ,
+        'Fund ' || :fund_1 || ' ' || :next_year ||  ' has ' || :fund_1_encumbrance_1 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_2 AND year = :next_year)
+        )
+    ,
+        :fund_2_encumbrance_1 + :fund_2_encumbrance_2
+    ,
+        'Fund ' || :fund_2 || ' ' || :next_year || ' has ' || :fund_2_encumbrance_1 + :fund_2_encumbrance_2 || ' encumbered funds allocated to it.'
+);
+
+SELECT is(
+        (
+            SELECT TRUNC(SUM(amount), 2)
+            FROM acq.fund_debit
+            WHERE fund = (SELECT id FROM acq.fund WHERE name = :fund_3 AND year = :next_year)
+        )
+    ,
+        :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3
+    ,
+        'Fund ' || :fund_3 || ' ' || :next_year || ' has ' || :fund_3_encumbrance_1 + :fund_3_encumbrance_2 + :fund_3_encumbrance_3 || ' encumbered funds allocated to it.'
+);
+
+-- Ensure the distribution forumlae were rolled over
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_1_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_1 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_1 || ' are rolled over.'
+);
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_2_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_2 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_2 || ' are rolled over.'
+);
+
+SELECT results_eq(
+        'SELECT formula, fund
+        FROM acq.distribution_formula_entry
+        WHERE fund = (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 || ''' AND year = ' || :next_year || ')'
+    ,
+        'VALUES(' || :distribution_formula_entry_3_formula || ', (SELECT id FROM acq.fund WHERE name = ''' || :fund_3 ||
+            ''' AND year = ' || :next_year || '))'
+    ,
+        'Distribution Formula for fund ' || :fund_3 || ' are rolled over.'
+);
+
+ROLLBACK;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.add_fund_amount_and_conversion_ratio_to_fund_allocation.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.add_fund_amount_and_conversion_ratio_to_fund_allocation.sql
new file mode 100644 (file)
index 0000000..256825b
--- /dev/null
@@ -0,0 +1,11 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+ALTER TABLE acq.fund_allocation
+ADD fund_amount NUMERIC;
+
+ALTER TABLE acq.fund_allocation
+ADD conversion_ratio NUMERIC;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.empty_fund_totals_now_0.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.empty_fund_totals_now_0.sql
new file mode 100644 (file)
index 0000000..62a8127
--- /dev/null
@@ -0,0 +1,34 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+-- We need to use CASCADE to remove acq.fund_combined_balance and acq.fund_spent_balance
+
+DROP VIEW acq.fund_allocation_total CASCADE;
+
+-- Recreate the acq.fund_allocation_total, so that funds without allocations are given a total
+-- of 0.00
+
+CREATE OR REPLACE VIEW acq.fund_allocation_total AS
+       SELECT  af.id AS fund,
+               COALESCE(sum(afa.amount * acq.exchange_ratio(COALESCE(afs.currency_type, af.currency_type), COALESCE(af.currency_type, afs.currency_type)))::NUMERIC(100,2), 0.00) AS amount
+       FROM acq.fund_allocation AS afa
+               RIGHT JOIN acq.fund AS af ON afa.fund = af.id
+               LEFT JOIN acq.funding_source AS afs ON afa.funding_source = afs.id
+       GROUP BY af.id;
+
+-- Recreate the VIEWs that are dependant upon acq.fund_allocation_total
+
+CREATE OR REPLACE VIEW acq.fund_combined_balance AS
+       SELECT  c.fund,
+               c.amount - COALESCE(d.amount, 0.0) AS amount
+       FROM acq.fund_allocation_total c
+       LEFT JOIN acq.fund_debit_total d USING (fund);
+
+CREATE OR REPLACE VIEW acq.fund_spent_balance AS
+       SELECT  c.fund,
+               c.amount - COALESCE(d.amount,0.0) AS amount
+       FROM  acq.fund_allocation_total c
+               LEFT JOIN acq.fund_spent_total d USING (fund);
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.insert_fund_allocation_fund_amount.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.insert_fund_allocation_fund_amount.sql
new file mode 100644 (file)
index 0000000..9feb489
--- /dev/null
@@ -0,0 +1,44 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+CREATE OR REPLACE FUNCTION acq.insert_fund_allocation_fund_amount() RETURNS TRIGGER AS $$
+/* -------------------------------------------------------------------------------
+This TRIGGER, UPDATES acq.fund_allocation.fund_amount and acq.fund_allocation.conversion_ratio
+when a new or new values are inserted into the acq.fund_allocation_table.
+
+First, the conversion_ratio is calculated.  If the amount being inserted is less than 0,
+then we are transferring funds back to the funding source, so we need to convert from the
+currency of the fund to the currency of the funding_source.  Conversely, if the amount being
+transferred is greater than or equal to 0, then we need to convert from the currency of the
+funding_source to the currency of the fund.
+------------------------------------------------------------------------------- */
+DECLARE calculated_conversion_ratio                                NUMERIC;
+
+BEGIN
+       SELECT acq.exchange_ratio((SELECT currency_type FROM acq.funding_source WHERE id = NEW.funding_source),
+               (SELECT currency_type FROM acq.fund WHERE id = NEW.fund)
+       )
+       INTO calculated_conversion_ratio;
+
+       UPDATE acq.fund_allocation
+       SET fund_amount = trunc((NEW.amount * calculated_conversion_ratio), 2)
+       WHERE fund = NEW.fund AND id = NEW.id;
+
+       UPDATE acq.fund_allocation
+       SET conversion_ratio = calculated_conversion_ratio
+       WHERE fund = NEW.fund AND id = NEW.id;
+
+       RETURN NEW;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+DROP TRIGGER acq_insert_fund_allocation_fund_amount ON acq.fund_allocation;
+
+CREATE TRIGGER acq_insert_fund_allocation_fund_amount
+       AFTER INSERT ON acq.fund_allocation
+       FOR EACH ROW
+       EXECUTE PROCEDURE acq.insert_fund_allocation_fund_amount();
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.return_funds_to_source.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.return_funds_to_source.sql
new file mode 100644 (file)
index 0000000..3519e55
--- /dev/null
@@ -0,0 +1,326 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+CREATE OR REPLACE FUNCTION acq.return_funds_to_source(
+       returning_fund_id   IN INT,
+       amount_to_return IN NUMERIC,                    -- in currency of returning
+                                                       -- fund
+       user_id    IN INT,
+       xfer_note  IN TEXT,                             -- to be recorded in
+                                                       -- acq.fund_transfer
+       return_funding_source IN INT DEFAULT NULL       -- if user wants to specify a
+                                                       -- funding source (see notes)
+) RETURNS VOID AS $$
+/* -------------------------------------------------------------------------------
+
+Function to return funds from a fund to a funding source or funding sources.
+
+A return is represented as a single entry in acq.fund_allocation, with a
+negative amount for the fund being debited.
+In some cases there may be more than one such pair of entries
+In order to pull the money from different funding sources, or more specifically
+from different funding source credits.  For each return there is also an
+entry in acq.fund_transfer.
+
+Since funding_source is a non-nullable column in acq.fund_allocation, we must
+choose a funding source for the returning money to return to.  This choice
+must meet two constraints, so far as possible:
+
+1. The amount returned to a given funding source must not exceed the
+amount allocated to the returning fund by the funding source.  To that end we
+compare the amount being returned to the amount allocated.
+
+2. We shouldn't return money that has already been spent or encumbered, as
+defined by the funding attribution process.  We attribute expenses to the
+oldest funding source credits first.  In order to avoid returning that
+attributed money, we reverse the priority, returning from the newest funding
+source credits first.  There can be no guarantee that this approach will
+avoid over returning from a fund, but no other approach can do any better.
+
+In this context the age of a funding source credit is defined by the
+deadline_date for credits with deadline_dates, and by the effective_date for
+credits without deadline_dates, with the proviso that credits with deadline_dates
+are all considered "older" than those without.
+
+----------
+
+In the signature for this function, there is one last parameter commented out,
+named "return_funding_soruce".  Correspondingly, the WHERE clause for the query
+driving the main loop has an OR clause commented out, which references the
+funding_source_in parameter.
+
+If these lines are uncommented, this function will allow the user optionally to
+restrict a fund return to a specified funding source.  If the source
+parameter is left NULL, then there will be no such restriction.
+
+Need to lock the acq.currency_rate table while this function is being
+executed.  Otherwise, the amount in new_amount may be less than an amount deducted
+if the currencies in the table fluctuate to a large degree while this is running.
+
+------------------------------------------------------------------------------- */
+DECLARE
+
+       returning_fund_currency                                         TEXT;
+
+       -- kept in the currency of return fund
+       funds_remaining_to_be_returned                                  NUMERIC;
+
+       -- kept in currency of funding source
+       current_amount_to_return                                        NUMERIC;
+
+       -- kept in currency of fund
+       fund_amount_to_return                                           NUMERIC;
+
+       -- in currency of funding source
+       funding_source_credit                                           NUMERIC;
+
+       -- kept in currency of returning fund
+       amount_allocated_by_funding_source                              NUMERIC;
+
+       -- kept in currency of returning fund
+       amount_currently_allocated_to_returning_fund                    NUMERIC;
+
+       -- a JOIN of acq.funding_source and acq.ordered_funding_source_credit
+       source                                                          RECORD;
+
+BEGIN
+       --
+       -- We need to lock this table because the exchange rates could be modifed
+       -- while we are doing a transfer, which would create unpredictable results
+       --
+       LOCK acq.exchange_rate
+       IN EXCLUSIVE MODE;
+       --
+       -- Sanity checks
+       --
+       IF returning_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: returning fund id is NULL';
+       END IF;
+       --
+       IF amount_to_return IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_soruce: amount to return is NULL';
+       END IF;
+       --
+       IF user_id IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: user id is NULL';
+       END IF;
+       --
+       IF amount_to_return = 0 THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source amount to return is 0';
+       END IF;
+
+       -- Ensure there is enough money in the fund to cover the amount to return
+       -- We are returning the balance plus any funds currently encumbered
+       -- So we use the Spent Balance because it includes the Total Encumbered
+       -- as well as the the Total Spent
+       SELECT amount
+       INTO amount_currently_allocated_to_returning_fund
+       FROM acq.fund_spent_balance
+       WHERE fund = returning_fund_id;
+
+       IF amount_to_return > amount_currently_allocated_to_returning_fund THEN
+           RAISE EXCEPTION 'Cannot return more money than is currently allocted to the fund. fund id: % has % allocated to it, and the acq.return_funds_to_source is trying to debit it for %', returning_fund_id, amount_currently_allocated_to_returning_fund, amount_to_return;
+       END IF;
+
+       --
+       -- Initialize the amounts to be returned, each denominated
+       -- in the currency of its respective fund.  They will be
+       -- reduced on each iteration of the loop.
+       --
+       funds_remaining_to_be_returned := amount_to_return;
+       --
+       -- Get the currency types of the old and new funds.
+       --
+       SELECT
+               currency_type
+       INTO
+               returning_fund_currency
+       FROM
+               acq.fund
+       WHERE
+               id = returning_fund_id;
+       --
+       IF returning_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.return_funds_to_source: old fund currency is not defined for fund id: %', returning_fund_id;
+       END IF;
+       --
+       -- Identify the funding source(s) from which we want to transfer the money.
+       -- The principle is that we want to transfer the newest money first, because
+       -- we spend the oldest money first.  The priority for spending is defined
+       -- by a sort of the view acq.ordered_funding_source_credit.
+       --
+
+       -- There is no guarantee that the currency types in the funding source will be one of
+       -- the two types passed in to this function.  This needs to be fixed.
+
+       -- Add code to determine if there are enough funds left in the fund to transfer the
+       -- amount requested.
+       FOR source IN
+               SELECT
+                       ofsc.id,
+                       ofsc.funding_source,
+                       ofsc.amount,
+                       ofsc.amount * acq.exchange_ratio(fs.currency_type, returning_fund_currency)
+                               AS amount_credited_to_funding_source,
+                       -- We store the amount to retreive when amount_to_receive IS NOT NULL and the
+                       -- fs.currency_type is the same currency as the amount to receive.  We use the
+                       -- inverted_conversion_ratio because we are concerned with having the amount in
+                       -- the currency of the returning fund.  The AS amount_credited_to_fund value is
+                       -- converted using the source to returning fund currencies.
+                       fs.currency_type
+               FROM
+                       acq.ordered_funding_source_credit AS ofsc,
+                       acq.funding_source fs
+               WHERE
+                       ofsc.funding_source = fs.id
+                       AND ofsc.funding_source IN
+                       (
+                               SELECT funding_source
+                               FROM acq.fund_allocation
+                               WHERE fund = returning_fund_id
+                       )
+                       AND
+                       (
+                               ofsc.funding_source = return_funding_source
+                               OR return_funding_source IS NULL
+                       )
+               ORDER BY
+                       ofsc.sort_priority DESC,
+                       ofsc.sort_date DESC,
+                       ofsc.id DESC
+       LOOP
+               --
+               -- Determine how much money the returning fund got from this funding source,
+               -- denominated in the currency types of the fund.
+               -- This result may reflect transfers from previous iterations.
+               -- Do not return negative values, the fact that they are negative
+               -- indicates that the amount has already been returned.
+               --
+               SELECT
+                       COALESCE( sum( amount ), 0 )
+               INTO
+                       amount_allocated_by_funding_source           -- in currency of the funding source
+               FROM
+                       acq.fund_allocation
+               WHERE
+                       fund = returning_fund_id
+                       AND funding_source = source.funding_source
+                       AND amount > 0;
+               --
+               -- Determine how much to transfer from this credit, in the currency
+               -- of the funding source.   Begin with the amount remaining to be returned.
+               --
+               current_amount_to_return := funds_remaining_to_be_returned *
+                       acq.exchange_ratio(returning_fund_currency, source.currency_type);
+
+               --
+               -- Can't attribute more than was allocated to the fund:
+               --
+               IF current_amount_to_return > amount_allocated_by_funding_source THEN
+                       current_amount_to_return := amount_allocated_by_funding_source;
+               END IF;
+               --
+               -- Can't attribute more than the amount of the current credit from the funding source:
+               --
+               IF current_amount_to_return > source.amount THEN
+                       current_amount_to_return := source.amount;
+               END IF;
+               --
+               current_amount_to_return := trunc( current_amount_to_return, 2 );
+               --
+               -- current_amount_to_return is in the currency of the funding source
+               -- but funds_remaining_to_be_returned is in the currency of the fund.
+               -- So, convert current_amount_to_return into the value of the fund in
+               -- order to keep the funds_reminaing_to_be_retunred in the currency
+               -- of the fund.
+               --
+               fund_amount_to_return = current_amount_to_return *
+                       acq.exchange_ratio(source.currency_type, returning_fund_currency);
+
+               funds_remaining_to_be_returned := funds_remaining_to_be_returned - fund_amount_to_return;
+
+               --
+               -- Determine the amount to be deducted, if any,
+               -- from the old allocation.
+               --
+
+               --
+               -- Either the entire amount is being credited (the case where
+               -- current_amount_to_return is less than the
+               -- amount_allocated_by_funding_soruce and source.fund_amount), and we're using the whole allocation
+               -- from the current
+               -- funding source credit, or the amount left allocated in this funding
+               -- source credit if it is
+               -- less than the original amount of the credit (due to previous debits), so
+               -- use that amount
+               -- directly.  In all these cases these values are represented by current_amount_to_return.
+               -- We need to translate the amount into the source.currency_type regardless of the
+               -- condiitons above because we are crediting to the source not the fund.
+               --
+
+               funding_source_credit := trunc((- current_amount_to_return), 2);
+
+               IF returning_fund_currency IS NULL THEN
+                       RAISE EXCEPTION 'returning_fund_currency IS NULL';
+               END IF;
+
+               IF current_amount_to_return IS NULL THEN
+                       RAISE EXCEPTION 'current_amount_to_return IS NULL % - %', amount_allocated_by_funding_source, source.amount_credited_to_fund;
+               END IF;
+               -- Ensure the addition is less than 0
+               IF funding_source_credit < 0 THEN
+                       --
+                       -- Insert negative allocation for old fund in fund_allocation,
+                       -- converted into the currency of the funding source
+                       --
+                       INSERT INTO acq.fund_allocation (
+                               funding_source,
+                               fund,
+                               amount,
+                               allocator,
+                               note
+                       ) VALUES (
+                               source.funding_source,
+                               returning_fund_id,
+                               funding_source_credit,
+                               user_id,
+                               'Returning funds to the source'
+                       );
+               ELSE
+                       RAISE EXCEPTION 'funding_source_credit of % is greater than 0: % -- %', funding_source_credit, current_amount_to_return, funds_remaining_to_be_returned;
+               END IF;
+               --
+               IF trunc( current_amount_to_return, 2 ) > 0 THEN
+                       --
+                       -- Insert row in fund_transfer, using amounts in the currency of the fund
+                       --
+                       INSERT INTO acq.fund_transfer (
+                               src_fund,
+                               src_amount,
+                               dest_fund,
+                               dest_amount,
+                               transfer_user,
+                               note,
+                               funding_source_credit
+                       ) VALUES (
+                               returning_fund_id,
+                               trunc( fund_amount_to_return, 2 ),
+                               NULL,
+                               NULL,
+                               user_id,
+                               xfer_note,
+                               source.id
+                       );
+               END IF;
+               --
+               -- It should be impossible for funds_remaining_to_be_returned to be less than 0.
+               --
+               IF trunc(funds_remaining_to_be_returned, 2) = 0.00 THEN
+                       EXIT;                   -- Nothing more to be transferred
+               END IF;
+       END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.rollover_with_return_funds_to_source.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.rollover_with_return_funds_to_source.sql
new file mode 100644 (file)
index 0000000..ecb20fb
--- /dev/null
@@ -0,0 +1,215 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit( old_year INTEGER, user_id INTEGER, org_unit_id INTEGER, encumb_only BOOL DEFAULT FALSE ) RETURNS VOID AS $$
+       SELECT acq.rollover_funds_by_org_tree( $1, $2, $3, $4, FALSE );
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
+       old_year INTEGER,
+       user_id INTEGER,
+       org_unit_id INTEGER,
+       encumb_only BOOL DEFAULT FALSE,
+       include_desc BOOL DEFAULT TRUE
+) RETURNS VOID AS $$
+DECLARE
+--
+new_fund    INT;
+new_year    INT := old_year + 1;
+org_found   BOOL;
+perm_ous    BOOL;
+xfer_amount NUMERIC := 0;
+roll_fund   RECORD;
+deb         RECORD;
+detail      RECORD;
+roll_distrib_forms BOOL;
+--
+BEGIN
+       --
+       -- Sanity checks
+       --
+       IF old_year IS NULL THEN
+               RAISE EXCEPTION 'Input year argument is NULL';
+       ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
+               RAISE EXCEPTION 'Input year is out of range';
+       END IF;
+       --
+       IF user_id IS NULL THEN
+               RAISE EXCEPTION 'Input user id argument is NULL';
+       END IF;
+       --
+       IF org_unit_id IS NULL THEN
+               RAISE EXCEPTION 'Org unit id argument is NULL';
+       ELSE
+               --
+               -- Validate the org unit
+               --
+               SELECT TRUE
+               INTO org_found
+               FROM actor.org_unit
+               WHERE id = org_unit_id;
+               --
+               IF org_found IS NULL THEN
+                       RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
+               ELSIF encumb_only THEN
+                       SELECT INTO perm_ous value::BOOL FROM
+                       actor.org_unit_ancestor_setting(
+                               'acq.fund.allow_rollover_without_money', org_unit_id
+                       );
+                       IF NOT FOUND OR NOT perm_ous THEN
+                               RAISE EXCEPTION 'Encumbrance-only rollover not permitted at org %', org_unit_id;
+                       END IF;
+               END IF;
+       END IF;
+       --
+       -- Loop over the propagable funds to identify the details
+       -- from the old fund plus the id of the new one, if it exists.
+       --
+       FOR roll_fund in
+       SELECT
+               oldf.id AS old_fund,
+               oldf.org,
+               oldf.name,
+               oldf.currency_type,
+               oldf.code,
+               oldf.rollover,
+               newf.id AS new_fund_id
+       FROM
+               acq.fund AS oldf
+               LEFT JOIN acq.fund AS newf
+                       ON ( oldf.code = newf.code )
+       WHERE
+               oldf.year = old_year
+               AND oldf.propagate
+               AND newf.year = new_year
+               AND ( ( include_desc AND oldf.org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) )
+                   OR (NOT include_desc AND oldf.org = org_unit_id ) )
+       LOOP
+               --
+               --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
+               --
+               IF roll_fund.new_fund_id IS NULL THEN
+                       --
+                       -- The old fund hasn't been propagated yet.  Propagate it now.
+                       --
+                       INSERT INTO acq.fund (
+                               org,
+                               name,
+                               year,
+                               currency_type,
+                               code,
+                               rollover,
+                               propagate,
+                               balance_warning_percent,
+                               balance_stop_percent
+                       ) VALUES (
+                               roll_fund.org,
+                               roll_fund.name,
+                               new_year,
+                               roll_fund.currency_type,
+                               roll_fund.code,
+                               true,
+                               true,
+                               roll_fund.balance_warning_percent,
+                               roll_fund.balance_stop_percent
+                       )
+                       RETURNING id INTO new_fund;
+               ELSE
+                       new_fund = roll_fund.new_fund_id;
+               END IF;
+               --
+               --
+               -- Determine the amount to transfer or return
+               --
+               SELECT amount
+               INTO xfer_amount
+               FROM acq.fund_spent_balance
+               WHERE fund = roll_fund.old_fund;
+
+               IF NOT encumb_only AND roll_fund.rollover THEN
+                       -- Transfer balance from old fund to new
+                       --
+                       -- RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
+                       --
+                       IF xfer_amount > 0 THEN
+                               PERFORM acq.transfer_fund(
+                                       roll_fund.old_fund,
+                                       xfer_amount,
+                                       new_fund,
+                                       user_id,
+                                       'Rollover',
+                                       xfer_amount,
+                                       NULL,
+                                       TRUE
+                               );
+                       END IF;
+               ELSE
+                       --
+                       -- Transfer balance from old fund to the void
+                       --
+                       -- RAISE NOTICE 'Returning % from fund % to its source(s)', xfer_amount, roll_fund.old_fund;
+                       --
+                       IF xfer_amount > 0 THEN
+                               PERFORM acq.return_funds_to_source(
+                                       roll_fund.old_fund,
+                                       xfer_amount,
+                                       user_id,
+                                       'Rollover - return funds to source'
+                               );
+                       END IF;
+               END IF;
+               --
+               IF roll_fund.rollover THEN
+                       --
+                       -- Move any lineitems from the old fund to the new one
+                       -- where the associated debit is an encumbrance.
+                       --
+                       -- Any other tables tying expenditure details to funds should
+                       -- receive similar treatment.  At this writing there are none.
+                       --
+                       UPDATE acq.lineitem_detail
+                       SET fund = new_fund
+                       WHERE
+                               fund = roll_fund.old_fund -- this condition may be redundant
+                               AND fund_debit IN
+                               (
+                                       SELECT id
+                                       FROM acq.fund_debit
+                                       WHERE
+                                               fund = roll_fund.old_fund
+                                               AND encumbrance
+                               );
+                       --
+                       -- Move encumbrance debits from the old fund to the new fund
+                       --
+                       UPDATE acq.fund_debit
+                       SET fund = new_fund
+                       wHERE
+                       fund = roll_fund.old_fund
+                       AND encumbrance;
+               END IF;
+
+               -- Rollover distribution formulae funds
+               SELECT INTO roll_distrib_forms value::BOOL FROM
+                       actor.org_unit_ancestor_setting(
+                               'acq.fund.rollover_distrib_forms', org_unit_id
+                       );
+
+               IF roll_distrib_forms THEN
+                       UPDATE acq.distribution_formula_entry 
+                       SET fund = roll_fund.new_fund_id
+                       WHERE fund = roll_fund.old_fund;
+               END IF;
+
+               --
+               -- Mark old fund as inactive, now that we've closed it
+               --
+               UPDATE acq.fund
+               SET active = FALSE
+               WHERE id = roll_fund.old_fund;
+       END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.transfer_fund.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.transfer_fund.sql
new file mode 100644 (file)
index 0000000..e2d57dd
--- /dev/null
@@ -0,0 +1,594 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('XXXX');
+
+DROP FUNCTION acq.transfer_fund (INT, NUMERIC, INT, NUMERIC, INT, TEXT);
+
+CREATE OR REPLACE FUNCTION acq.transfer_fund(
+    transferring_fund_id            IN INT,
+    amount_to_transfer              IN NUMERIC,             -- In currency of
+                                                                   -- transferring fund
+    receiving_fund_id               IN INT,
+    user_id                         IN INT,                 -- User initiating
+                                                                   -- the transfer
+    xfer_note                       IN TEXT,                -- To be recorded in
+                                                                   -- acq.fund_transfer
+    amount_to_receive               IN NUMERIC DEFAULT NULL,-- In currency of
+                                                                   -- receiving fund.
+                                                                   -- It can be null in
+                                                                   -- cases where the
+                                                                   -- amount to transfer
+                                                                   -- will be calculated
+                                                                   -- within the function.
+    transferring_funding_source     IN INT DEFAULT NULL,    -- If user wants to
+                                                                   -- specify a funding
+                                                                   -- source (see notes)
+    rollover_transfer               IN BOOLEAN DEFAULT FALSE-- If true, then
+                                                                   -- we make sure the
+                                                                   -- amount being
+                                                                   -- transferred is not
+                                                                   -- greater than the
+                                                                   -- spent balance as
+                                                                   -- oposed to the
+                                                                   -- combined balance.
+) RETURNS VOID AS $$
+/* -------------------------------------------------------------------------------
+
+Function to transfer money from one fund to another.
+
+A transfer is represented as a pair of entries in acq.fund_allocation, with a
+negative amount for the transferring fund and a positive amount for the
+receiving fund.  In some cases there may be more than one such pair of entries
+in order to pull the money from different funding sources, or more specifically
+from different funding source credits.  For each such pair there is also an
+entry in acq.fund_transfer.
+
+Since funding_source is a non-nullable column in acq.fund_allocation, we must
+choose a funding source for the transferred money to come from.  This choice
+must meet two constraints, so far as possible:
+
+1. The amount transferred from a given funding source must not exceed the
+amount allocated to the old fund by the funding source.  To that end we
+compare the amount being transferred to the amount allocated.
+
+2. We shouldn't transfer money that has already been spent or encumbered, as
+defined by the funding attribution process.  We attribute expenses to the
+oldest funding source credits first.  In order to avoid transferring that
+attributed money, we reverse the priority, transferring from the newest funding
+source credits first.  There can be no guarantee that this approach will
+avoid overcommitting a fund, but no other approach can do any better.
+
+In this context the age of a funding source credit is defined by the
+deadline_date for credits with deadline_dates, and by the effective_date for
+credits without deadline_dates, with the proviso that credits with deadline_dates
+are all considered "older" than those without.
+
+----------
+
+In the signature for this function, there is one last parameter commented out,
+named "transferring_funding_source".  Correspondingly, the WHERE clause for the query
+driving the main loop has an OR clause commented out, which references the
+transferring_funding_source parameter.
+
+If these lines are uncommented, this function will allow the user optionally to
+restrict a fund transfer to a specified funding source.  If the source
+parameter is left NULL, then there will be no such restriction.
+
+------------------------------------------------------------------------------- */
+DECLARE
+       -- Are the transferring and receiving funds the same currency?
+       same_currency                                                   BOOLEAN;
+
+       -- The ratio between the transferring fund and the receiving
+       -- fund.  Other ratios may be needed due to other currencies
+       -- in the funding sources.
+       conversion_ratio                                                NUMERIC;
+
+       -- The ratio between the receiving fund and the transferring
+       -- fund.  Other ratios may be needed due to other currencies
+       -- in the funding sources.
+       inverted_conversion_ratio                                       NUMERIC;
+
+       transferring_fund_currency                                      TEXT;
+
+       -- Kept in the currency of transferring fund
+       funds_remaining_to_be_transferred                               NUMERIC;
+
+       -- The transferring fund must be active if the amount to
+       -- transfer isnegative because it will have to accept funds
+       transferring_fund_active                                        BOOLEAN;
+
+       receiving_fund_currency                                         TEXT;
+
+       -- Kept in currency of recieving fund
+       funds_remaining_to_be_received                                  NUMERIC;
+
+       -- The receiving fund must be active to accept funds
+       receiving_fund_active                                           BOOLEAN;
+
+       -- Kept in currency of the funding source
+       current_amount_to_transfer                                      NUMERIC;
+
+       -- In the currency of the fund
+       fund_amount_to_transfer                                         NUMERIC;
+
+       -- In currency of recevinig fund
+       current_amount_to_receive                                       NUMERIC;
+
+       -- In currency of funding source
+       funding_source_credit                                           NUMERIC;
+
+       -- Ratio between the transferring fund and the source
+       source_credit_conversion_ratio                                  NUMERIC;
+
+       -- In currency of funding source
+       funding_source_debit                                            NUMERIC;
+
+       -- Ratio between the receiving fund and the source
+       source_debit_conversion_ratio                                   NUMERIC;
+
+       -- Kept in currency of transferring fund
+       amount_allocated_by_funding_source                              NUMERIC;
+
+       -- Kept in currency of the transferring fund
+       amount_credited                                                 NUMERIC;
+
+       -- Kept in currency of transferring fund
+       amount_currently_allocated_to_transferring_fund                 NUMERIC;
+
+       -- A JOIN of acq.funding_source and
+       -- acq.ordered_funding_source_credit
+       source                                                          RECORD;
+
+       -- Used to swap funds when a negative amount is supplied
+       -- to amount_to_transfer
+       temp_fund_id                                                    INTEGER;
+
+       -- Used to sawp currencies when a negative amount is
+       -- supplied to amount_to_transfer
+       temp_currency                                                   TEXT;
+
+       -- Used to swap the amount to transfer when negative
+       -- amounts are supplied to amount_to_transfer and
+       -- amount_to_receive
+       temp_amount                                                         NUMERIC;
+BEGIN
+       --
+       -- We need to lock this table because the exchange rates could be modifed
+       -- while we are doing a transfer, which would create unpredictable results
+       --
+       LOCK acq.exchange_rate
+       IN EXCLUSIVE MODE;
+
+       --
+       -- Sanity checks
+       --
+       IF transferring_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: transferring fund id is NULL';
+       END IF;
+       --
+       IF receiving_fund_id IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: recieving fund id is NULL';
+       END IF;
+       --
+       IF amount_to_transfer IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
+       END IF;
+       --
+       IF user_id IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
+       END IF;
+       --
+       IF amount_to_transfer = 0 THEN
+               RAISE EXCEPTION 'acq.transfer_fund amount to transfer is 0';
+       END IF;
+
+       IF amount_to_receive IS NOT NULL THEN
+               IF (amount_to_transfer > 0 AND amount_to_receive < 0) OR
+                       (amount_to_transfer < 0 AND amount_to_receive > 0) THEN
+                       RAISE EXCEPTION 'acq.transfer_fund amount to transfer (%) and amount to receive (%) do not have the same sign', amount_to_transfer, amount_to_receive;
+               END IF;
+       END IF;
+       --
+       -- Get the currency and active status  of the transferring fund
+       --
+       SELECT
+               currency_type,
+               active
+       INTO
+               transferring_fund_currency,
+               transferring_fund_active
+       FROM
+               acq.fund
+       WHERE
+               id = transferring_fund_id;
+
+       IF transferring_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: transferring fund currency is not defined for fund id: %', transferring_fund_id;
+       END IF;
+       --
+       -- Get the currency and active status of the receiving fund
+       --
+       SELECT
+               currency_type,
+               active
+       INTO
+               receiving_fund_currency,
+               receiving_fund_active
+       FROM
+               acq.fund
+       WHERE
+               id = receiving_fund_id;
+       --
+       IF receiving_fund_currency IS NULL THEN
+               RAISE EXCEPTION 'acq.transfer_fund: receiving fund currency is not defined for fund id: %', receiving_fund_id;
+       END IF;
+       --
+       -- If the amount to transfer is negative then swap the transferring and
+       -- receiving funds
+       --
+       IF amount_to_transfer < 0 THEN
+               --
+               -- Because the amount is negative, we need to check if the transferring
+               -- fund is active, so we do not transfer to an inactive transferring fund
+               --
+               IF NOT transferring_fund_active THEN
+                       --
+                       -- No point in putting money into a fund from whence you can't spend it
+                       --
+                       RAISE EXCEPTION 'acq.transfer_fund: Amount to transfer is negative, and transferring fund id % is inactive', transferring_fund_id;
+               END IF;
+
+               IF (transferring_fund_crrency != receiving_fund_currecy) AND
+                       (amount_to_recevie IS NULL) THEN
+                       -- convert the funds amount to transfer from
+                       -- the currency of the transferring fund into the currecny of
+                       -- the receiving fund.  Only do this if there is no value in
+                       -- in amount_to_receive
+                       amount_to_transfer := trunc( amount_to_transfer *
+                               acq.exchange_ratio(transferring_fund_currency, receiving_fund_currency), 2 );
+               END IF;
+
+               -- make the amount to transfer, and conditionally the amount to recieve,  positive
+               amount_to_transfer := amount_to_transfer * -1;
+
+               IF amount_to_receive IS NOT NULL THEN
+                       amount_to_receive := amount_to_receive * -1;
+               END IF;
+
+               temp_fund_id := transferring_fund_id;
+               temp_currency := transferring_fund_currency;
+               temp_amount = amount_to_transfer;
+               transferring_fund_id := receiving_fund_id;
+               transferring_fund_currency := receiving_fund_currency;
+               amount_to_transfer = amount_to_receive;
+               receiving_fund_id := temp_fund_id;
+               receiving_fund_currency := temp_currency;
+               amount_to_receive = temp_amount;
+       ELSIF NOT receiving_fund_active THEN
+               -- We need to check this after a possible swap and not before
+               -- because it is ok to transfer from an inactive fund, so
+               -- the fact that receiving fund has not been swapped means
+               -- we can now check to make sure it is active
+               --
+               -- No point in putting money into a fund from whence you can't spend it
+               --
+               RAISE EXCEPTION 'acq.transfer_fund: receiving fund id % is inactive', receiving_fund_id;
+       END IF;
+
+       -- RAISE EXCEPTION 'acq.transfer_fund test';
+
+       -- ensure there is enough money in the fund to cover the amount to transfer
+       -- we do this after checking if we need to swap funds to ensure we are
+       -- checking the correct fund for the necessary funds to transfer
+       -- If this is a rollover transfer then we check against the spent balance
+       -- which will be higher because it does not contain the encubmerance total.
+       -- Otherwise, makes sure we do not spend encumbered funds
+       IF rollover_transfer THEN
+               SELECT amount
+               INTO amount_currently_allocated_to_transferring_fund
+               FROM acq.fund_spent_balance
+               WHERE fund = transferring_fund_id;
+       ELSE
+               SELECT amount
+               INTO amount_currently_allocated_to_transferring_fund
+               FROM acq.fund_combined_balance
+               WHERE fund = transferring_fund_id;
+       END IF;
+
+       -- Ensure there is enough money left in this fund to fulfill the transfer
+       IF amount_to_transfer > amount_currently_allocated_to_transferring_fund THEN
+               RAISE EXCEPTION 'Cannot transfer more money than is currently allocted to the fund. fund id: % has % allocated to it, and acq.transfer_fund is trying to debit it for %',
+                       transferring_fund_id, amount_currently_allocated_to_transferring_fund, amount_to_transfer;
+       END IF;
+
+       --
+       -- Initialize the amounts to be transferred, each denominated
+       -- in the currency of its respective fund.  They will be
+       -- reduced on each iteration of the loop.
+       --
+       funds_remaining_to_be_transferred := amount_to_transfer;
+       IF transferring_fund_currency = receiving_fund_currency THEN
+               same_currency := true;
+               conversion_ratio := 1;
+               inverted_conversion_ratio := 1;
+               funds_remaining_to_be_received := funds_remaining_to_be_transferred;
+       ELSE
+               --
+               -- We'll have to translate currency between funds.
+               -- In this version we are only using the value to be transfered
+               -- from the old fund.  We will convert in this funciton
+               --
+               same_currency := false;
+
+               IF amount_to_receive IS NULL THEN
+                       conversion_ratio := acq.exchange_ratio(transferring_fund_currency, receiving_fund_currency);
+                       -- This is not needed yet, but I am setting it here because it can be calculated, which
+                       -- ensures that if the case where amount_to_receive is NULl requires the inversted_conversion_ratio
+                       -- then it will be set.e
+                       inverted_conversion_ratio = acq.exchange_ratio(receiving_fund_currency, transferring_fund_currency);
+               ELSE
+                       -- Because amount_to_receive has been speficied we will
+                       -- use whatever conversion ratio the system making this call used
+                       -- to calculate the amount_to_receive
+                       conversion_ratio := amount_to_receive/amount_to_transfer;
+                       inverted_conversion_ratio = amount_to_transfer/amount_to_receive;
+               END IF;
+
+               funds_remaining_to_be_received := trunc( funds_remaining_to_be_transferred * conversion_ratio, 2 );
+       END IF;
+
+       --
+       -- Identify the funding source(s) from which we want to transfer the money.
+       -- The principle is that we want to transfer the newest money first, because
+       -- we spend the oldest money first.  The priority for spending is defined
+       -- by a sort of the view acq.ordered_funding_source_credit.
+       --
+
+       FOR source IN
+               SELECT
+                       ofsc.id,
+                       ofsc.funding_source,
+                       ofsc.amount,
+                       ofsc.amount * acq.exchange_ratio(fs.currency_type, transferring_fund_currency)
+                               AS amount_credited_to_funding_source,
+                       -- We store the amount to retreive when amount_to_receive IS NOT NULL and the
+                       -- fs.currency_type is the same currency as the amount to receive.  We use the
+                       -- inverted_conversion_ratio because we are concerned with having the amount in
+                       -- the currency of the transferring fund.  The AS amount_credited_to_fund value is
+                       -- converted using the source to transferring currencies.  However, if the soure
+                       -- is the same currency as the receiving currency, then we need to calculate this
+                       -- using the inversion of the ratio used to convert the currencies when the
+                       -- amount_to_receive is specified.
+                       ofsc.amount * inverted_conversion_ratio AS amount_credited_to_funding_source_at_supplied_ratio,
+                       fs.currency_type
+               FROM
+                       acq.ordered_funding_source_credit AS ofsc,
+                       acq.funding_source fs
+               WHERE
+                       ofsc.funding_source = fs.id
+                       AND ofsc.funding_source IN
+                       (
+                               SELECT funding_source
+                               FROM acq.fund_allocation
+                               WHERE fund = transferring_fund_id
+                       )
+                       AND
+                       (
+                               ofsc.funding_source = transferring_funding_source
+                               OR transferring_funding_source IS NULL
+                       )
+               ORDER BY
+                       ofsc.sort_priority DESC,
+                       ofsc.sort_date DESC,
+                       ofsc.id DESC
+       LOOP
+               --
+               -- Determine total amount of credits that this funding source has give this fund
+               -- Denominated in the currency types of the transferring fund.
+               -- Because we need to look at specific allocations,
+               -- this result may reflect transfers from previous iterations.
+               -- Do not transfer negative values, the fact that they are negative
+               -- indicates that the amount has already been transferred.
+               --
+               SELECT
+                       COALESCE( sum( amount ), 0 )
+               INTO
+                       -- in currency of the funding source
+                       amount_allocated_by_funding_source
+               FROM
+                       acq.fund_allocation
+               WHERE
+                       fund = transferring_fund_id
+                       AND funding_source = source.funding_source
+                       AND amount > 0;
+
+               -- Determine how much to transfer from this credit, in the currency
+               -- of the funding source.   Begin with the amount remaining to be transferred
+               --
+               current_amount_to_transfer := funds_remaining_to_be_transferred *
+                       acq.exchange_ratio(transferring_fund_currency, source.currency_type);
+
+               --
+               -- Can't attribute more than was allocated to the fund:
+               --
+
+               IF current_amount_to_transfer > amount_allocated_by_funding_source THEN
+                       current_amount_to_transfer := amount_allocated_by_funding_source;
+               END IF;
+
+               --
+               -- Can't attribute more than the amount of the current credit from the funding source:
+               --
+
+               IF (current_amount_to_transfer > source.amount) THEN
+                       current_amount_to_transfer := source.amount;
+               END IF;
+
+               --
+               current_amount_to_transfer := trunc( current_amount_to_transfer, 2 );
+               --
+               -- At some point funds_remaining_to_be_transferred value WILL become 0
+               -- because current_amount_to_transfer is set to funds_remaining_to_be_transferred above
+               -- and it is never increased above that.
+               --
+               fund_amount_to_transfer = current_amount_to_transfer *
+                       acq.exchange_ratio(source.currency_type, transferring_fund_currency);
+
+               funds_remaining_to_be_transferred := funds_remaining_to_be_transferred - fund_amount_to_transfer;
+
+               --
+               -- Determine the amount to be credited, if any,
+               -- to the funding source.
+               --
+
+               --
+               -- Either the entire amount is being deducted (the case where current_amount_to_transfer
+               -- is less than the
+               -- amount_allocated_by_funding_source and source.amount_credited_to_fund), we're
+               -- are deducting the whole allocation from the current funding source credit,
+               -- or we are deducting the amount allocated in this single unding source credit if it is
+               -- less than the original amount of the credit (due to previous debits).
+               -- In all these cases these values are represented by current_amount_to_transfer.
+               -- We need to convert the amount into the source.currency_type regardless of the
+               -- condiitons above because we are deducting from the source not the fund.
+               --
+
+               IF (amount_to_receive IS NULL) AND (receiving_fund_currency != source.currency_type) THEN
+                       source_credit_conversion_ratio := acq.exchange_ratio( transferring_fund_currency, source.currency_type );
+               ELSE
+                       -- use the calculated ratio when the amount_to_receive is specified
+                       source_credit_conversion_ratio := conversion_ratio;
+               END IF;
+
+               funding_source_credit := trunc((- current_amount_to_transfer ), 2);
+
+               -- Ensure the credit is less than or equal to 0
+               -- We insert the case where it is equal to 0 because
+               -- there may be a need for a corresponding 0 entry in acq.fund_transfer,
+               -- which is INSERTed near the end of this function.
+
+               -- transfering a negative amount from a fund is the same as
+               -- crediting it back to the funding source, which must
+               -- happen before the amount is transferred to the receiving fund
+               IF funding_source_credit <= 0 THEN
+                       --
+                       -- Insert negative (or 0) allocation for old fund in fund_allocation.
+                       --
+                       INSERT INTO acq.fund_allocation (
+                               funding_source,
+                               fund,
+                               amount,
+                               allocator,
+                               note
+                       ) VALUES (
+                               source.funding_source,
+                               transferring_fund_id,
+                               funding_source_credit,
+                               user_id,
+                               'Transfer to fund ' || receiving_fund_id
+                       );
+               ELSE
+                       RAISE EXCEPTION 'funding_source_credit of % is greater than 0', funding_source_credit;
+               END IF;
+               --
+               --
+               -- Determine how much to add to the receiving fund, in
+               -- its currency, and how much remains to be added:
+               --
+               current_amount_to_receive := current_amount_to_transfer;
+
+               --
+               -- Determine how much to add, if any,
+               -- to the receiving fund's allocation.
+               --
+
+               funding_source_debit := current_amount_to_receive;
+               --
+               -- Ensure the debit is greater than or equal to 0
+               -- Similar to funding_source_credit there may be a corresponding
+               -- entry in acq.fund_transfer.
+               --
+               IF funding_source_debit >= 0 THEN
+                       --
+                       -- Insert positive allocation (or 0) for new fund in fund_allocation,
+                       -- in the currency of the founding source
+                       --
+                       INSERT INTO acq.fund_allocation (
+                               funding_source,
+                               fund,
+                               amount,
+                               allocator,
+                               note
+                       ) VALUES (
+                               source.funding_source,
+                               receiving_fund_id,
+                               funding_source_debit,
+                               user_id,
+                               'Transfer from fund ' || transferring_fund_id
+                       );
+               ELSE
+                       RAISE EXCEPTION 'acq.fund_transfer: funding_source_debit % is less than 0', funding_source_debit;
+               END IF;
+               --
+               -- Either of these can be greater than 0.
+               -- current_amount_to_transfer is the value to be transferred.  current_amount_to_receive is that
+               -- value converted into the new funds currency
+               -- If the entire amount can be taken from a single source credit. Then
+               -- this is a simple calculation.
+               -- Otherwise, current_amount_to_transfer is set to the amount left in funds_remaining_to_be_transferred
+               -- after a loop or the values in the current funding source credit.
+               -- And, current_amount_to_receive is set to the converted amount from current_amount_to_transfer.
+               -- Finally, once funds_remaining_to_be_transferred = 0 curr_new_amount is set to funds_remaining_to_be_received,
+               -- and new remaining will be 0 after that. Because funds_remaining_to_be_received
+               -- is only calculated once (after that it is only debited), this should
+               -- ensure that they both arrive at 0 together
+               -- There may be some conversion issues that make this not true, but I
+               -- cannot think of any at the moment.  The code needs further examination.
+               IF trunc( current_amount_to_transfer, 2 ) > 0
+               OR trunc( current_amount_to_receive, 2 ) > 0 THEN
+                       --
+                       -- Insert row in fund_transfer, using amounts in the currency of the funds
+                       --
+                       INSERT INTO acq.fund_transfer (
+                               src_fund,
+                               src_amount,
+                               dest_fund,
+                               dest_amount,
+                               transfer_user,
+                               note,
+                               funding_source_credit
+                       ) VALUES (
+                               transferring_fund_id,
+                               trunc(current_amount_to_transfer *
+                                       acq.exchange_ratio(source.currency_type, transferring_fund_currency), 2),
+                               receiving_fund_id,
+                               trunc(current_amount_to_receive *
+                                       acq.exchange_ratio(source.currency_type, receiving_fund_currency), 2),
+                               user_id,
+                               xfer_note,
+                               source.id
+                       );
+               END IF;
+               --
+               -- It should be impossible for funds_remaining_to_be_transferred to be less than 0.
+               --
+               IF funds_remaining_to_be_transferred = 0 THEN
+                       EXIT;                   -- Nothing more to be transferred
+               ELSIF funds_remaining_to_be_transferred < 0 THEN
+                       RAISE EXCEPTION 'acq.transfer_fund: funds_remaining_to_be_transferred is less thant 0: % FIND OUT WHY',
+                               funds_remaining_to_be_transferred;
+               END IF;
+       END LOOP;
+
+       --
+       -- This should not be possible any more, but we can leave it in just in case there
+       -- is a case that has not been thought of.
+       --
+       IF trunc(funds_remaining_to_be_transferred, 2) > 0 THEN
+               RAISE EXCEPTION 'not all of funds_remaining_to_be_transferred were transfered.  There must not have been enough funds in the funding sources';
+       END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+COMMIT;