2 * Copyright (C) 2004-2008 Georgia Public Library Service
3 * Copyright (C) 2007-2008 Equinox Software, Inc.
4 * Mike Rylander <miker@esilibrary.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
18 DROP SCHEMA IF EXISTS asset CASCADE;
24 CREATE TABLE asset.copy_location (
25 id SERIAL PRIMARY KEY,
27 owning_lib INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
28 holdable BOOL NOT NULL DEFAULT TRUE,
29 hold_verify BOOL NOT NULL DEFAULT FALSE,
30 opac_visible BOOL NOT NULL DEFAULT TRUE,
31 circulate BOOL NOT NULL DEFAULT TRUE,
34 checkin_alert BOOL NOT NULL DEFAULT FALSE,
35 deleted BOOL NOT NULL DEFAULT FALSE,
38 CREATE UNIQUE INDEX acl_name_once_per_lib ON asset.copy_location (name, owning_lib) WHERE deleted = FALSE OR deleted IS FALSE;
40 CREATE TABLE asset.copy_location_order
42 id SERIAL PRIMARY KEY,
44 REFERENCES asset.copy_location
46 DEFERRABLE INITIALLY DEFERRED,
48 REFERENCES actor.org_unit
50 DEFERRABLE INITIALLY DEFERRED,
51 position INT NOT NULL DEFAULT 0,
52 CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
55 CREATE TABLE asset.copy_location_group (
56 id SERIAL PRIMARY KEY,
57 name TEXT NOT NULL, -- i18n
58 owner INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
59 pos INT NOT NULL DEFAULT 0,
60 top BOOL NOT NULL DEFAULT FALSE,
61 opac_visible BOOL NOT NULL DEFAULT TRUE,
62 CONSTRAINT lgroup_once_per_owner UNIQUE (owner,name)
65 CREATE TABLE asset.copy_location_group_map (
66 id SERIAL PRIMARY KEY,
67 location INT NOT NULL REFERENCES asset.copy_location (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
68 lgroup INT NOT NULL REFERENCES asset.copy_location_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
69 CONSTRAINT lgroup_once_per_group UNIQUE (lgroup,location)
72 CREATE TABLE asset.copy (
73 id BIGSERIAL PRIMARY KEY,
74 circ_lib INT NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
75 creator BIGINT NOT NULL,
76 call_number BIGINT NOT NULL,
77 editor BIGINT NOT NULL,
78 create_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
79 edit_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
81 status INT NOT NULL DEFAULT 0 REFERENCES config.copy_status (id) DEFERRABLE INITIALLY DEFERRED,
82 location INT NOT NULL DEFAULT 1 REFERENCES asset.copy_location (id) DEFERRABLE INITIALLY DEFERRED,
83 loan_duration INT NOT NULL CHECK ( loan_duration IN (1,2,3) ),
84 fine_level INT NOT NULL CHECK ( fine_level IN (1,2,3) ),
86 circulate BOOL NOT NULL DEFAULT TRUE,
87 deposit BOOL NOT NULL DEFAULT FALSE,
88 ref BOOL NOT NULL DEFAULT FALSE,
89 holdable BOOL NOT NULL DEFAULT TRUE,
90 deposit_amount NUMERIC(6,2) NOT NULL DEFAULT 0.00,
92 barcode TEXT NOT NULL,
98 opac_visible BOOL NOT NULL DEFAULT TRUE,
99 deleted BOOL NOT NULL DEFAULT FALSE,
102 status_changed_time TIMESTAMP WITH TIME ZONE,
103 active_date TIMESTAMP WITH TIME ZONE,
104 mint_condition BOOL NOT NULL DEFAULT TRUE,
107 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
108 CREATE INDEX cp_cn_idx ON asset.copy (call_number);
109 CREATE INDEX cp_avail_cn_idx ON asset.copy (call_number);
110 CREATE INDEX cp_creator_idx ON asset.copy ( creator );
111 CREATE INDEX cp_editor_idx ON asset.copy ( editor );
112 CREATE INDEX cp_create_date ON asset.copy (create_date);
113 CREATE INDEX cp_available_by_circ_lib_idx ON asset.copy (circ_lib) WHERE status IN (0,7);
114 CREATE RULE protect_copy_delete AS ON DELETE TO asset.copy DO INSTEAD UPDATE asset.copy SET deleted = TRUE WHERE OLD.id = asset.copy.id;
116 CREATE TABLE asset.copy_part_map (
117 id SERIAL PRIMARY KEY,
118 target_copy BIGINT NOT NULL, -- points o asset.copy
119 part INT NOT NULL REFERENCES biblio.monograph_part (id) ON DELETE CASCADE
121 CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part);
123 CREATE TABLE asset.latest_inventory (
124 id SERIAL PRIMARY KEY,
125 inventory_workstation INTEGER REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED,
126 inventory_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
129 CREATE INDEX latest_inventory_copy_idx ON asset.latest_inventory (copy);
131 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
132 RETURNS TRIGGER AS $$
134 IF NEW.status <> OLD.status AND NOT (NEW.status = 0 AND OLD.status = 7) THEN
135 NEW.status_changed_time := now();
136 IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
137 NEW.active_date := now();
144 -- Need to check on initial create. Fast adds, manual edit of status at create, etc.
145 CREATE OR REPLACE FUNCTION asset.acp_created()
146 RETURNS TRIGGER AS $$
148 IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
149 NEW.active_date := now();
151 IF NEW.status_changed_time IS NULL THEN
152 NEW.status_changed_time := now();
158 CREATE TRIGGER acp_status_changed_trig
159 BEFORE UPDATE ON asset.copy
160 FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
162 CREATE TRIGGER acp_created_trig
163 BEFORE INSERT ON asset.copy
164 FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
166 CREATE OR REPLACE FUNCTION asset.acp_location_fixer()
167 RETURNS TRIGGER AS $$
169 new_copy_location INT;
171 IF (TG_OP = 'UPDATE') THEN
172 IF NEW.location = OLD.location AND NEW.call_number = OLD.call_number AND NEW.circ_lib = OLD.circ_lib THEN
176 SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance((SELECT owning_lib FROM asset.call_number WHERE id = NEW.call_number)) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
177 IF new_copy_location IS NULL THEN
178 SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance(NEW.circ_lib) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
180 IF new_copy_location IS NOT NULL THEN
181 NEW.location = new_copy_location;
187 CREATE TRIGGER acp_location_fixer_trig
188 BEFORE INSERT OR UPDATE OF location, call_number, circ_lib ON asset.copy
189 FOR EACH ROW EXECUTE PROCEDURE asset.acp_location_fixer();
191 CREATE TABLE asset.stat_cat_sip_fields (
192 field CHAR(2) PRIMARY KEY,
194 one_only BOOL NOT NULL DEFAULT FALSE
196 COMMENT ON TABLE asset.stat_cat_sip_fields IS $$
197 Asset Statistical Category SIP Fields
199 Contains the list of valid SIP Field identifiers for
200 Statistical Categories.
203 CREATE TABLE asset.stat_cat_entry_transparency_map (
204 id BIGSERIAL PRIMARY KEY,
205 stat_cat INT NOT NULL, -- needs ON DELETE CASCADE
206 stat_cat_entry INT NOT NULL, -- needs ON DELETE CASCADE
207 owning_transparency INT NOT NULL, -- needs ON DELETE CASCADE
208 CONSTRAINT scte_once_per_trans UNIQUE (owning_transparency,stat_cat)
211 CREATE TABLE asset.stat_cat (
212 id SERIAL PRIMARY KEY,
214 opac_visible BOOL NOT NULL DEFAULT FALSE,
216 required BOOL NOT NULL DEFAULT FALSE,
217 sip_field CHAR(2) REFERENCES asset.stat_cat_sip_fields(field) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
219 checkout_archive BOOL NOT NULL DEFAULT FALSE,
220 CONSTRAINT sc_once_per_owner UNIQUE (owner,name)
223 CREATE TABLE asset.stat_cat_entry (
224 id SERIAL PRIMARY KEY,
225 stat_cat INT NOT NULL,
228 CONSTRAINT sce_once_per_owner UNIQUE (stat_cat,owner,value)
231 CREATE TABLE asset.stat_cat_entry_copy_map (
232 id BIGSERIAL PRIMARY KEY,
233 stat_cat INT NOT NULL,
234 stat_cat_entry INT NOT NULL,
235 owning_copy BIGINT NOT NULL,
236 CONSTRAINT sce_once_per_copy UNIQUE (owning_copy,stat_cat)
238 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
240 CREATE FUNCTION asset.stat_cat_check() RETURNS trigger AS $func$
242 sipfield asset.stat_cat_sip_fields%ROWTYPE;
245 IF NEW.sip_field IS NOT NULL THEN
246 SELECT INTO sipfield * FROM asset.stat_cat_sip_fields WHERE field = NEW.sip_field;
247 IF sipfield.one_only THEN
248 SELECT INTO use_count count(id) FROM asset.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
249 IF use_count > 0 THEN
250 RAISE EXCEPTION 'Sip field cannot be used twice';
256 $func$ LANGUAGE PLPGSQL;
258 CREATE TRIGGER asset_stat_cat_sip_update_trigger
259 BEFORE INSERT OR UPDATE ON asset.stat_cat FOR EACH ROW
260 EXECUTE PROCEDURE asset.stat_cat_check();
262 CREATE TABLE asset.copy_note (
263 id BIGSERIAL PRIMARY KEY,
264 owning_copy BIGINT NOT NULL,
265 creator BIGINT NOT NULL,
266 create_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
267 pub BOOL NOT NULL DEFAULT FALSE,
271 CREATE INDEX asset_copy_note_creator_idx ON asset.copy_note ( creator );
272 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
274 CREATE TABLE asset.uri (
275 id SERIAL PRIMARY KEY,
278 use_restriction TEXT,
279 active BOOL NOT NULL DEFAULT TRUE
282 CREATE TABLE asset.call_number_class (
283 id bigserial PRIMARY KEY,
285 normalizer TEXT NOT NULL DEFAULT 'asset.normalize_generic',
286 field TEXT NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
288 COMMENT ON TABLE asset.call_number_class IS $$
289 Defines the call number normalization database functions in the "normalizer"
290 column and the tag/subfield combinations to use to lookup the call number in
291 the "field" column for a given classification scheme. Tag/subfield combinations
292 are delimited by commas.
295 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
299 sortkey := NEW.label_sortkey;
301 IF NEW.label_class IS NULL THEN
302 NEW.label_class := COALESCE(
304 SELECT substring(value from E'\\d+')::integer
305 FROM actor.org_unit_ancestor_setting('cat.default_classification_scheme', NEW.owning_lib)
310 EXECUTE 'SELECT ' || acnc.normalizer || '(' ||
311 quote_literal( NEW.label ) || ')'
312 FROM asset.call_number_class acnc
313 WHERE acnc.id = NEW.label_class
315 NEW.label_sortkey = sortkey;
318 $func$ LANGUAGE PLPGSQL;
320 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
321 # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
322 # thus could probably be considered a derived work, although nothing was
323 # directly copied - but to err on the safe side of providing attribution:
324 # Copyright (C) 2007 LibLime
325 # Copyright (C) 2011 Equinox Software, Inc (Steve Callendar)
326 # Licensed under the GPL v2 or later
331 # Converts the callnumber to uppercase
332 # Strips spaces from start and end of the call number
333 # Converts anything other than letters, digits, and periods into spaces
334 # Collapses multiple spaces into a single underscore
335 my $callnum = uc(shift);
336 $callnum =~ s/^\s//g;
337 $callnum =~ s/\s$//g;
338 # NOTE: this previously used underscores, but this caused sorting issues
339 # for the "before" half of page 0 on CN browse, sorting CNs containing a
340 # decimal before "whole number" CNs
341 $callnum =~ s/[^A-Z0-9_.]/ /g;
342 $callnum =~ s/ {2,}/ /g;
345 $func$ LANGUAGE PLPERLU IMMUTABLE;
347 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
348 # Derived from the Koha C4::ClassSortRoutine::Dewey module
349 # Copyright (C) 2007 LibLime
350 # Licensed under the GPL v2 or later
355 my $init = uc(shift);
359 $init =~ s/^([\p{IsAlpha}]+)/$1 /;
360 my @tokens = split /\.|\s+/, $init;
361 my $digit_group_count = 0;
362 my $first_digit_group_idx;
363 for (my $i = 0; $i <= $#tokens; $i++) {
364 if ($tokens[$i] =~ /^\d+$/) {
365 $digit_group_count++;
366 if ($digit_group_count == 1) {
367 $first_digit_group_idx = $i;
369 if (2 == $digit_group_count) {
370 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
371 $tokens[$i] =~ tr/ /0/;
375 # Pad the first digit_group if there was only one
376 if (1 == $digit_group_count) {
377 $tokens[$first_digit_group_idx] .= '_000000000000000'
379 my $key = join("_", @tokens);
380 $key =~ s/[^\p{IsAlnum}_]//g;
384 $func$ LANGUAGE PLPERLU IMMUTABLE;
387 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
391 # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
392 # The author hopes to upload it to CPAN some day, which would make our lives easier
393 use Library::CallNumber::LC;
395 my $callnum = Library::CallNumber::LC->new(shift);
396 return $callnum->normalize();
398 $func$ LANGUAGE PLPERLU IMMUTABLE;
400 INSERT INTO asset.call_number_class (name, normalizer, field) VALUES
401 ('Generic', 'asset.label_normalizer_generic', '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'),
402 ('Dewey (DDC)', 'asset.label_normalizer_dewey', '080ab,082ab,092abef'),
403 ('Library of Congress (LC)', 'asset.label_normalizer_lc', '050ab,055ab,090abef')
406 CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$
408 NEW.label_sortkey := REGEXP_REPLACE(
409 evergreen.lpad_number_substrings(
410 naco_normalize(NEW.label),
422 CREATE TABLE asset.call_number_prefix (
423 id SERIAL PRIMARY KEY,
424 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
425 label TEXT NOT NULL, -- i18n
428 CREATE TRIGGER prefix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_prefix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
429 CREATE UNIQUE INDEX asset_call_number_prefix_once_per_lib ON asset.call_number_prefix (label, owning_lib);
430 CREATE INDEX asset_call_number_prefix_sortkey_idx ON asset.call_number_prefix (label_sortkey);
432 CREATE TABLE asset.call_number_suffix (
433 id SERIAL PRIMARY KEY,
434 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
435 label TEXT NOT NULL, -- i18n
438 CREATE TRIGGER suffix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_suffix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
439 CREATE UNIQUE INDEX asset_call_number_suffix_once_per_lib ON asset.call_number_suffix (label, owning_lib);
440 CREATE INDEX asset_call_number_suffix_sortkey_idx ON asset.call_number_suffix (label_sortkey);
442 CREATE TABLE asset.call_number (
443 id bigserial PRIMARY KEY,
444 creator BIGINT NOT NULL,
445 create_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
446 editor BIGINT NOT NULL,
447 edit_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
448 record bigint NOT NULL,
449 owning_lib INT NOT NULL,
451 deleted BOOL NOT NULL DEFAULT FALSE,
452 prefix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_prefix(id) DEFERRABLE INITIALLY DEFERRED,
453 suffix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_suffix(id) DEFERRABLE INITIALLY DEFERRED,
454 label_class BIGINT NOT NULL
455 REFERENCES asset.call_number_class(id)
456 DEFERRABLE INITIALLY DEFERRED,
459 CREATE INDEX asset_call_number_record_idx ON asset.call_number (record);
460 CREATE INDEX asset_call_number_creator_idx ON asset.call_number (creator);
461 CREATE INDEX asset_call_number_editor_idx ON asset.call_number (editor);
462 CREATE INDEX asset_call_number_dewey_idx ON asset.call_number (public.call_number_dewey(label));
463 CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib);
464 CREATE INDEX asset_call_number_label_sortkey ON asset.call_number(oils_text_as_bytea(label_sortkey));
465 CREATE UNIQUE INDEX asset_call_number_label_once_per_lib ON asset.call_number (record, owning_lib, label, prefix, suffix) WHERE deleted = FALSE OR deleted IS FALSE;
466 CREATE INDEX asset_call_number_label_sortkey_browse ON asset.call_number(oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib) WHERE deleted IS FALSE OR deleted = FALSE;
467 CREATE RULE protect_cn_delete AS ON DELETE TO asset.call_number DO INSTEAD UPDATE asset.call_number SET deleted = TRUE WHERE OLD.id = asset.call_number.id;
468 CREATE RULE protect_acn_id_neg1 AS ON UPDATE TO asset.call_number WHERE OLD.id = -1 DO INSTEAD NOTHING;
470 CREATE TRIGGER asset_label_sortkey_trigger
471 BEFORE UPDATE OR INSERT ON asset.call_number
472 FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
474 CREATE TABLE asset.uri_call_number_map (
475 id BIGSERIAL PRIMARY KEY,
476 uri INT NOT NULL REFERENCES asset.uri (id),
477 call_number INT NOT NULL REFERENCES asset.call_number (id),
478 CONSTRAINT uri_cn_once UNIQUE (uri,call_number)
480 CREATE INDEX asset_uri_call_number_map_cn_idx ON asset.uri_call_number_map (call_number);
482 CREATE TABLE asset.call_number_note (
483 id BIGSERIAL PRIMARY KEY,
484 call_number BIGINT NOT NULL,
485 creator BIGINT NOT NULL,
486 create_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
487 pub BOOL NOT NULL DEFAULT FALSE,
491 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
493 CREATE TABLE asset.copy_template (
494 id SERIAL PRIMARY KEY,
495 owning_lib INT NOT NULL
496 REFERENCES actor.org_unit (id)
497 DEFERRABLE INITIALLY DEFERRED,
498 creator BIGINT NOT NULL
499 REFERENCES actor.usr (id)
500 DEFERRABLE INITIALLY DEFERRED,
501 editor BIGINT NOT NULL
502 REFERENCES actor.usr (id)
503 DEFERRABLE INITIALLY DEFERRED,
504 create_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
505 edit_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
507 -- columns above this point are attributes of the template itself
508 -- columns after this point are attributes of the copy this template modifies/creates
509 circ_lib INT REFERENCES actor.org_unit (id)
510 DEFERRABLE INITIALLY DEFERRED,
511 status INT REFERENCES config.copy_status (id)
512 DEFERRABLE INITIALLY DEFERRED,
513 location INT REFERENCES asset.copy_location (id)
514 DEFERRABLE INITIALLY DEFERRED,
515 loan_duration INT CONSTRAINT valid_loan_duration CHECK (
516 loan_duration IS NULL OR loan_duration IN (1,2,3)),
517 fine_level INT CONSTRAINT valid_fine_level CHECK (
518 fine_level IS NULL OR loan_duration IN (1,2,3)),
524 deposit_amount NUMERIC(6,2),
534 CREATE TABLE asset.copy_vis_attr_cache (
535 id BIGSERIAL PRIMARY KEY,
536 record BIGINT NOT NULL, -- No FKEYs, managed by user triggers.
537 target_copy BIGINT NOT NULL,
538 vis_attr_vector INT[]
540 CREATE INDEX copy_vis_attr_cache_record_idx ON asset.copy_vis_attr_cache (record);
541 CREATE INDEX copy_vis_attr_cache_copy_idx ON asset.copy_vis_attr_cache (target_copy);
543 CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
548 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
550 FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
552 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
553 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
554 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
558 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
564 asset.copy_vis_attr_cache av
565 JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
566 JOIN asset.call_number cn ON (cp.call_number = cn.id AND not cn.deleted)
567 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
571 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
578 $f$ LANGUAGE PLPGSQL;
580 CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
585 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
587 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
589 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
590 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
591 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
595 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
600 asset.copy_vis_attr_cache av
601 JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
602 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
606 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
613 $f$ LANGUAGE PLPGSQL;
615 CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count(org integer, rid bigint)
616 RETURNS TABLE(depth integer, org_unit integer, visible bigint, available bigint, unshadow bigint, transcendant integer)
623 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
625 FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
627 WITH available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
630 (cp.status = ANY (available_statuses.ids))::INT as available,
631 (cl.opac_visible AND cp.opac_visible)::INT as opac_visible
634 actor.org_unit_descendants(ans.id) d
635 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
636 JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
637 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
641 (cp.status = ANY (available_statuses.ids))::INT as available,
642 (cl.opac_visible AND cp.opac_visible)::INT as opac_visible
645 actor.org_unit_descendants(ans.id) d
646 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
647 JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
648 JOIN biblio.peer_bib_copy_map bp ON (bp.peer_record = rid AND bp.target_copy = cp.id)
650 select ans.depth, ans.id, count(id), sum(x.available::int), sum(x.opac_visible::int), trans
651 from ((select * from cp) union (select * from peer)) x
655 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
663 CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
668 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;
670 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
675 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
676 SUM( CASE WHEN cl.opac_visible AND cp.opac_visible THEN 1 ELSE 0 END),
679 actor.org_unit_descendants(ans.id) d
680 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
681 JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
682 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
686 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
693 $f$ LANGUAGE PLPGSQL;
695 CREATE OR REPLACE FUNCTION asset.record_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
697 IF staff IS TRUE THEN
699 RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
701 RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
705 RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
707 RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
713 $f$ LANGUAGE PLPGSQL;
715 CREATE OR REPLACE FUNCTION asset.record_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
720 JOIN asset.call_number acn ON acp.call_number = acn.id
721 JOIN asset.copy_location acpl ON acp.location = acpl.id
722 JOIN config.copy_status ccs ON acp.status = ccs.id
725 AND acp.holdable = true
726 AND acpl.holdable = true
727 AND ccs.holdable = true
728 AND acp.deleted = false
729 AND acpl.deleted = false
730 AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
737 $f$ LANGUAGE PLPGSQL;
739 CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
744 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
746 FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
748 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
749 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
750 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
754 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
760 asset.copy_vis_attr_cache av
761 JOIN asset.copy cp ON (cp.id = av.target_copy)
762 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
763 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
767 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
774 $f$ LANGUAGE PLPGSQL;
776 CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
781 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
783 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
785 WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
786 available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
787 mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
791 SUM( (cp.status = ANY (available_statuses.ids))::INT ),
797 asset.copy_vis_attr_cache av
798 JOIN asset.copy cp ON (cp.id = av.target_copy)
799 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
800 WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
804 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
811 $f$ LANGUAGE PLPGSQL;
813 CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
818 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
820 FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
825 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
829 actor.org_unit_descendants(ans.id) d
830 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
831 JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
832 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
836 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
843 $f$ LANGUAGE PLPGSQL;
845 CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, rid BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
850 SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;
852 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
857 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
861 actor.org_unit_descendants(ans.id) d
862 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
863 JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
864 JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
868 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
875 $f$ LANGUAGE PLPGSQL;
877 CREATE OR REPLACE FUNCTION asset.metarecord_copy_count ( place INT, rid BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
879 IF staff IS TRUE THEN
881 RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
883 RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
887 RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
889 RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
895 $f$ LANGUAGE PLPGSQL;
897 CREATE OR REPLACE FUNCTION asset.metarecord_has_holdable_copy ( rid BIGINT, ou INT DEFAULT NULL) RETURNS BOOL AS $f$
902 JOIN asset.call_number acn ON acp.call_number = acn.id
903 JOIN asset.copy_location acpl ON acp.location = acpl.id
904 JOIN config.copy_status ccs ON acp.status = ccs.id
905 JOIN metabib.metarecord_source_map mmsm ON acn.record = mmsm.source
907 mmsm.metarecord = rid
908 AND acp.holdable = true
909 AND acpl.holdable = true
910 AND ccs.holdable = true
911 AND acp.deleted = false
912 AND acpl.deleted = false
913 AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
920 $f$ LANGUAGE PLPGSQL;
922 CREATE OR REPLACE FUNCTION asset.autogenerate_placeholder_barcode ( ) RETURNS TRIGGER AS $f$
924 IF NEW.barcode LIKE '@@%' THEN
925 NEW.barcode := '@@' || NEW.id;
929 $f$ LANGUAGE PLPGSQL;
931 CREATE TRIGGER autogenerate_placeholder_barcode
932 BEFORE INSERT OR UPDATE ON asset.copy
933 FOR EACH ROW EXECUTE PROCEDURE asset.autogenerate_placeholder_barcode();
935 CREATE OR REPLACE FUNCTION evergreen.fake_fkey_tgr () RETURNS TRIGGER AS $F$
939 EXECUTE 'SELECT ($1).' || quote_ident(TG_ARGV[0]) INTO copy_id USING NEW;
940 IF copy_id IS NOT NULL THEN
941 PERFORM * FROM asset.copy WHERE id = copy_id;
943 RAISE EXCEPTION 'Key (%.%=%) does not exist in asset.copy', TG_TABLE_SCHEMA, TG_TABLE_NAME, copy_id;
948 $F$ LANGUAGE PLPGSQL;
950 CREATE TABLE asset.copy_tag (
951 id SERIAL PRIMARY KEY,
952 tag_type TEXT REFERENCES config.copy_tag_type (code)
953 ON UPDATE CASCADE ON DELETE CASCADE,
956 index_vector tsvector NOT NULL,
958 pub BOOLEAN DEFAULT TRUE,
959 owner INTEGER NOT NULL REFERENCES actor.org_unit (id),
963 CREATE INDEX asset_copy_tag_label_idx
964 ON asset.copy_tag (label);
965 CREATE INDEX asset_copy_tag_label_lower_idx
966 ON asset.copy_tag (evergreen.lowercase(label));
967 CREATE INDEX asset_copy_tag_index_vector_idx
969 USING GIN(index_vector);
970 CREATE INDEX asset_copy_tag_tag_type_idx
971 ON asset.copy_tag (tag_type);
972 CREATE INDEX asset_copy_tag_owner_idx
973 ON asset.copy_tag (owner);
975 CREATE OR REPLACE FUNCTION asset.set_copy_tag_value () RETURNS TRIGGER AS $$
977 IF NEW.value IS NULL THEN
978 NEW.value = NEW.label;
983 $$ LANGUAGE 'plpgsql';
985 -- name of following trigger chosen to ensure it runs first
986 CREATE TRIGGER asset_copy_tag_do_value
987 BEFORE INSERT OR UPDATE ON asset.copy_tag
988 FOR EACH ROW EXECUTE PROCEDURE asset.set_copy_tag_value();
989 CREATE TRIGGER asset_copy_tag_fti_trigger
990 BEFORE UPDATE OR INSERT ON asset.copy_tag
991 FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('default');
993 CREATE TABLE asset.copy_tag_copy_map (
994 id BIGSERIAL PRIMARY KEY,
996 tag INTEGER REFERENCES asset.copy_tag (id)
997 ON UPDATE CASCADE ON DELETE CASCADE
1000 CREATE INDEX asset_copy_tag_copy_map_copy_idx
1001 ON asset.copy_tag_copy_map (copy);
1002 CREATE INDEX asset_copy_tag_copy_map_tag_idx
1003 ON asset.copy_tag_copy_map (tag);
1005 CREATE OR REPLACE FUNCTION asset.copy_state (cid BIGINT) RETURNS TEXT AS $$
1007 last_circ_stop TEXT;
1008 the_copy asset.copy%ROWTYPE;
1011 SELECT * INTO the_copy FROM asset.copy WHERE id = cid;
1012 IF NOT FOUND THEN RETURN NULL; END IF;
1014 IF the_copy.status = 3 THEN -- Lost
1016 ELSIF the_copy.status = 4 THEN -- Missing
1018 ELSIF the_copy.status = 14 THEN -- Damaged
1020 ELSIF the_copy.status = 17 THEN -- Lost and paid
1021 RETURN 'LOST_AND_PAID';
1024 SELECT stop_fines INTO last_circ_stop
1025 FROM action.circulation
1026 WHERE target_copy = cid AND checkin_time IS NULL
1027 ORDER BY xact_start DESC LIMIT 1;
1030 IF last_circ_stop IN (
1031 'CLAIMSNEVERCHECKEDOUT',
1035 RETURN last_circ_stop;
1041 $$ LANGUAGE PLPGSQL;
1043 CREATE TYPE config.copy_alert_type_state AS ENUM (
1051 'CLAIMSNEVERCHECKEDOUT'
1054 CREATE TYPE config.copy_alert_type_event AS ENUM (
1059 CREATE TABLE config.copy_alert_type (
1060 id serial primary key, -- reserve 1-100 for system
1061 scope_org int not null references actor.org_unit (id) on delete cascade,
1062 active bool not null default true,
1063 name text not null unique,
1064 state config.copy_alert_type_state,
1065 event config.copy_alert_type_event,
1067 invert_location bool not null default false,
1072 SELECT SETVAL('config.copy_alert_type_id_seq'::TEXT, 100);
1074 CREATE TABLE actor.copy_alert_suppress (
1075 id serial primary key,
1076 org int not null references actor.org_unit (id) on delete cascade,
1077 alert_type int not null references config.copy_alert_type (id) on delete cascade
1080 CREATE TABLE asset.copy_alert (
1081 id bigserial primary key,
1082 alert_type int not null references config.copy_alert_type (id) on delete cascade,
1083 copy bigint not null,
1084 temp bool not null default false,
1085 create_time timestamptz not null default now(),
1086 create_staff bigint not null references actor.usr (id) on delete set null,
1088 ack_time timestamptz,
1089 ack_staff bigint references actor.usr (id) on delete set null
1092 CREATE VIEW asset.active_copy_alert AS
1094 FROM asset.copy_alert
1095 WHERE ack_time IS NULL;
1097 CREATE TABLE asset.course_module_course (
1098 id SERIAL PRIMARY KEY,
1100 course_number TEXT NOT NULL,
1101 section_number TEXT,
1102 owning_lib INT REFERENCES actor.org_unit (id),
1103 is_archived BOOLEAN DEFAULT false
1106 CREATE TABLE asset.course_module_role (
1107 id SERIAL PRIMARY KEY,
1108 name TEXT UNIQUE NOT NULL,
1109 is_public BOOLEAN NOT NULL DEFAULT false
1112 CREATE TABLE asset.course_module_course_users (
1113 id SERIAL PRIMARY KEY,
1114 course INT NOT NULL REFERENCES asset.course_module_course (id),
1115 usr INT NOT NULL REFERENCES actor.usr (id),
1116 usr_role INT REFERENCES asset.course_module_role (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
1119 CREATE TABLE asset.course_module_course_materials (
1120 id SERIAL PRIMARY KEY,
1121 course INT NOT NULL REFERENCES asset.course_module_course (id),
1122 item INT REFERENCES asset.copy (id),
1124 record INT REFERENCES biblio.record_entry (id),
1125 temporary_record BOOLEAN,
1126 original_location INT REFERENCES asset.copy_location,
1127 original_status INT REFERENCES config.copy_status,
1128 original_circ_modifier TEXT, --REFERENCES config.circ_modifier
1129 original_callnumber INT REFERENCES asset.call_number,
1130 unique (course, item, record)
1133 CREATE TABLE asset.course_module_term (
1134 id SERIAL PRIMARY KEY,
1135 name TEXT UNIQUE NOT NULL,
1136 owning_lib INT REFERENCES actor.org_unit (id),
1137 start_date TIMESTAMP WITH TIME ZONE,
1138 end_date TIMESTAMP WITH TIME ZONE
1141 CREATE TABLE asset.course_module_term_course_map (
1142 id BIGSERIAL PRIMARY KEY,
1143 term INT NOT NULL REFERENCES asset.course_module_term (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1144 course INT NOT NULL REFERENCES asset.course_module_course (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED