1 DROP SCHEMA acq CASCADE;
11 CREATE TABLE acq.currency_type (
12 code TEXT PRIMARY KEY,
16 -- Use the ISO 4217 abbreviations for currency codes
17 INSERT INTO acq.currency_type (code, label) VALUES ('USD','US Dollars');
18 INSERT INTO acq.currency_type (code, label) VALUES ('CAN','Canadian Dollars');
19 INSERT INTO acq.currency_type (code, label) VALUES ('EUR','Euros');
21 CREATE TABLE acq.exchange_rate (
22 id SERIAL PRIMARY KEY,
23 from_currency TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
24 to_currency TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
25 ratio NUMERIC NOT NULL,
26 CONSTRAINT exchange_rate_from_to_once UNIQUE (from_currency,to_currency)
29 INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','CAN',1.2);
30 INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','EUR',0.5);
32 CREATE TABLE acq.provider (
33 id SERIAL PRIMARY KEY,
35 owner INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
36 currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
40 CONSTRAINT provider_name_once_per_owner UNIQUE (name,owner),
41 CONSTRAINT code_once_per_owner UNIQUE (code, owner)
44 CREATE TABLE acq.provider_holding_subfield_map (
45 id SERIAL PRIMARY KEY,
46 provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
47 name TEXT NOT NULL, -- barcode, price, etc
48 subfield TEXT NOT NULL,
49 CONSTRAINT name_once_per_provider UNIQUE (provider,name)
52 CREATE TABLE acq.provider_address (
53 id SERIAL PRIMARY KEY,
54 valid BOOL NOT NULL DEFAULT TRUE,
56 provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
57 street1 TEXT NOT NULL,
62 country TEXT NOT NULL,
63 post_code TEXT NOT NULL
66 CREATE TABLE acq.provider_contact (
67 id SERIAL PRIMARY KEY,
68 provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
70 role TEXT, -- free-form.. e.g. "our sales guy"
75 CREATE TABLE acq.provider_contact_address (
76 id SERIAL PRIMARY KEY,
77 valid BOOL NOT NULL DEFAULT TRUE,
79 contact INT NOT NULL REFERENCES acq.provider_contact (id) DEFERRABLE INITIALLY DEFERRED,
80 street1 TEXT NOT NULL,
85 country TEXT NOT NULL,
86 post_code TEXT NOT NULL
90 CREATE TABLE acq.funding_source (
91 id SERIAL PRIMARY KEY,
93 owner INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
94 currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
96 CONSTRAINT funding_source_name_once_per_owner UNIQUE (name,owner)
99 CREATE TABLE acq.funding_source_credit (
100 id SERIAL PRIMARY KEY,
101 funding_source INT NOT NULL REFERENCES acq.funding_source (id) DEFERRABLE INITIALLY DEFERRED,
102 amount NUMERIC NOT NULL,
104 deadline_date TIMESTAMPTZ,
105 effective_date TIMESTAMPTZ NOT NULL default now()
108 CREATE VIEW acq.ordered_funding_source_credit AS
110 CASE WHEN deadline_date IS NULL THEN
114 END AS sort_priority,
115 CASE WHEN deadline_date IS NULL THEN
125 acq.funding_source_credit;
127 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
129 * Copyright (C) 2009 Georgia Public Library Service
130 * Scott McKellar <scott@gmail.com>
132 * The acq.ordered_funding_source_credit view is a prioritized
133 * ordering of funding source credits. When ordered by the first
134 * three columns, this view defines the order in which the various
135 * credits are to be tapped for spending, subject to the allocations
136 * in the acq.fund_allocation table.
138 * The first column reflects the principle that we should spend
139 * money with deadlines before spending money without deadlines.
141 * The second column reflects the principle that we should spend the
142 * oldest money first. For money with deadlines, that means that we
143 * spend first from the credit with the earliest deadline. For
144 * money without deadlines, we spend first from the credit with the
145 * earliest effective date.
147 * The third column is a tie breaker to ensure a consistent
152 * This program is free software; you can redistribute it and/or
153 * modify it under the terms of the GNU General Public License
154 * as published by the Free Software Foundation; either version 2
155 * of the License, or (at your option) any later version.
157 * This program is distributed in the hope that it will be useful,
158 * but WITHOUT ANY WARRANTY; without even the implied warranty of
159 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
160 * GNU General Public License for more details.
164 CREATE TABLE acq.fund (
165 id SERIAL PRIMARY KEY,
166 org INT NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
168 year INT NOT NULL DEFAULT EXTRACT( YEAR FROM NOW() ),
169 currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
171 rollover BOOL NOT NULL DEFAULT FALSE,
172 propagate BOOL NOT NULL DEFAULT TRUE,
173 CONSTRAINT name_once_per_org_year UNIQUE (org,name,year),
174 CONSTRAINT code_once_per_org_year UNIQUE (org, code, year),
175 CONSTRAINT acq_fund_rollover_implies_propagate CHECK ( propagate OR NOT rollover )
178 CREATE TABLE acq.fund_debit (
179 id SERIAL PRIMARY KEY,
180 fund INT NOT NULL REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED,
181 origin_amount NUMERIC NOT NULL, -- pre-exchange-rate amount
182 origin_currency_type TEXT NOT NULL REFERENCES acq.currency_type (code) DEFERRABLE INITIALLY DEFERRED,
183 amount NUMERIC NOT NULL,
184 encumbrance BOOL NOT NULL DEFAULT TRUE,
185 debit_type TEXT NOT NULL,
186 xfer_destination INT REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED,
187 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
190 CREATE TABLE acq.fund_allocation (
191 id SERIAL PRIMARY KEY,
192 funding_source INT NOT NULL REFERENCES acq.funding_source (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
193 fund INT NOT NULL REFERENCES acq.fund (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
194 amount NUMERIC NOT NULL,
195 allocator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
197 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
199 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
201 CREATE TABLE acq.picklist (
202 id SERIAL PRIMARY KEY,
203 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
204 creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
205 editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
206 org_unit INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
208 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
209 edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
210 CONSTRAINT name_once_per_owner UNIQUE (name,owner)
212 CREATE INDEX acq_picklist_owner_idx ON acq.picklist ( owner );
213 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
214 CREATE INDEX acq_picklist_editor_idx ON acq.picklist ( editor );
216 CREATE TABLE acq.purchase_order (
217 id SERIAL PRIMARY KEY,
218 owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
219 creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
220 editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
221 ordering_agency INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
222 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
223 edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
224 provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
225 state TEXT NOT NULL DEFAULT 'new',
226 order_date TIMESTAMP WITH TIME ZONE,
229 CREATE INDEX po_owner_idx ON acq.purchase_order (owner);
230 CREATE INDEX po_provider_idx ON acq.purchase_order (provider);
231 CREATE INDEX po_state_idx ON acq.purchase_order (state);
232 CREATE INDEX po_creator_idx ON acq.purchase_order ( creator );
233 CREATE INDEX po_editor_idx ON acq.purchase_order ( editor );
234 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
236 -- The name should default to the id, as text. We can't reference a column
237 -- in a DEFAULT clause, so we use a trigger:
239 CREATE OR REPLACE FUNCTION acq.purchase_order_name_default () RETURNS TRIGGER
242 IF NEW.name IS NULL THEN
243 NEW.name := NEW.id::TEXT;
250 CREATE TRIGGER po_name_default_trg
251 BEFORE INSERT OR UPDATE ON acq.purchase_order
252 FOR EACH ROW EXECUTE PROCEDURE acq.purchase_order_name_default ();
254 -- The order name should be unique for a given ordering agency on a given order date
255 -- (truncated to midnight), but only where the order_date is not NULL. Conceptually
256 -- this rule requires a check constraint with a subquery. However you can't have a
257 -- subquery in a CHECK constraint, so we fake it with a trigger.
259 CREATE OR REPLACE FUNCTION acq.po_org_name_date_unique () RETURNS TRIGGER
265 -- If order_date is not null, then make sure we don't have a collision
266 -- on order_date (truncated to day), org, and name
268 IF NEW.order_date IS NULL THEN
272 -- In the WHERE clause, we compare the order_dates without regard to time of day.
273 -- We use a pair of inequalities instead of comparing truncated dates so that the
274 -- query can do an indexed range scan.
276 SELECT 1 INTO collision
277 FROM acq.purchase_order
279 ordering_agency = NEW.ordering_agency
281 AND order_date >= date_trunc( 'day', NEW.order_date )
282 AND order_date < date_trunc( 'day', NEW.order_date ) + '1 day'::INTERVAL
285 IF collision IS NULL THEN
286 -- okay, no collision
289 -- collision; nip it in the bud
290 RAISE EXCEPTION 'Colliding purchase orders: ordering_agency %, date %, name ''%''',
291 NEW.ordering_agency, NEW.order_date, NEW.name;
296 CREATE TRIGGER po_org_name_date_unique_trg
297 BEFORE INSERT OR UPDATE ON acq.purchase_order
298 FOR EACH ROW EXECUTE PROCEDURE acq.po_org_name_date_unique ();
300 CREATE TABLE acq.po_note (
301 id SERIAL PRIMARY KEY,
302 purchase_order INT NOT NULL REFERENCES acq.purchase_order (id) DEFERRABLE INITIALLY DEFERRED,
303 creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
304 editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
305 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
306 edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
309 CREATE INDEX po_note_po_idx ON acq.po_note (purchase_order);
310 CREATE INDEX acq_po_note_creator_idx ON acq.po_note ( creator );
311 CREATE INDEX acq_po_note_editor_idx ON acq.po_note ( editor );
313 CREATE TABLE acq.lineitem (
314 id BIGSERIAL PRIMARY KEY,
315 creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
316 editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
317 selector INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
318 provider INT REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
319 purchase_order INT REFERENCES acq.purchase_order (id) DEFERRABLE INITIALLY DEFERRED,
320 picklist INT REFERENCES acq.picklist (id) DEFERRABLE INITIALLY DEFERRED,
321 expected_recv_time TIMESTAMP WITH TIME ZONE,
322 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
323 edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
325 eg_bib_id INT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
327 item_count INT NOT NULL DEFAULT 0,
328 state TEXT NOT NULL DEFAULT 'new',
329 CONSTRAINT picklist_or_po CHECK (picklist IS NOT NULL OR purchase_order IS NOT NULL)
331 CREATE INDEX li_po_idx ON acq.lineitem (purchase_order);
332 CREATE INDEX li_pl_idx ON acq.lineitem (picklist);
333 CREATE INDEX li_creator_idx ON acq.lineitem ( creator );
334 CREATE INDEX li_editor_idx ON acq.lineitem ( editor );
335 CREATE INDEX li_selector_idx ON acq.lineitem ( selector );
337 CREATE TABLE acq.lineitem_note (
338 id SERIAL PRIMARY KEY,
339 lineitem INT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED,
340 creator INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
341 editor INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
342 create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
343 edit_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
346 CREATE INDEX li_note_li_idx ON acq.lineitem_note (lineitem);
347 CREATE INDEX li_note_creator_idx ON acq.lineitem_note ( creator );
348 CREATE INDEX li_note_editor_idx ON acq.lineitem_note ( editor );
350 CREATE TABLE acq.lineitem_detail (
351 id BIGSERIAL PRIMARY KEY,
352 lineitem INT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED,
353 fund INT REFERENCES acq.fund (id) DEFERRABLE INITIALLY DEFERRED,
354 fund_debit INT REFERENCES acq.fund_debit (id) DEFERRABLE INITIALLY DEFERRED,
355 eg_copy_id BIGINT REFERENCES asset.copy (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
359 collection_code TEXT,
360 circ_modifier TEXT REFERENCES config.circ_modifier (code) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
361 owning_lib INT REFERENCES actor.org_unit (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
362 location INT REFERENCES asset.copy_location (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
363 recv_time TIMESTAMP WITH TIME ZONE
366 CREATE INDEX li_detail_li_idx ON acq.lineitem_detail (lineitem);
368 CREATE TABLE acq.lineitem_attr_definition (
369 id BIGSERIAL PRIMARY KEY,
371 description TEXT NOT NULL,
372 remove TEXT NOT NULL DEFAULT '',
373 ident BOOL NOT NULL DEFAULT FALSE
376 CREATE TABLE acq.lineitem_marc_attr_definition (
377 id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
379 ) INHERITS (acq.lineitem_attr_definition);
381 CREATE TABLE acq.lineitem_provider_attr_definition (
382 id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
384 provider INT NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED
385 ) INHERITS (acq.lineitem_attr_definition);
387 CREATE TABLE acq.lineitem_generated_attr_definition (
388 id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
390 ) INHERITS (acq.lineitem_attr_definition);
392 CREATE TABLE acq.lineitem_usr_attr_definition (
393 id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
394 usr INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED
395 ) INHERITS (acq.lineitem_attr_definition);
396 CREATE INDEX li_usr_attr_def_usr_idx ON acq.lineitem_usr_attr_definition ( usr );
398 CREATE TABLE acq.lineitem_local_attr_definition (
399 id BIGINT PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq')
400 ) INHERITS (acq.lineitem_attr_definition);
402 CREATE TABLE acq.lineitem_attr (
403 id BIGSERIAL PRIMARY KEY,
404 definition BIGINT NOT NULL,
405 lineitem BIGINT NOT NULL REFERENCES acq.lineitem (id) DEFERRABLE INITIALLY DEFERRED,
406 attr_type TEXT NOT NULL,
407 attr_name TEXT NOT NULL,
408 attr_value TEXT NOT NULL
411 CREATE INDEX li_attr_li_idx ON acq.lineitem_attr (lineitem);
412 CREATE INDEX li_attr_value_idx ON acq.lineitem_attr (attr_value);
413 CREATE INDEX li_attr_definition_idx ON acq.lineitem_attr (definition);
419 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('title','Title of work','//*[@tag="245"]/*[contains("abcmnopr",@code)]');
420 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('author','Author of work','//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]');
421 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('language','Language of work','//*[@tag="240"]/*[@code="l"][1]');
422 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pagination','Pagination','//*[@tag="300"]/*[@code="a"][1]');
423 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('isbn','ISBN','//*[@tag="020"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
424 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('issn','ISSN','//*[@tag="022"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
425 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('price','Price','//*[@tag="020" or @tag="022"]/*[@code="c"][1]');
426 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('identifier','Identifier','//*[@tag="001"]');
427 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('publisher','Publisher','//*[@tag="260"]/*[@code="b"][1]');
428 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pubdate','Publication Date','//*[@tag="260"]/*[@code="c"][1]');
429 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('edition','Edition','//*[@tag="250"]/*[@code="a"][1]');
431 INSERT INTO acq.lineitem_local_attr_definition ( code, description ) VALUES ('estimated_price', 'Estimated Price');
434 CREATE TABLE acq.distribution_formula (
435 id SERIAL PRIMARY KEY,
437 REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
439 skip_count INT NOT NULL DEFAULT 0,
440 CONSTRAINT acqdf_name_once_per_owner UNIQUE (name, owner)
443 CREATE TABLE acq.distribution_formula_entry (
444 id SERIAL PRIMARY KEY,
445 formula INTEGER NOT NULL REFERENCES acq.distribution_formula(id)
447 DEFERRABLE INITIALLY DEFERRED,
448 position INTEGER NOT NULL,
449 item_count INTEGER NOT NULL,
450 owning_lib INTEGER REFERENCES actor.org_unit(id)
451 DEFERRABLE INITIALLY DEFERRED,
452 location INTEGER REFERENCES asset.copy_location(id),
453 CONSTRAINT acqdfe_lib_once_per_formula UNIQUE( formula, position ),
454 CONSTRAINT acqdfe_must_be_somewhere
455 CHECK( owning_lib IS NOT NULL OR location IS NOT NULL )
458 CREATE TABLE acq.fund_tag (
459 id SERIAL PRIMARY KEY,
461 REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
463 CONSTRAINT acqft_tag_once_per_owner UNIQUE (name, owner)
466 CREATE TABLE acq.fund_tag_map (
467 id SERIAL PRIMARY KEY,
468 fund INTEGER NOT NULL REFERENCES acq.fund(id)
469 DEFERRABLE INITIALLY DEFERRED,
470 tag INTEGER REFERENCES acq.fund_tag(id)
472 DEFERRABLE INITIALLY DEFERRED,
473 CONSTRAINT acqftm_fund_once_per_tag UNIQUE( fund, tag )
476 CREATE TABLE acq.fund_transfer (
477 id SERIAL PRIMARY KEY,
478 src_fund INT NOT NULL REFERENCES acq.fund( id )
479 DEFERRABLE INITIALLY DEFERRED,
480 src_amount NUMERIC NOT NULL,
481 dest_fund INT NOT NULL REFERENCES acq.fund( id )
482 DEFERRABLE INITIALLY DEFERRED,
483 dest_amount NUMERIC NOT NULL,
484 transfer_time TIMESTAMPTZ NOT NULL DEFAULT now(),
485 transfer_user INT NOT NULL REFERENCES actor.usr( id )
486 DEFERRABLE INITIALLY DEFERRED,
488 funding_source_credit INT NOT NULL REFERENCES acq.funding_source_credit( id )
489 DEFERRABLE INITIALLY DEFERRED
492 CREATE INDEX acqftr_usr_idx
493 ON acq.fund_transfer( transfer_user );
495 COMMENT ON TABLE acq.fund_transfer IS $$
497 * Copyright (C) 2009 Georgia Public Library Service
498 * Scott McKellar <scott@esilibrary.com>
502 * Each row represents the transfer of money from a source fund
503 * to a destination fund. There should be corresponding entries
504 * in acq.fund_allocation. The purpose of acq.fund_transfer is
505 * to record how much money moved from which fund to which other
508 * The presence of two amount fields, rather than one, reflects
509 * the possibility that the two funds are denominated in different
510 * currencies. If they use the same currency type, the two
511 * amounts should be the same.
515 * This program is free software; you can redistribute it and/or
516 * modify it under the terms of the GNU General Public License
517 * as published by the Free Software Foundation; either version 2
518 * of the License, or (at your option) any later version.
520 * This program is distributed in the hope that it will be useful,
521 * but WITHOUT ANY WARRANTY; without even the implied warranty of
522 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
523 * GNU General Public License for more details.
527 CREATE TABLE acq.fiscal_calendar (
528 id SERIAL PRIMARY KEY,
532 -- Create a default calendar (though we don't specify its contents).
533 -- Create a foreign key in actor.org_unit, initially pointing to
534 -- the default calendar.
536 INSERT INTO acq.fiscal_calendar (
543 ALTER TABLE actor.org_unit
544 ADD COLUMN fiscal_calendar INT NOT NULL
545 REFERENCES acq.fiscal_calendar( id )
546 DEFERRABLE INITIALLY DEFERRED
549 CREATE TABLE acq.fiscal_year (
550 id SERIAL PRIMARY KEY,
551 calendar INT NOT NULL
552 REFERENCES acq.fiscal_calendar
554 DEFERRABLE INITIALLY DEFERRED,
556 year_begin TIMESTAMPTZ NOT NULL,
557 year_end TIMESTAMPTZ NOT NULL,
558 CONSTRAINT acq_fy_logical_key UNIQUE ( calendar, year ),
559 CONSTRAINT acq_fy_physical_key UNIQUE ( calendar, year_begin )
564 CREATE TYPE acq.flat_lineitem_holding_subfield AS (lineitem int, holding int, subfield text, data text);
565 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
568 lida acq.flat_lineitem_holding_subfield%ROWTYPE;
571 SELECT COUNT(*) INTO counter
576 '//*[@tag="' || tag || '"]',
578 ) as t(i int,c text);
580 FOR i IN 1 .. counter LOOP
583 FROM ( SELECT id,i,t,v
588 '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
589 '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
591 ) as t(id int,t text,v text)
602 CREATE TYPE acq.flat_lineitem_detail AS (lineitem int, holding int, attr text, data text);
603 CREATE OR REPLACE FUNCTION acq.extract_provider_holding_data ( lineitem_i int ) RETURNS SETOF acq.flat_lineitem_detail AS $$
607 lida acq.flat_lineitem_detail%ROWTYPE;
609 SELECT provider INTO prov_i FROM acq.lineitem WHERE id = lineitem_i;
610 IF NOT FOUND THEN RETURN; END IF;
612 SELECT holding_tag INTO tag_t FROM acq.provider WHERE id = prov_i;
613 IF NOT FOUND OR tag_t IS NULL THEN RETURN; END IF;
620 FROM acq.extract_holding_attr_table( lineitem_i, tag_t ) h
621 JOIN acq.provider_holding_subfield_map a USING (subfield)
622 WHERE a.provider = prov_i
631 -- select * from acq.extract_provider_holding_data(699);
633 CREATE OR REPLACE FUNCTION public.extract_acq_marc_field ( BIGINT, TEXT, TEXT) RETURNS TEXT AS $$
634 SELECT public.extract_marc_field('acq.lineitem', $1, $2, $3);
638 CREATE OR REPLACE FUNCTION public.extract_bib_marc_field ( BIGINT, TEXT ) RETURNS TEXT AS $$
639 SELECT public.extract_marc_field('biblio.record_entry', $1, $2);
642 CREATE OR REPLACE FUNCTION public.extract_authority_marc_field ( BIGINT, TEXT ) RETURNS TEXT AS $$
643 SELECT public.extract_marc_field('authority.record_entry', $1, $2);
647 -- INSERT INTO acq.lineitem_provider_attr_definition ( provider, code, description, xpath ) VALUES (1,'price','Price','//*[@tag="020" or @tag="022"]/*[@code="a"][1]');
650 Suggested vendor fields:
658 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $$
666 FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
668 SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
670 IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
671 IF (atype = 'lineitem_provider_attr_definition') THEN
672 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
673 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
676 IF (atype = 'lineitem_provider_attr_definition') THEN
677 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
678 ELSIF (atype = 'lineitem_marc_attr_definition') THEN
679 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
680 ELSIF (atype = 'lineitem_generated_attr_definition') THEN
681 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
684 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
686 IF (value IS NOT NULL AND value <> '') THEN
687 INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
688 VALUES (NEW.id, adef.id, atype, adef.code, value);
699 CREATE OR REPLACE FUNCTION public.cleanup_acq_marc ( ) RETURNS TRIGGER AS $$
701 IF TG_OP = 'UPDATE' THEN
702 DELETE FROM acq.lineitem_attr
703 WHERE lineitem = OLD.id AND attr_type IN ('lineitem_provider_attr_definition', 'lineitem_marc_attr_definition','lineitem_generated_attr_definition');
706 DELETE FROM acq.lineitem_attr WHERE lineitem = OLD.id;
712 CREATE TRIGGER cleanup_lineitem_trigger
713 BEFORE UPDATE OR DELETE ON acq.lineitem
714 FOR EACH ROW EXECUTE PROCEDURE public.cleanup_acq_marc();
716 CREATE TRIGGER ingest_lineitem_trigger
717 AFTER INSERT OR UPDATE ON acq.lineitem
718 FOR EACH ROW EXECUTE PROCEDURE public.ingest_acq_marc();
720 CREATE OR REPLACE FUNCTION acq.exchange_ratio ( from_ex TEXT, to_ex TEXT ) RETURNS NUMERIC AS $$
724 IF from_ex = to_ex THEN
728 SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = from_ex AND to_currency = to_ex;
733 SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = to_ex AND to_currency = from_ex;
744 CREATE OR REPLACE FUNCTION acq.exchange_ratio ( TEXT, TEXT, NUMERIC ) RETURNS NUMERIC AS $$
745 SELECT $3 * acq.exchange_ratio($1, $2);
748 CREATE OR REPLACE FUNCTION acq.find_bad_fy()
750 Examine the acq.fiscal_year table, comparing successive years.
751 Report any inconsistencies, i.e. years that overlap, have gaps
752 between them, or are out of sequence.
754 RETURNS SETOF RECORD AS $$
778 ELSIF curr_year.calendar = prev_year.calendar THEN
779 IF curr_year.year_begin > prev_year.year_end THEN
780 -- This ugly kludge works around the fact that older
781 -- versions of PostgreSQL don't support RETURN QUERY SELECT
782 FOR return_rec IN SELECT
785 'Gap between fiscal years'::TEXT
787 RETURN NEXT return_rec;
789 ELSIF curr_year.year_begin < prev_year.year_end THEN
790 FOR return_rec IN SELECT
793 'Overlapping fiscal years'::TEXT
795 RETURN NEXT return_rec;
797 ELSIF curr_year.year < prev_year.year THEN
798 FOR return_rec IN SELECT
801 'Fiscal years out of order'::TEXT
803 RETURN NEXT return_rec;
808 prev_year := curr_year;
815 CREATE OR REPLACE VIEW acq.funding_source_credit_total AS
816 SELECT funding_source,
817 SUM(amount) AS amount
818 FROM acq.funding_source_credit
821 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
822 SELECT funding_source,
823 SUM(a.amount)::NUMERIC(100,2) AS amount
824 FROM acq.fund_allocation a
827 CREATE OR REPLACE VIEW acq.funding_source_balance AS
828 SELECT COALESCE(c.funding_source, a.funding_source) AS funding_source,
829 SUM(COALESCE(c.amount,0.0) - COALESCE(a.amount,0.0))::NUMERIC(100,2) AS amount
830 FROM acq.funding_source_credit_total c
831 FULL JOIN acq.funding_source_allocation_total a USING (funding_source)
834 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
836 SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
837 FROM acq.fund_allocation a
838 JOIN acq.fund f ON (a.fund = f.id)
839 JOIN acq.funding_source s ON (a.funding_source = s.id)
842 CREATE OR REPLACE VIEW acq.fund_debit_total AS
845 SUM(amount) AS amount
849 CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS
851 SUM(amount) AS amount
852 FROM acq.fund_debit_total
853 WHERE encumbrance IS TRUE
856 CREATE OR REPLACE VIEW acq.fund_spent_total AS
858 SUM(amount) AS amount
859 FROM acq.fund_debit_total
860 WHERE encumbrance IS FALSE
863 CREATE OR REPLACE VIEW acq.fund_combined_balance AS
865 c.amount - COALESCE(d.amount,0.0) AS amount
866 FROM acq.fund_allocation_total c
867 LEFT JOIN acq.fund_debit_total d USING (fund);
869 CREATE OR REPLACE VIEW acq.fund_spent_balance AS
871 c.amount - COALESCE(d.amount,0.0) AS amount
872 FROM acq.fund_allocation_total c
873 LEFT JOIN acq.fund_spent_total d USING (fund);