[open-ils-commits] r14566 - in trunk/Open-ILS: examples src/sql/Pg src/sql/Pg/upgrade (scottmk)

svn at svn.open-ils.org svn at svn.open-ils.org
Thu Oct 22 14:55:18 EDT 2009


Author: scottmk
Date: 2009-10-22 14:55:14 -0400 (Thu, 22 Oct 2009)
New Revision: 14566

Added:
   trunk/Open-ILS/src/sql/Pg/upgrade/0049.schema.acq_funding_allocation_percent.sql
Modified:
   trunk/Open-ILS/examples/fm_IDL.xml
   trunk/Open-ILS/src/sql/Pg/002.schema.config.sql
Log:
Create new table acq.fund_allocation_percent, to store the percentages by which
future funding credits will be allocated to funds.

Create a trigger to ensure that the percentages never add up to more than
100 for any given funding source.

Populate the new table from acq.fund_allocation.

In acq.fund_allocation: convert any percentages to amounts, and nullify the
percentages.

M    Open-ILS/src/sql/Pg/002.schema.config.sql
A    Open-ILS/src/sql/Pg/upgrade/0049.schema.acq_funding_allocation_percent.sql
M    Open-ILS/examples/fm_IDL.xml


Modified: trunk/Open-ILS/examples/fm_IDL.xml
===================================================================
--- trunk/Open-ILS/examples/fm_IDL.xml	2009-10-22 16:19:28 UTC (rev 14565)
+++ trunk/Open-ILS/examples/fm_IDL.xml	2009-10-22 18:55:14 UTC (rev 14566)
@@ -1690,6 +1690,7 @@
 			<field reporter:label="Billable Transactions" name="billable_transactions" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Checkins" name="checkins" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Circulations Performed as Staff" name="performed_circulations" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Fund Allocation Percentages" name="fund_alloc_pcts" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="demographic" reltype="might_have" key="id" map="" class="rud"/>
@@ -1717,8 +1718,10 @@
 			<link reporter:label="Check-ins Performed as Staff" field="checkins" reltype="has_many" key="checkin_staff" map="" class="circ"/>
 			<link field="cards" reltype="has_many" key="usr" map="" class="ac"/>
 			<link reporter:label="Circulations Performed as Staff" field="performed_circulations" reltype="has_many" key="circ_staff" map="" class="circ"/>
+			<link field="fund_alloc_pcts" reltype="has_many" key="allocator" map="" class="acqfap"/>
 		</links>
 	</class>
+
 	<class id="coust" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::org_unit_setting_type" oils_persist:tablename="config.org_unit_setting_type" reporter:label="Organizational Unit Setting Type">
 		<fields oils_persist:primary="name">
 			<field name="name" reporter:datatype="text"/>
@@ -2778,6 +2781,7 @@
 			<field reporter:label="Addresses" name="addresses" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Checkins" name="checkins" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Workstations" name="workstations" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Fund Allocation Percentages" name="fund_alloc_pcts" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="billing_address" reltype="has_a" key="id" map="" class="aoa"/>
@@ -2796,6 +2800,7 @@
 			<link field="workstations" reltype="has_many" key="owning_lib" map="" class="aws"/>
 			<link field="distribution_formulas" reltype="has_many" key="owner" map="" class="acqdf"/>
 			<link field="distribution_formula_entries" reltype="has_many" key="owning_lib" map="" class="acqdfe"/>
+			<link field="fund_alloc_pcts" reltype="has_many" key="org" map="" class="acqfap"/>
 		</links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -4078,12 +4083,14 @@
 			<field name="summary" oils_persist:virtual="true"/>
 			<field reporter:label="Allocations" name="allocations" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Credits" name="credits" oils_persist:virtual="true" reporter:datatype="link"/>
+			<field reporter:label="Fund Allocation Percentages" name="fund_alloc_pcts" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="currency_type" reltype="has_a" key="code" map="" class="acqct"/>
 			<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
             <link field="allocations" reltype="has_many" map="" key="funding_source" class="acqfa"/>
             <link field="credits" reltype="has_many" key="funding_source" map="" class="acqfscred"/>
+			<link field="fund_alloc_pcts" reltype="has_many" key="funding_source" map="" class="acqfap"/>
 		</links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -4262,6 +4269,32 @@
 		</links>
 	</class>
 
+    <class id="acqfap" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="acq::fund_allocation_percent" oils_persist:tablename="acq.fund_allocation_percent" reporter:label="Fund Allocation Percent">
+        <fields oils_persist:primary="id" oils_persist:sequence="acq.fund_allocation_percent_id_seq">
+            <field reporter:label="Allocation ID" name="id" reporter:datatype="id" />
+            <field reporter:label="Funding Source" name="funding_source" reporter:datatype="link" />
+            <field reporter:label="Org Unit" name="org" reporter:datatype="org_unit" />
+            <field reporter:label="" name="fund_code" reporter:datatype="text" />
+            <field reporter:label="Percent" name="percent" reporter:datatype="float" />
+            <field reporter:label="Allocating User" name="allocator" reporter:datatype="link" />
+            <field reporter:label="Note" name="note" reporter:datatype="text" />
+            <field reporter:label="Create Time" name="create_time" reporter:datatype="timestamp" />
+        </fields>
+        <links>
+            <link field="allocator" reltype="has_a" key="id" map="" class="au"/>
+            <link field="org" reltype="has_a" key="id" map="" class="aou"/>
+            <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+        </links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<create permission="ADMIN_ACQ_FUND_ALLOCATION_PERCENT" global_required="true"/>
+				<retrieve permission="VIEW_ACQ_FUND_ALLOCATION_PERCENT" global_required="true"/>
+				<update permission="ADMIN_ACQ_FUND_ALLOCATION_PERCENT" global_required="true"/>
+				<delete permission="ADMIN_ACQ_FUND_ALLOCATION_PERCENT" global_required="true"/>
+			</actions>
+		</permacrud>
+    </class>
+
 	<class id="acqpl" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::picklist" oils_persist:tablename="acq.picklist" reporter:label="Pick List">
 		<fields oils_persist:primary="id" oils_persist:sequence="acq.picklist_id_seq">
 			<field reporter:label="Picklist ID" name="id" reporter:datatype="id" />

Modified: trunk/Open-ILS/src/sql/Pg/002.schema.config.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/002.schema.config.sql	2009-10-22 16:19:28 UTC (rev 14565)
+++ trunk/Open-ILS/src/sql/Pg/002.schema.config.sql	2009-10-22 18:55:14 UTC (rev 14566)
@@ -51,7 +51,7 @@
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0048'); -- berick
+INSERT INTO config.upgrade_log (version) VALUES ('0049'); -- Scott McKellar
 
 
 CREATE TABLE config.bib_source (

Added: trunk/Open-ILS/src/sql/Pg/upgrade/0049.schema.acq_funding_allocation_percent.sql
===================================================================
--- trunk/Open-ILS/src/sql/Pg/upgrade/0049.schema.acq_funding_allocation_percent.sql	                        (rev 0)
+++ trunk/Open-ILS/src/sql/Pg/upgrade/0049.schema.acq_funding_allocation_percent.sql	2009-10-22 18:55:14 UTC (rev 14566)
@@ -0,0 +1,251 @@
+BEGIN;
+
+-- Create new table acq.fund_allocation_percent
+-- Populate it from acq.fund_allocation
+-- Convert all percentages to amounts in acq.fund_allocation
+
+INSERT INTO config.upgrade_log (version) VALUES ('0049'); -- Scott McKellar
+
+CREATE TABLE acq.fund_allocation_percent
+(
+    id                   SERIAL            PRIMARY KEY,
+    funding_source       INT               NOT NULL REFERENCES acq.funding_source
+                                               DEFERRABLE INITIALLY DEFERRED,
+    org                  INT               NOT NULL REFERENCES actor.org_unit
+                                               DEFERRABLE INITIALLY DEFERRED,
+    fund_code            TEXT,
+    percent              NUMERIC           NOT NULL,
+    allocator            INTEGER           NOT NULL REFERENCES actor.usr
+                                               DEFERRABLE_INITIALLY DEFERRED,
+    note                 TEXT,
+    create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
+    CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
+    CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
+);
+
+-- Trigger function to validate combination of org_unit and fund_code
+
+CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
+RETURNS TRIGGER AS $$
+--
+DECLARE
+--
+dummy int := 0;
+--
+BEGIN
+    SELECT
+        1
+    INTO
+        dummy
+    FROM
+        acq.fund
+    WHERE
+        org = NEW.org
+        AND code = NEW.fund_code
+        LIMIT 1;
+    --
+    IF dummy = 1 then
+        RETURN NEW;
+    ELSE
+        RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER acq_fund_alloc_percent_val_trig
+    BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
+    FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
+
+-- To do: trigger to verify that percentages don't add up to more than 100
+
+CREATE OR REPLACE FUNCTION acq.fap_limit_100()
+RETURNS TRIGGER AS $$
+DECLARE
+--
+total_percent numeric;
+--
+BEGIN
+    SELECT
+        sum( percent )
+    INTO
+        total_percent
+    FROM
+        acq.fund_allocation_percent AS fap
+    WHERE
+        fap.funding_source = NEW.funding_source;
+    --
+    IF total_percent > 100 THEN
+        RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
+            NEW.funding_source;
+    ELSE
+        RETURN NEW;
+    END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER acqfap_limit_100_trig
+    AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
+    FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
+
+-- Populate new table from acq.fund_allocation
+
+INSERT INTO acq.fund_allocation_percent
+(
+    funding_source,
+    org,
+    fund_code,
+    percent,
+    allocator,
+    note,
+    create_time
+)
+    SELECT
+        fa.funding_source,
+        fund.org,
+        fund.code,
+        fa.percent,
+        fa.allocator,
+        fa.note,
+        fa.create_time
+    FROM
+        acq.fund_allocation AS fa
+            INNER JOIN acq.fund AS fund
+                ON ( fa.fund = fund.id )
+    WHERE
+        fa.percent is not null
+    ORDER BY
+        fund.org,
+
+-- Temporary function to convert percentages to amounts in acq.fund_allocation
+
+-- Algorithm to apply to each funding source:
+
+-- 1. Add up the credits.
+-- 2. Add up the percentages.
+-- 3. Multiply the sum of the percentages timies the sum of the credits.  Drop any
+--    fractional cents from the result.  This is the total amount to be allocated.
+-- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
+--    fractional cents to get a preliminary amount.
+-- 5. Add up the preliminary amounts for all the allocations.
+-- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
+--    number of residual cents (resulting from having dropped fractional cents) that
+--    must be distributed across the funds in order to make the total of the amounts
+--    match the total allocation.
+-- 7. Make a second pass through the allocations, in decreasing order of the fractional
+--    cents that were dropped from their amounts in step 4.  Add one cent to the amount
+--    for each successive fund, until all the residual cents have been exhausted.
+
+-- Result: the sum of the individual allocations now equals the total to be allocated,
+-- to the penny.  The individual amounts match the percentages as closely as possible,
+-- given the constraint that the total must match.
+
+CREATE OR REPLACE FUNCTION acq.apply_percents()
+RETURNS VOID AS $$
+declare
+--
+tot              RECORD;
+fund             RECORD;
+tot_cents        INTEGER;
+src              INTEGER;
+id               INTEGER[];
+curr_id          INTEGER;
+pennies          NUMERIC[];
+curr_amount      NUMERIC;
+i                INTEGER;
+total_of_floors  INTEGER;
+total_percent    NUMERIC;
+total_allocation INTEGER;
+residue          INTEGER;
+--
+begin
+	RAISE NOTICE 'Applying percents';
+	FOR tot IN
+		SELECT
+			fsrc.funding_source,
+			sum( fsrc.amount ) AS total
+		FROM
+			acq.funding_source_credit AS fsrc
+		WHERE fsrc.funding_source IN
+			( SELECT DISTINCT fa.funding_source
+			  FROM acq.fund_allocation AS fa
+			  WHERE fa.percent IS NOT NULL )
+		GROUP BY
+			fsrc.funding_source
+	LOOP
+		tot_cents = floor( tot.total * 100 );
+		src = tot.funding_source;
+		RAISE NOTICE 'Funding source % total %',
+			src, tot_cents;
+		i := 0;
+		total_of_floors := 0;
+		total_percent := 0;
+		--
+		FOR fund in
+			SELECT
+				fa.id,
+				fa.percent,
+				floor( fa.percent * tot_cents / 100 ) as floor_pennies
+			FROM
+				acq.fund_allocation AS fa
+			WHERE
+				fa.funding_source = src
+				AND fa.percent IS NOT NULL
+			ORDER BY
+				mod( fa.percent * tot_cents / 100, 1 ),
+				fa.fund,
+				fa.id
+		LOOP
+			RAISE NOTICE '   %: %',
+				fund.id,
+				fund.floor_pennies;
+			i := i + 1;
+			id[i] = fund.id;
+			pennies[i] = fund.floor_pennies;
+			total_percent := total_percent + fund.percent;
+			total_of_floors := total_of_floors + pennies[i];
+		END LOOP;
+		total_allocation := floor( total_percent * tot_cents /100 );
+		RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
+		residue := total_allocation - total_of_floors;
+		RAISE NOTICE 'Residue: %', residue;
+		--
+		-- Post the calculated amounts, revising as needed to
+		-- distribute the rounding error
+		--
+		WHILE i > 0 LOOP
+			IF residue > 0 THEN
+				pennies[i] = pennies[i] + 1;
+				residue := residue - 1;
+			END IF;
+			--
+			-- Post amount
+			--
+			curr_id     := id[i];
+			curr_amount := trunc( pennies[i] / 100, 2 );
+			--
+			UPDATE
+				acq.fund_allocation AS fa
+			SET
+				amount = curr_amount,
+				percent = NULL
+			WHERE
+				fa.id = curr_id;
+			--
+			RAISE NOTICE '   ID % and amount %',
+				curr_id,
+				curr_amount;
+			i = i - 1;
+		END LOOP;
+	END LOOP;
+end;
+$$ LANGUAGE 'plpgsql';
+
+-- Run the temporary function
+
+select * from acq.apply_percents();
+
+-- Drop the temporary function now that we're done with it
+
+drop function acq.apply_percents();
+
+COMMIT;



More information about the open-ils-commits mailing list