2 -- Rather than polluting the public schema with general Evergreen
3 -- functions, carve out a dedicated schema. It might already exist,
4 -- so do it before the transaction starts
5 CREATE SCHEMA evergreen;
10 ALTER TABLE permission.grp_tree
11 ADD COLUMN hold_priority INT NOT NULL DEFAULT 0;
14 ALTER TABLE config.hold_matrix_matchpoint
15 ADD COLUMN strict_ou_match BOOL NOT NULL DEFAULT FALSE,
16 ADD COLUMN marc_bib_level text,
17 DROP CONSTRAINT hous_once_per_grp_loc_mod_marc,
18 DROP CONSTRAINT hold_matrix_matchpoint_marc_form_fkey,
19 DROP CONSTRAINT hold_matrix_matchpoint_marc_type_fkey,
20 DROP CONSTRAINT hold_matrix_matchpoint_marc_vr_format_fkey;
24 -- Replace all uses of PostgreSQL's built-in LOWER() function with
25 -- a more locale-savvy PLPERLU evergreen.lowercase() function
26 CREATE OR REPLACE FUNCTION evergreen.lowercase( TEXT ) RETURNS TEXT AS $$
28 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
31 CREATE OR REPLACE FUNCTION evergreen.change_db_setting(setting_name TEXT, settings TEXT[]) RETURNS VOID AS $$
33 EXECUTE 'ALTER DATABASE ' || quote_ident(current_database()) || ' SET ' || quote_ident(setting_name) || ' = ' || array_to_string(settings, ',');
39 SELECT evergreen.change_db_setting('search_path', ARRAY['evergreen','public','pg_catalog']);
41 -- Fix function breakage due to short search path
42 CREATE OR REPLACE FUNCTION evergreen.force_unicode_normal_form(string TEXT, form TEXT) RETURNS TEXT AS $func$
43 use Unicode::Normalize 'normalize';
44 return normalize($_[1],$_[0]); # reverse the params
45 $func$ LANGUAGE PLPERLU;
47 CREATE OR REPLACE FUNCTION evergreen.facet_force_nfc() RETURNS TRIGGER AS $$
49 NEW.value := evergreen.force_unicode_normal_form(NEW.value,'NFC');
54 CREATE OR REPLACE FUNCTION evergreen.xml_escape(str TEXT) RETURNS text AS $$
55 SELECT REPLACE(REPLACE(REPLACE($1,
59 $$ LANGUAGE SQL IMMUTABLE;
61 CREATE OR REPLACE FUNCTION evergreen.maintain_901 () RETURNS TRIGGER AS $func$
63 use_id_for_tcn BOOLEAN;
65 -- Remove any existing 901 fields before we insert the authoritative one
66 NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield[^>]*?tag="901".+?</datafield>', '', 'g');
68 IF TG_TABLE_SCHEMA = 'biblio' THEN
69 -- Set TCN value to record ID?
70 SELECT enabled FROM config.global_flag INTO use_id_for_tcn
71 WHERE name = 'cat.bib.use_id_for_tcn';
73 IF use_id_for_tcn = 't' THEN
74 NEW.tcn_value := NEW.id;
77 NEW.marc := REGEXP_REPLACE(
79 E'(</(?:[^:]*?:)?record>)',
80 E'<datafield tag="901" ind1=" " ind2=" ">' ||
81 '<subfield code="a">' || evergreen.xml_escape(NEW.tcn_value) || E'</subfield>' ||
82 '<subfield code="b">' || evergreen.xml_escape(NEW.tcn_source) || E'</subfield>' ||
83 '<subfield code="c">' || NEW.id || E'</subfield>' ||
84 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
85 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
86 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
89 ELSIF TG_TABLE_SCHEMA = 'authority' THEN
90 NEW.marc := REGEXP_REPLACE(
92 E'(</(?:[^:]*?:)?record>)',
93 E'<datafield tag="901" ind1=" " ind2=" ">' ||
94 '<subfield code="c">' || NEW.id || E'</subfield>' ||
95 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
98 ELSIF TG_TABLE_SCHEMA = 'serial' THEN
99 NEW.marc := REGEXP_REPLACE(
101 E'(</(?:[^:]*?:)?record>)',
102 E'<datafield tag="901" ind1=" " ind2=" ">' ||
103 '<subfield code="c">' || NEW.id || E'</subfield>' ||
104 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
105 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
106 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
110 NEW.marc := REGEXP_REPLACE(
112 E'(</(?:[^:]*?:)?record>)',
113 E'<datafield tag="901" ind1=" " ind2=" ">' ||
114 '<subfield code="c">' || NEW.id || E'</subfield>' ||
115 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
122 $func$ LANGUAGE PLPGSQL;
124 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
126 CREATE OR REPLACE FUNCTION evergreen.lpad_number_substrings( TEXT, TEXT, INT ) RETURNS TEXT AS $$
132 while ($string =~ /(?:^|\D)(\d{1,$find})(?:$|\D)/) {
134 $padded = $pad x ($len - length($padded)) . $padded;
135 $string =~ s/$1/$padded/sg;
142 ALTER TABLE config.hard_due_date DROP CONSTRAINT hard_due_date_name_check;
145 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
148 use Unicode::Normalize;
151 my $str = decode_utf8(shift);
154 # Apply NACO normalization to input string; based on
155 # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
157 # Note that unlike a strict reading of the NACO normalization rules,
158 # output is returned as lowercase instead of uppercase for compatibility
159 # with previous versions of the Evergreen naco_normalize routine.
161 # Convert to upper-case first; even though final output will be lowercase, doing this will
162 # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
163 # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
166 # remove non-filing strings
167 $str =~ s/\x{0098}.*?\x{009C}//g;
171 # additional substitutions - 3.6.
172 $str =~ s/\x{00C6}/AE/g;
173 $str =~ s/\x{00DE}/TH/g;
174 $str =~ s/\x{0152}/OE/g;
175 $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}]['/DDOLl/d;
177 # transformations based on Unicode category codes
178 $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
180 if ($sf && $sf =~ /^a/o) {
181 my $commapos = index($str, ',');
182 if ($commapos > -1) {
183 if ($commapos != length($str) - 1) {
184 $str =~ s/,/\x07/; # preserve first comma
189 # since we've stripped out the control characters, we can now
190 # use a few as placeholders temporarily
191 $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
192 $str =~ s/[\p{Pc}\p{Pd}\p{Pe}\p{Pf}\p{Pi}\p{Po}\p{Ps}\p{Sk}\p{Sm}\p{So}\p{Zl}\p{Zp}\p{Zs}]/ /g;
193 $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
196 $str =~ tr/\x{0660}-\x{0669}\x{06F0}-\x{06F9}\x{07C0}-\x{07C9}\x{0966}-\x{096F}\x{09E6}-\x{09EF}\x{0A66}-\x{0A6F}\x{0AE6}-\x{0AEF}\x{0B66}-\x{0B6F}\x{0BE6}-\x{0BEF}\x{0C66}-\x{0C6F}\x{0CE6}-\x{0CEF}\x{0D66}-\x{0D6F}\x{0E50}-\x{0E59}\x{0ED0}-\x{0ED9}\x{0F20}-\x{0F29}\x{1040}-\x{1049}\x{1090}-\x{1099}\x{17E0}-\x{17E9}\x{1810}-\x{1819}\x{1946}-\x{194F}\x{19D0}-\x{19D9}\x{1A80}-\x{1A89}\x{1A90}-\x{1A99}\x{1B50}-\x{1B59}\x{1BB0}-\x{1BB9}\x{1C40}-\x{1C49}\x{1C50}-\x{1C59}\x{A620}-\x{A629}\x{A8D0}-\x{A8D9}\x{A900}-\x{A909}\x{A9D0}-\x{A9D9}\x{AA50}-\x{AA59}\x{ABF0}-\x{ABF9}\x{FF10}-\x{FF19}/0-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-9/;
198 # intentionally skipping step 8 of the NACO algorithm; if the string
199 # gets normalized away, that's fine.
201 # leading and trailing spaces
207 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
210 CREATE OR REPLACE FUNCTION permission.grp_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
211 WITH RECURSIVE grp_ancestors_distance(id, distance) AS (
214 SELECT pgt.parent, gad.distance+1
215 FROM permission.grp_tree pgt JOIN grp_ancestors_distance gad ON pgt.id = gad.id
216 WHERE pgt.parent IS NOT NULL
218 SELECT * FROM grp_ancestors_distance;
219 $$ LANGUAGE SQL STABLE;
221 CREATE OR REPLACE FUNCTION permission.grp_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
222 WITH RECURSIVE grp_descendants_distance(id, distance) AS (
225 SELECT pgt.id, gdd.distance+1
226 FROM permission.grp_tree pgt JOIN grp_descendants_distance gdd ON pgt.parent = gdd.id
228 SELECT * FROM grp_descendants_distance;
229 $$ LANGUAGE SQL STABLE;
231 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
232 WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
235 SELECT ou.parent_ou, ouad.distance+1
236 FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON ou.id = ouad.id
237 WHERE ou.parent_ou IS NOT NULL
239 SELECT * FROM org_unit_ancestors_distance;
240 $$ LANGUAGE SQL STABLE;
242 CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TABLE (id INT, distance INT) AS $$
243 WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
246 SELECT ou.id, oudd.distance+1
247 FROM actor.org_unit ou JOIN org_unit_descendants_distance oudd ON ou.parent_ou = oudd.id
249 SELECT * FROM org_unit_descendants_distance;
250 $$ LANGUAGE SQL STABLE;
252 CREATE TABLE config.circ_matrix_weights (
253 id SERIAL PRIMARY KEY,
254 name TEXT NOT NULL UNIQUE,
255 org_unit NUMERIC(6,2) NOT NULL,
256 grp NUMERIC(6,2) NOT NULL,
257 circ_modifier NUMERIC(6,2) NOT NULL,
258 marc_type NUMERIC(6,2) NOT NULL,
259 marc_form NUMERIC(6,2) NOT NULL,
260 marc_vr_format NUMERIC(6,2) NOT NULL,
261 copy_circ_lib NUMERIC(6,2) NOT NULL,
262 copy_owning_lib NUMERIC(6,2) NOT NULL,
263 user_home_ou NUMERIC(6,2) NOT NULL,
264 ref_flag NUMERIC(6,2) NOT NULL,
265 juvenile_flag NUMERIC(6,2) NOT NULL,
266 is_renewal NUMERIC(6,2) NOT NULL,
267 usr_age_lower_bound NUMERIC(6,2) NOT NULL,
268 usr_age_upper_bound NUMERIC(6,2) NOT NULL
271 CREATE TABLE config.hold_matrix_weights (
272 id SERIAL PRIMARY KEY,
273 name TEXT NOT NULL UNIQUE,
274 user_home_ou NUMERIC(6,2) NOT NULL,
275 request_ou NUMERIC(6,2) NOT NULL,
276 pickup_ou NUMERIC(6,2) NOT NULL,
277 item_owning_ou NUMERIC(6,2) NOT NULL,
278 item_circ_ou NUMERIC(6,2) NOT NULL,
279 usr_grp NUMERIC(6,2) NOT NULL,
280 requestor_grp NUMERIC(6,2) NOT NULL,
281 circ_modifier NUMERIC(6,2) NOT NULL,
282 marc_type NUMERIC(6,2) NOT NULL,
283 marc_form NUMERIC(6,2) NOT NULL,
284 marc_vr_format NUMERIC(6,2) NOT NULL,
285 juvenile_flag NUMERIC(6,2) NOT NULL,
286 ref_flag NUMERIC(6,2) NOT NULL
289 CREATE TABLE config.weight_assoc (
290 id SERIAL PRIMARY KEY,
291 active BOOL NOT NULL,
292 org_unit INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
293 circ_weights INT REFERENCES config.circ_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
294 hold_weights INT REFERENCES config.hold_matrix_weights (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
296 CREATE UNIQUE INDEX cwa_one_active_per_ou ON config.weight_assoc (org_unit) WHERE active;
298 INSERT INTO config.circ_matrix_weights(name, org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_upper_bound, usr_age_lower_bound) VALUES
299 ('Default', 10.0, 11.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
300 ('Org_Unit_First', 11.0, 10.0, 5.0, 4.0, 3.0, 2.0, 8.0, 8.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
301 ('Item_Owner_First', 8.0, 8.0, 5.0, 4.0, 3.0, 2.0, 10.0, 11.0, 8.0, 1.0, 6.0, 7.0, 0.0, 0.0),
302 ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
304 INSERT INTO config.hold_matrix_weights(name, user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag) VALUES
305 ('Default', 5.0, 5.0, 5.0, 5.0, 5.0, 7.0, 8.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
306 ('Item_Owner_First', 5.0, 5.0, 5.0, 8.0, 7.0, 5.0, 5.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
307 ('User_Before_Requestor', 5.0, 5.0, 5.0, 5.0, 5.0, 8.0, 7.0, 4.0, 3.0, 2.0, 1.0, 4.0, 0.0),
308 ('All_Equal', 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
310 INSERT INTO config.weight_assoc(active, org_unit, circ_weights, hold_weights) VALUES
314 DROP FUNCTION IF EXISTS actor.usr_purge_data(INT, INT);
316 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
318 specified_dest_usr IN INTEGER
322 renamable_row RECORD;
326 IF specified_dest_usr IS NULL THEN
327 dest_usr := 1; -- Admin user on stock installs
329 dest_usr := specified_dest_usr;
335 mailing_address = NULL,
336 billing_address = NULL
340 UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
341 UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
342 UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
343 UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
344 UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
345 UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
346 DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
348 -- Update with a rename to avoid collisions
352 WHERE owner = src_usr
354 suffix := ' (' || src_usr || ')';
358 SET owner = dest_usr, name = name || suffix
359 WHERE id = renamable_row.id;
360 EXCEPTION WHEN unique_violation THEN
361 suffix := suffix || ' ';
368 UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
369 UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
370 UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
371 UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
372 UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
373 UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
374 UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
375 UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
378 DELETE FROM action.circulation WHERE usr = src_usr;
379 UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
380 UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
381 UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
382 UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
383 UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
384 DELETE FROM action.hold_request WHERE usr = src_usr;
385 UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
386 UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
387 DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
388 UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
389 DELETE FROM action.survey_response WHERE usr = src_usr;
390 UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
393 DELETE FROM actor.card WHERE usr = src_usr;
394 DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
396 -- The following update is intended to avoid transient violations of a foreign
397 -- key constraint, whereby actor.usr_address references itself. It may not be
398 -- necessary, but it does no harm.
399 UPDATE actor.usr_address SET replaces = NULL
400 WHERE usr = src_usr AND replaces IS NOT NULL;
401 DELETE FROM actor.usr_address WHERE usr = src_usr;
402 DELETE FROM actor.usr_note WHERE usr = src_usr;
403 UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
404 DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
405 UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
406 DELETE FROM actor.usr_setting WHERE usr = src_usr;
407 DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
408 UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
411 UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
412 UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
413 UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
414 UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
415 UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
416 UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
419 DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
420 DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
421 UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
422 UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr;
423 UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
424 UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr;
425 UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
426 UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr;
429 UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
430 UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
431 UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
432 UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
435 -- Update buckets with a rename to avoid collisions
438 FROM container.biblio_record_entry_bucket
439 WHERE owner = src_usr
441 suffix := ' (' || src_usr || ')';
444 UPDATE container.biblio_record_entry_bucket
445 SET owner = dest_usr, name = name || suffix
446 WHERE id = renamable_row.id;
447 EXCEPTION WHEN unique_violation THEN
448 suffix := suffix || ' ';
457 FROM container.call_number_bucket
458 WHERE owner = src_usr
460 suffix := ' (' || src_usr || ')';
463 UPDATE container.call_number_bucket
464 SET owner = dest_usr, name = name || suffix
465 WHERE id = renamable_row.id;
466 EXCEPTION WHEN unique_violation THEN
467 suffix := suffix || ' ';
476 FROM container.copy_bucket
477 WHERE owner = src_usr
479 suffix := ' (' || src_usr || ')';
482 UPDATE container.copy_bucket
483 SET owner = dest_usr, name = name || suffix
484 WHERE id = renamable_row.id;
485 EXCEPTION WHEN unique_violation THEN
486 suffix := suffix || ' ';
495 FROM container.user_bucket
496 WHERE owner = src_usr
498 suffix := ' (' || src_usr || ')';
501 UPDATE container.user_bucket
502 SET owner = dest_usr, name = name || suffix
503 WHERE id = renamable_row.id;
504 EXCEPTION WHEN unique_violation THEN
505 suffix := suffix || ' ';
512 DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
515 DELETE FROM money.billable_xact WHERE usr = src_usr;
516 DELETE FROM money.collections_tracker WHERE usr = src_usr;
517 UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
520 DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
521 DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
522 DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
523 DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
526 -- Update with a rename to avoid collisions
530 FROM reporter.output_folder
531 WHERE owner = src_usr
533 suffix := ' (' || src_usr || ')';
536 UPDATE reporter.output_folder
537 SET owner = dest_usr, name = name || suffix
538 WHERE id = renamable_row.id;
539 EXCEPTION WHEN unique_violation THEN
540 suffix := suffix || ' ';
546 EXCEPTION WHEN undefined_table THEN
551 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
552 EXCEPTION WHEN undefined_table THEN
556 -- Update with a rename to avoid collisions
560 FROM reporter.report_folder
561 WHERE owner = src_usr
563 suffix := ' (' || src_usr || ')';
566 UPDATE reporter.report_folder
567 SET owner = dest_usr, name = name || suffix
568 WHERE id = renamable_row.id;
569 EXCEPTION WHEN unique_violation THEN
570 suffix := suffix || ' ';
576 EXCEPTION WHEN undefined_table THEN
581 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
582 EXCEPTION WHEN undefined_table THEN
587 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
588 EXCEPTION WHEN undefined_table THEN
592 -- Update with a rename to avoid collisions
596 FROM reporter.template_folder
597 WHERE owner = src_usr
599 suffix := ' (' || src_usr || ')';
602 UPDATE reporter.template_folder
603 SET owner = dest_usr, name = name || suffix
604 WHERE id = renamable_row.id;
605 EXCEPTION WHEN unique_violation THEN
606 suffix := suffix || ' ';
612 EXCEPTION WHEN undefined_table THEN
617 -- Update with a rename to avoid collisions
621 WHERE owner = src_usr
623 suffix := ' (' || src_usr || ')';
626 UPDATE vandelay.queue
627 SET owner = dest_usr, name = name || suffix
628 WHERE id = renamable_row.id;
629 EXCEPTION WHEN unique_violation THEN
630 suffix := suffix || ' ';
640 -- 0482, 0487, and parts of others
641 -- Circ matchpoint table changes
642 ALTER TABLE config.circ_matrix_matchpoint
643 ALTER COLUMN circulate DROP NOT NULL, -- Fallthrough enable
644 ALTER COLUMN circulate DROP DEFAULT, -- Stop defaulting to true to enable default to fallthrough
645 ALTER COLUMN duration_rule DROP NOT NULL, -- Fallthrough enable
646 ALTER COLUMN recurring_fine_rule DROP NOT NULL, -- Fallthrough enable
647 ALTER COLUMN max_fine_rule DROP NOT NULL, -- Fallthrough enable
648 ADD COLUMN renewals INT, -- Renewals override
649 ADD COLUMN user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
650 ADD COLUMN grace_period INTERVAL,
651 ADD COLUMN marc_bib_level text,
652 DROP CONSTRAINT ep_once_per_grp_loc_mod_marc,
653 DROP CONSTRAINT circ_matrix_matchpoint_marc_form_fkey,
654 DROP CONSTRAINT circ_matrix_matchpoint_marc_type_fkey,
655 DROP CONSTRAINT circ_matrix_matchpoint_marc_vr_format_fkey;
657 -- Clean up tables before making normalized index
659 CREATE OR REPLACE FUNCTION action.cleanup_matrix_matchpoints() RETURNS void AS $func$
665 SELECT org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_lower_bound, usr_age_upper_bound, COUNT(id) as rowcount, MIN(id) as firstrow
666 FROM config.circ_matrix_matchpoint
668 GROUP BY org_unit, grp, circ_modifier, marc_type, marc_form, marc_vr_format, copy_circ_lib, copy_owning_lib, user_home_ou, ref_flag, juvenile_flag, is_renewal, usr_age_lower_bound, usr_age_upper_bound
669 HAVING COUNT(id) > 1 LOOP
671 UPDATE config.circ_matrix_matchpoint SET active=false
672 WHERE id > temp_row.firstrow
673 AND org_unit = temp_row.org_unit
674 AND grp = temp_row.grp
675 AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
676 AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
677 AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
678 AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
679 AND copy_circ_lib IS NOT DISTINCT FROM temp_row.copy_circ_lib
680 AND copy_owning_lib IS NOT DISTINCT FROM temp_row.copy_owning_lib
681 AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
682 AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag
683 AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
684 AND is_renewal IS NOT DISTINCT FROM temp_row.is_renewal
685 AND usr_age_lower_bound IS NOT DISTINCT FROM temp_row.usr_age_lower_bound
686 AND usr_age_upper_bound IS NOT DISTINCT FROM temp_row.usr_age_upper_bound;
691 SELECT user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag, COUNT(id) as rowcount, MIN(id) as firstrow
692 FROM config.hold_matrix_matchpoint
694 GROUP BY user_home_ou, request_ou, pickup_ou, item_owning_ou, item_circ_ou, usr_grp, requestor_grp, circ_modifier, marc_type, marc_form, marc_vr_format, juvenile_flag, ref_flag
695 HAVING COUNT(id) > 1 LOOP
697 UPDATE config.hold_matrix_matchpoint SET active=false
698 WHERE id > temp_row.firstrow
699 AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
700 AND request_ou IS NOT DISTINCT FROM temp_row.request_ou
701 AND pickup_ou IS NOT DISTINCT FROM temp_row.pickup_ou
702 AND item_owning_ou IS NOT DISTINCT FROM temp_row.item_owning_ou
703 AND item_circ_ou IS NOT DISTINCT FROM temp_row.item_circ_ou
704 AND usr_grp IS NOT DISTINCT FROM temp_row.usr_grp
705 AND requestor_grp IS NOT DISTINCT FROM temp_row.requestor_grp
706 AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
707 AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
708 AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
709 AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
710 AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
711 AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag;
714 $func$ LANGUAGE plpgsql;
716 SELECT action.cleanup_matrix_matchpoints();
718 DROP FUNCTION IF EXISTS action.cleanup_matrix_matchpoints();
720 -- Create Normalized indexes
722 CREATE UNIQUE INDEX ccmm_once_per_paramset ON config.circ_matrix_matchpoint (org_unit, grp, COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(copy_circ_lib::TEXT, ''), COALESCE(copy_owning_lib::TEXT, ''), COALESCE(user_home_ou::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(is_renewal::TEXT, ''), COALESCE(usr_age_lower_bound::TEXT, ''), COALESCE(usr_age_upper_bound::TEXT, '')) WHERE active;
724 CREATE UNIQUE INDEX chmm_once_per_paramset ON config.hold_matrix_matchpoint (COALESCE(user_home_ou::TEXT, ''), COALESCE(request_ou::TEXT, ''), COALESCE(pickup_ou::TEXT, ''), COALESCE(item_owning_ou::TEXT, ''), COALESCE(item_circ_ou::TEXT, ''), COALESCE(usr_grp::TEXT, ''), COALESCE(requestor_grp::TEXT, ''), COALESCE(circ_modifier, ''), COALESCE(marc_type, ''), COALESCE(marc_form, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, '')) WHERE active;
727 DROP FUNCTION asset.metarecord_copy_count ( INT, BIGINT, BOOL );
728 DROP FUNCTION asset.record_copy_count ( INT, BIGINT, BOOL );
730 DROP FUNCTION asset.opac_ou_record_copy_count (INT, BIGINT);
731 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$
736 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;
738 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
743 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
747 actor.org_unit_descendants(ans.id) d
748 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
749 JOIN asset.copy cp ON (cp.id = av.copy_id)
753 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
760 $f$ LANGUAGE PLPGSQL;
762 DROP FUNCTION asset.opac_lasso_record_copy_count (INT, BIGINT);
763 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$
768 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;
770 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
775 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
779 actor.org_unit_descendants(ans.id) d
780 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
781 JOIN asset.copy cp ON (cp.id = av.copy_id)
785 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
792 $f$ LANGUAGE PLPGSQL;
794 DROP FUNCTION asset.staff_ou_record_copy_count (INT, BIGINT);
796 DROP FUNCTION asset.staff_lasso_record_copy_count (INT, BIGINT);
798 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$
800 IF staff IS TRUE THEN
802 RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
804 RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
808 RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
810 RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
816 $f$ LANGUAGE PLPGSQL;
818 DROP FUNCTION asset.opac_ou_metarecord_copy_count (INT, BIGINT);
819 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$
824 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;
826 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
831 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
835 actor.org_unit_descendants(ans.id) d
836 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
837 JOIN asset.copy cp ON (cp.id = av.copy_id)
838 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
842 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
849 $f$ LANGUAGE PLPGSQL;
851 DROP FUNCTION asset.opac_lasso_metarecord_copy_count (INT, BIGINT);
852 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$
857 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;
859 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
864 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
868 actor.org_unit_descendants(ans.id) d
869 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
870 JOIN asset.copy cp ON (cp.id = av.copy_id)
871 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
875 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
882 $f$ LANGUAGE PLPGSQL;
884 DROP FUNCTION asset.staff_lasso_metarecord_copy_count (INT, BIGINT);
886 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$
888 IF staff IS TRUE THEN
890 RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
892 RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
896 RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
898 RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
904 $f$ LANGUAGE PLPGSQL;
907 CREATE OR REPLACE VIEW reporter.simple_record AS
914 title.value AS title,
915 uniform_title.value AS uniform_title,
916 author.value AS author,
917 publisher.value AS publisher,
918 SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
919 series_title.value AS series_title,
920 series_statement.value AS series_statement,
921 summary.value AS summary,
922 ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
923 ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
924 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
925 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
926 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
927 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
928 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
929 ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
930 FROM biblio.record_entry r
931 JOIN metabib.metarecord_source_map s ON (s.source = r.id)
932 LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
933 LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
934 LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
935 LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
936 LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
937 LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
938 LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
939 LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
940 LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
941 LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
942 GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
944 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
950 FIRST(title.value) AS title,
951 FIRST(author.value) AS author,
952 ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
953 ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
954 ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
955 ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
956 FROM biblio.record_entry r
957 LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
958 LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
959 LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
960 LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
961 LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
962 LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
966 ALTER TABLE money.credit_card_payment ADD COLUMN cc_order_number TEXT;
968 -- Changing return types requires explicit dropping of old versions
969 DROP FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL );
970 DROP FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL );
971 DROP FUNCTION action.item_user_circ_test( INT, BIGINT, INT );
972 DROP FUNCTION action.item_user_renew_test( INT, BIGINT, INT );
975 CREATE TYPE action.found_circ_matrix_matchpoint AS ( success BOOL, matchpoint config.circ_matrix_matchpoint, buildrows INT[] );
977 -- Helper function - For manual calling, it can be easier to pass in IDs instead of objects
978 CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.found_circ_matrix_matchpoint AS $func$
980 item_object asset.copy%ROWTYPE;
981 user_object actor.usr%ROWTYPE;
983 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
984 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
986 RETURN QUERY SELECT * FROM action.find_circ_matrix_matchpoint( context_ou, item_object, user_object, renewal );
988 $func$ LANGUAGE plpgsql;
990 CREATE TYPE action.circ_matrix_test_result AS ( success BOOL, fail_part TEXT, buildrows INT[], matchpoint INT, circulate BOOL, duration_rule INT, recurring_fine_rule INT, max_fine_rule INT, hard_due_date INT, renewals INT, grace_period INTERVAL );
992 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
994 user_object actor.usr%ROWTYPE;
995 standing_penalty config.standing_penalty%ROWTYPE;
996 item_object asset.copy%ROWTYPE;
997 item_status_object config.copy_status%ROWTYPE;
998 item_location_object asset.copy_location%ROWTYPE;
999 result action.circ_matrix_test_result;
1000 circ_test action.found_circ_matrix_matchpoint;
1001 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
1002 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
1003 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
1004 hold_ratio action.hold_stats%ROWTYPE;
1007 context_org_list INT[];
1010 -- Assume success unless we hit a failure condition
1011 result.success := TRUE;
1013 -- Fail if the user is BARRED
1014 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
1016 -- Fail if we couldn't find the user
1017 IF user_object.id IS NULL THEN
1018 result.fail_part := 'no_user';
1019 result.success := FALSE;
1025 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
1027 -- Fail if we couldn't find the item
1028 IF item_object.id IS NULL THEN
1029 result.fail_part := 'no_item';
1030 result.success := FALSE;
1036 IF user_object.barred IS TRUE THEN
1037 result.fail_part := 'actor.usr.barred';
1038 result.success := FALSE;
1043 -- Fail if the item can't circulate
1044 IF item_object.circulate IS FALSE THEN
1045 result.fail_part := 'asset.copy.circulate';
1046 result.success := FALSE;
1051 -- Fail if the item isn't in a circulateable status on a non-renewal
1052 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
1053 result.fail_part := 'asset.copy.status';
1054 result.success := FALSE;
1057 ELSIF renewal AND item_object.status <> 1 THEN
1058 result.fail_part := 'asset.copy.status';
1059 result.success := FALSE;
1064 -- Fail if the item can't circulate because of the shelving location
1065 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
1066 IF item_location_object.circulate IS FALSE THEN
1067 result.fail_part := 'asset.copy_location.circulate';
1068 result.success := FALSE;
1073 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
1075 circ_matchpoint := circ_test.matchpoint;
1076 result.matchpoint := circ_matchpoint.id;
1077 result.circulate := circ_matchpoint.circulate;
1078 result.duration_rule := circ_matchpoint.duration_rule;
1079 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
1080 result.max_fine_rule := circ_matchpoint.max_fine_rule;
1081 result.hard_due_date := circ_matchpoint.hard_due_date;
1082 result.renewals := circ_matchpoint.renewals;
1083 result.buildrows := circ_test.buildrows;
1085 -- Fail if we couldn't find a matchpoint
1086 IF circ_test.success = false THEN
1087 result.fail_part := 'no_matchpoint';
1088 result.success := FALSE;
1091 RETURN; -- All tests after this point require a matchpoint. No sense in running on an incomplete or missing one.
1094 -- Apparently....use the circ matchpoint org unit to determine what org units are valid.
1095 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_matchpoint.org_unit );
1098 penalty_type = '%RENEW%';
1100 penalty_type = '%CIRC%';
1103 FOR standing_penalty IN
1104 SELECT DISTINCT csp.*
1105 FROM actor.usr_standing_penalty usp
1106 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
1107 WHERE usr = match_user
1108 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
1109 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
1110 AND csp.block_list LIKE penalty_type LOOP
1112 result.fail_part := standing_penalty.name;
1113 result.success := FALSE;
1118 -- Fail if the test is set to hard non-circulating
1119 IF circ_matchpoint.circulate IS FALSE THEN
1120 result.fail_part := 'config.circ_matrix_test.circulate';
1121 result.success := FALSE;
1126 -- Fail if the total copy-hold ratio is too low
1127 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
1128 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
1129 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
1130 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
1131 result.success := FALSE;
1137 -- Fail if the available copy-hold ratio is too low
1138 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
1139 IF hold_ratio.hold_count IS NULL THEN
1140 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
1142 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
1143 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
1144 result.success := FALSE;
1150 -- Fail if the user has too many items with specific circ_modifiers checked out
1151 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
1152 SELECT INTO items_out COUNT(*)
1153 FROM action.circulation circ
1154 JOIN asset.copy cp ON (cp.id = circ.target_copy)
1155 WHERE circ.usr = match_user
1156 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
1157 AND circ.checkin_time IS NULL
1158 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
1159 AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
1160 IF items_out >= out_by_circ_mod.items_out THEN
1161 result.fail_part := 'config.circ_matrix_circ_mod_test';
1162 result.success := FALSE;
1168 -- If we passed everything, return the successful matchpoint id
1175 $func$ LANGUAGE plpgsql;
1177 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
1178 SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
1179 $func$ LANGUAGE SQL;
1181 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
1182 SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
1183 $func$ LANGUAGE SQL;
1186 CREATE OR REPLACE FUNCTION asset.staff_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$
1191 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;
1193 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
1198 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1202 actor.org_unit_descendants(ans.id) d
1203 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1204 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1208 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1215 $f$ LANGUAGE PLPGSQL;
1217 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$
1222 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;
1224 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
1229 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1233 actor.org_unit_descendants(ans.id) d
1234 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1235 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1239 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1246 $f$ LANGUAGE PLPGSQL;
1248 DROP FUNCTION asset.staff_ou_metarecord_copy_count (INT, BIGINT);
1249 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$
1254 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;
1256 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
1261 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1265 actor.org_unit_descendants(ans.id) d
1266 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1267 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1268 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
1272 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1279 $f$ LANGUAGE PLPGSQL;
1281 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$
1286 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;
1288 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
1293 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1297 actor.org_unit_descendants(ans.id) d
1298 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1299 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1300 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
1304 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1311 $f$ LANGUAGE PLPGSQL;
1315 UPDATE config.org_unit_setting_type
1316 SET description = 'Amount of time before a hold expires at which point the patron should be alerted. Examples: "5 days", "1 hour"'
1317 WHERE label = 'Holds: Expire Alert Interval';
1319 UPDATE config.org_unit_setting_type
1320 SET description = 'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the default estimated length of time to assume an item will be checked out. Examples: "3 weeks", "7 days"'
1321 WHERE label = 'Holds: Default Estimated Wait';
1323 UPDATE config.org_unit_setting_type
1324 SET description = 'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the minimum estimated length of time to assume an item will be checked out. Examples: "1 week", "5 days"'
1325 WHERE label = 'Holds: Minimum Estimated Wait';
1327 UPDATE config.org_unit_setting_type
1328 SET description = 'The purpose is to provide an interval of time after an item goes into the on-holds-shelf status before it appears to patrons that it is actually on the holds shelf. This gives staff time to process the item before it shows as ready-for-pickup. Examples: "5 days", "1 hour"'
1329 WHERE label = 'Hold Shelf Status Delay';
1332 UPDATE config.metabib_field
1333 SET xpath = $$//mods32:mods/mods32:subject$$
1334 WHERE field_class = 'subject' AND name = 'complete';
1336 UPDATE config.metabib_field
1337 SET xpath = $$//marc:datafield[@tag='099']$$
1338 WHERE field_class = 'identifier' AND name = 'bibcn';
1341 CREATE TABLE config.record_attr_definition (
1342 name TEXT PRIMARY KEY,
1343 label TEXT NOT NULL, -- I18N
1345 filter BOOL NOT NULL DEFAULT TRUE, -- becomes QP filter if true
1346 sorter BOOL NOT NULL DEFAULT FALSE, -- becomes QP sort() axis if true
1348 -- For pre-extracted fields. Takes the first occurance, uses naive subfield ordering
1349 tag TEXT, -- LIKE format
1350 sf_list TEXT, -- pile-o-values, like 'abcd' for a and b and c and d
1352 -- This is used for both tag/sf and xpath entries
1355 -- For xpath-extracted attrs
1357 format TEXT REFERENCES config.xml_transform (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1362 fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
1364 -- For phys-char fields
1365 phys_char_sf INT REFERENCES config.marc21_physical_characteristic_subfield_map (id)
1368 CREATE TABLE config.record_attr_index_norm_map (
1369 id SERIAL PRIMARY KEY,
1370 attr TEXT NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1371 norm INT NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1373 pos INT NOT NULL DEFAULT 0
1376 CREATE TABLE config.coded_value_map (
1377 id SERIAL PRIMARY KEY,
1378 ctype TEXT NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1380 value TEXT NOT NULL,
1384 -- record attributes
1385 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('alph','Alph','Alph');
1386 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('audience','Audn','Audn');
1387 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('bib_level','BLvl','BLvl');
1388 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('biog','Biog','Biog');
1389 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('conf','Conf','Conf');
1390 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('control_type','Ctrl','Ctrl');
1391 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ctry','Ctry','Ctry');
1392 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date1','Date1','Date1');
1393 INSERT INTO config.record_attr_definition (name,label,fixed_field,sorter,filter) values ('pubdate','Pub Date','Date1',TRUE,FALSE);
1394 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date2','Date2','Date2');
1395 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('cat_form','Desc','Desc');
1396 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('pub_status','DtSt','DtSt');
1397 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('enc_level','ELvl','ELvl');
1398 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('fest','Fest','Fest');
1399 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_form','Form','Form');
1400 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('gpub','GPub','GPub');
1401 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ills','Ills','Ills');
1402 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('indx','Indx','Indx');
1403 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_lang','Lang','Lang');
1404 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('lit_form','LitF','LitF');
1405 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('mrec','MRec','MRec');
1406 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ff_sl','S/L','S/L');
1407 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('type_mat','TMat','TMat');
1408 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_type','Type','Type');
1409 INSERT INTO config.record_attr_definition (name,label,phys_char_sf) values ('vr_format','Videorecording format',72);
1410 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('titlesort','Title',TRUE,FALSE,'tnf');
1411 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('authorsort','Author',TRUE,FALSE,'1%');
1413 INSERT INTO config.upgrade_log (version) VALUES ('0624'); -- miker/tsbere
1414 -- Cont was typod as Conf. Update the old entries.
1415 UPDATE config.marc21_ff_pos_map SET fixed_field = 'Cont' WHERE fixed_field = 'Conf' AND length > 1;
1416 -- Conf thus didn't exist. Add it.
1417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 11, 1, ' ');
1418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 11, 1, ' ');
1419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 29, 1, ' ');
1420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 29, 1, ' ');
1422 INSERT INTO config.coded_value_map (ctype,code,value,description)
1423 SELECT 'item_lang' AS ctype, code, value, NULL FROM config.language_map
1425 SELECT 'bib_level' AS ctype, code, value, NULL FROM config.bib_level_map
1427 SELECT 'item_form' AS ctype, code, value, NULL FROM config.item_form_map
1429 SELECT 'item_type' AS ctype, code, value, NULL FROM config.item_type_map
1431 SELECT 'lit_form' AS ctype, code, value, description FROM config.lit_form_map
1433 SELECT 'audience' AS ctype, code, value, description FROM config.audience_map
1435 SELECT 'vr_format' AS ctype, code, value, NULL FROM config.videorecording_format_map;
1437 ALTER TABLE config.i18n_locale DROP CONSTRAINT i18n_locale_marc_code_fkey;
1439 DROP TABLE config.language_map;
1440 DROP TABLE config.bib_level_map;
1441 DROP TABLE config.item_form_map;
1442 DROP TABLE config.item_type_map;
1443 DROP TABLE config.lit_form_map;
1444 DROP TABLE config.audience_map;
1445 DROP TABLE config.videorecording_format_map;
1447 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clm.value' AND ccvm.ctype = 'item_lang' AND identity_value = ccvm.code;
1448 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cblvl.value' AND ccvm.ctype = 'bib_level' AND identity_value = ccvm.code;
1449 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cifm.value' AND ccvm.ctype = 'item_form' AND identity_value = ccvm.code;
1450 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'citm.value' AND ccvm.ctype = 'item_type' AND identity_value = ccvm.code;
1451 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clfm.value' AND ccvm.ctype = 'lit_form' AND identity_value = ccvm.code;
1452 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cam.value' AND ccvm.ctype = 'audience' AND identity_value = ccvm.code;
1453 UPDATE config.i18n_core SET fq_field = 'ccvm.value', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cvrfm.value' AND ccvm.ctype = 'vr_format' AND identity_value = ccvm.code;
1455 UPDATE config.i18n_core SET fq_field = 'ccvm.description', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'clfm.description' AND ccvm.ctype = 'lit_form' AND identity_value = ccvm.code;
1456 UPDATE config.i18n_core SET fq_field = 'ccvm.description', identity_value = ccvm.id FROM config.coded_value_map AS ccvm WHERE fq_field = 'cam.description' AND ccvm.ctype = 'audience' AND identity_value = ccvm.code;
1458 CREATE VIEW config.language_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_lang';
1459 CREATE VIEW config.bib_level_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'bib_level';
1460 CREATE VIEW config.item_form_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_form';
1461 CREATE VIEW config.item_type_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_type';
1462 CREATE VIEW config.lit_form_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'lit_form';
1463 CREATE VIEW config.audience_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'audience';
1464 CREATE VIEW config.videorecording_format_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'vr_format';
1466 CREATE TABLE metabib.record_attr (
1467 id BIGINT PRIMARY KEY REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
1468 attrs HSTORE NOT NULL DEFAULT ''::HSTORE
1470 CREATE INDEX metabib_svf_attrs_idx ON metabib.record_attr USING GIST (attrs);
1471 CREATE INDEX metabib_svf_date1_idx ON metabib.record_attr ( (attrs->'date1') );
1472 CREATE INDEX metabib_svf_dates_idx ON metabib.record_attr ( (attrs->'date1'), (attrs->'date2') );
1474 INSERT INTO metabib.record_attr (id,attrs)
1475 SELECT DISTINCT ON (mrd.record) mrd.record, hstore(mrd) - '{id,record}'::TEXT[] FROM metabib.rec_descriptor mrd;
1477 -- Back-compat view ... we're moving to an HSTORE world
1478 CREATE TYPE metabib.rec_desc_type AS (
1496 DROP TABLE metabib.rec_descriptor CASCADE;
1498 CREATE VIEW metabib.rec_descriptor AS
1501 (populate_record(NULL::metabib.rec_desc_type, attrs)).*
1502 FROM metabib.record_attr;
1504 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
1511 retval config.marc21_rec_type_map%ROWTYPE;
1513 ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
1515 IF ldr IS NULL OR ldr = '' THEN
1516 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1520 SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
1521 SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
1524 tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
1525 bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
1527 -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
1529 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
1532 IF retval.code IS NULL THEN
1533 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1538 $func$ LANGUAGE PLPGSQL;
1540 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
1541 SELECT * FROM vandelay.marc21_record_type( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1542 $func$ LANGUAGE SQL;
1544 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
1551 rtype := (vandelay.marc21_record_type( marc )).code;
1552 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
1553 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1554 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1557 val := REPEAT( ff_pos.default_val, ff_pos.length );
1563 $func$ LANGUAGE PLPGSQL;
1565 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
1566 SELECT * FROM vandelay.marc21_extract_fixed_field( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2 );
1567 $func$ LANGUAGE SQL;
1569 CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
1570 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
1575 output biblio.record_ff_map%ROWTYPE;
1577 rtype := (vandelay.marc21_record_type( marc )).code;
1579 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
1580 output.ff_name := ff_pos.fixed_field;
1581 output.ff_value := NULL;
1583 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(tag) || '"]/text()', marc ) ) x(value) LOOP
1584 output.ff_value := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1585 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
1587 output.ff_value := NULL;
1594 $func$ LANGUAGE PLPGSQL;
1596 CREATE OR REPLACE FUNCTION biblio.marc21_extract_all_fixed_fields( rid BIGINT ) RETURNS SETOF biblio.record_ff_map AS $func$
1597 SELECT $1 AS record, ff_name, ff_value FROM vandelay.marc21_extract_all_fixed_fields( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1598 $func$ LANGUAGE SQL;
1600 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1604 ptype config.marc21_physical_characteristic_type_map%ROWTYPE;
1605 psf config.marc21_physical_characteristic_subfield_map%ROWTYPE;
1606 pval config.marc21_physical_characteristic_value_map%ROWTYPE;
1607 retval biblio.marc21_physical_characteristics%ROWTYPE;
1610 _007 := oils_xpath_string( '//*[@tag="007"]', marc );
1612 IF _007 IS NOT NULL AND _007 <> '' THEN
1613 SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
1615 IF ptype.ptype_key IS NOT NULL THEN
1616 FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
1617 SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007, psf.start_pos + 1, psf.length );
1619 IF pval.id IS NOT NULL THEN
1622 retval.ptype := ptype.ptype_key;
1623 retval.subfield := psf.id;
1624 retval.value := pval.id;
1634 $func$ LANGUAGE PLPGSQL;
1636 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1637 SELECT id, $1 AS record, ptype, subfield, value FROM vandelay.marc21_physical_characteristics( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1638 $func$ LANGUAGE SQL;
1640 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
1642 transformed_xml TEXT;
1645 xfrm config.xml_transform%ROWTYPE;
1647 new_attrs HSTORE := ''::HSTORE;
1648 attr_def config.record_attr_definition%ROWTYPE;
1651 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
1652 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
1653 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
1654 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
1655 RETURN NEW; -- and we're done
1658 IF TG_OP = 'UPDATE' THEN -- re-ingest?
1659 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
1661 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
1666 -- Record authority linking
1667 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
1669 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
1672 -- Flatten and insert the mfr data
1673 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
1675 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
1677 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
1678 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
1680 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
1682 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
1683 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
1684 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
1685 WHERE record = NEW.id
1686 AND tag LIKE attr_def.tag
1688 WHEN attr_def.sf_list IS NOT NULL
1689 THEN POSITION(subfield IN attr_def.sf_list) > 0
1696 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
1697 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
1699 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
1701 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
1703 -- See if we can skip the XSLT ... it's expensive
1704 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1705 -- Can't skip the transform
1706 IF xfrm.xslt <> '---' THEN
1707 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
1709 transformed_xml := NEW.marc;
1712 prev_xfrm := xfrm.name;
1715 IF xfrm.name IS NULL THEN
1716 -- just grab the marcxml (empty) transform
1717 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
1718 prev_xfrm := xfrm.name;
1721 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
1723 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
1724 SELECT value::TEXT INTO attr_value
1725 FROM biblio.marc21_physical_characteristics(NEW.id)
1726 WHERE subfield = attr_def.phys_char_sf
1727 LIMIT 1; -- Just in case ...
1731 -- apply index normalizers to attr_value
1733 SELECT n.func AS func,
1734 n.param_count AS param_count,
1736 FROM config.index_normalizer n
1737 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
1738 WHERE attr = attr_def.name
1740 EXECUTE 'SELECT ' || normalizer.func || '(' ||
1741 quote_literal( attr_value ) ||
1743 WHEN normalizer.param_count > 0
1744 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1747 ')' INTO attr_value;
1751 -- Add the new value to the hstore
1752 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
1756 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
1757 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
1759 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
1765 -- Gather and insert the field entry data
1766 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
1768 -- Located URI magic
1769 IF TG_OP = 'INSERT' THEN
1770 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
1772 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
1775 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
1777 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
1781 -- (re)map metarecord-bib linking
1782 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
1783 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
1785 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
1787 ELSE -- we're doing an update, and we're not deleted, remap
1788 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
1790 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
1796 $func$ LANGUAGE PLPGSQL;
1798 DROP FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT );
1800 CREATE OR REPLACE FUNCTION public.approximate_date( TEXT, TEXT ) RETURNS TEXT AS $func$
1801 SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );
1802 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1804 CREATE OR REPLACE FUNCTION public.approximate_low_date( TEXT ) RETURNS TEXT AS $func$
1805 SELECT approximate_date( $1, '0');
1806 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1808 CREATE OR REPLACE FUNCTION public.approximate_high_date( TEXT ) RETURNS TEXT AS $func$
1809 SELECT approximate_date( $1, '9');
1810 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1812 CREATE OR REPLACE FUNCTION public.integer_or_null( TEXT ) RETURNS TEXT AS $func$
1813 SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END
1814 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1816 CREATE OR REPLACE FUNCTION public.content_or_null( TEXT ) RETURNS TEXT AS $func$
1817 SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END
1818 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1820 CREATE OR REPLACE FUNCTION public.force_to_isbn13( TEXT ) RETURNS TEXT AS $func$
1825 # Find the first ISBN, force it to ISBN13 and return it
1829 foreach my $word (split(/\s/, $input)) {
1830 my $isbn = Business::ISBN->new($word);
1832 # First check the checksum; if it is not valid, fix it and add the original
1833 # bad-checksum ISBN to the output
1834 if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
1835 $isbn->fix_checksum();
1838 # If we now have a valid ISBN, force it to ISBN13 and return it
1839 return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
1842 $func$ LANGUAGE PLPERLU;
1844 COMMENT ON FUNCTION public.force_to_isbn13(TEXT) IS $$
1846 * Copyright (C) 2011 Equinox Software
1847 * Mike Rylander <mrylander@gmail.com>
1849 * Inspired by translate_isbn1013
1851 * The force_to_isbn13 function takes an input ISBN and returns the ISBN13
1852 * version without hypens and with a repaired checksum if the checksum was bad
1857 UPDATE config.metabib_field
1858 SET xpath = $$//marc:datafield[@tag='024' and @ind1='1']/marc:subfield[@code='a' or @code='z']$$
1859 WHERE field_class = 'identifier' AND name = 'upc';
1861 UPDATE config.metabib_field
1862 SET xpath = $$//marc:datafield[@tag='024' and @ind1='2']/marc:subfield[@code='a' or @code='z']$$
1863 WHERE field_class = 'identifier' AND name = 'ismn';
1865 UPDATE config.metabib_field
1866 SET xpath = $$//marc:datafield[@tag='024' and @ind1='3']/marc:subfield[@code='a' or @code='z']$$
1867 WHERE field_class = 'identifier' AND name = 'ean';
1869 UPDATE config.metabib_field
1870 SET xpath = $$//marc:datafield[@tag='024' and @ind1='0']/marc:subfield[@code='a' or @code='z']$$
1871 WHERE field_class = 'identifier' AND name = 'isrc';
1873 UPDATE config.metabib_field
1874 SET xpath = $$//marc:datafield[@tag='024' and @ind1='4']/marc:subfield[@code='a' or @code='z']$$
1875 WHERE field_class = 'identifier' AND name = 'sici';
1878 INSERT into config.org_unit_setting_type
1879 ( name, label, description, datatype ) VALUES
1881 ( 'ui.patron.edit.au.active.show',
1882 oils_i18n_gettext('ui.patron.edit.au.active.show', 'GUI: Show active field on patron registration', 'coust', 'label'),
1883 oils_i18n_gettext('ui.patron.edit.au.active.show', 'The active field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1885 ( 'ui.patron.edit.au.active.suggest',
1886 oils_i18n_gettext('ui.patron.edit.au.active.suggest', 'GUI: Suggest active field on patron registration', 'coust', 'label'),
1887 oils_i18n_gettext('ui.patron.edit.au.active.suggest', 'The active field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1889 ( 'ui.patron.edit.au.alert_message.show',
1890 oils_i18n_gettext('ui.patron.edit.au.alert_message.show', 'GUI: Show alert_message field on patron registration', 'coust', 'label'),
1891 oils_i18n_gettext('ui.patron.edit.au.alert_message.show', 'The alert_message field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1893 ( 'ui.patron.edit.au.alert_message.suggest',
1894 oils_i18n_gettext('ui.patron.edit.au.alert_message.suggest', 'GUI: Suggest alert_message field on patron registration', 'coust', 'label'),
1895 oils_i18n_gettext('ui.patron.edit.au.alert_message.suggest', 'The alert_message field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1897 ( 'ui.patron.edit.au.alias.show',
1898 oils_i18n_gettext('ui.patron.edit.au.alias.show', 'GUI: Show alias field on patron registration', 'coust', 'label'),
1899 oils_i18n_gettext('ui.patron.edit.au.alias.show', 'The alias field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1901 ( 'ui.patron.edit.au.alias.suggest',
1902 oils_i18n_gettext('ui.patron.edit.au.alias.suggest', 'GUI: Suggest alias field on patron registration', 'coust', 'label'),
1903 oils_i18n_gettext('ui.patron.edit.au.alias.suggest', 'The alias field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1905 ( 'ui.patron.edit.au.barred.show',
1906 oils_i18n_gettext('ui.patron.edit.au.barred.show', 'GUI: Show barred field on patron registration', 'coust', 'label'),
1907 oils_i18n_gettext('ui.patron.edit.au.barred.show', 'The barred field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1909 ( 'ui.patron.edit.au.barred.suggest',
1910 oils_i18n_gettext('ui.patron.edit.au.barred.suggest', 'GUI: Suggest barred field on patron registration', 'coust', 'label'),
1911 oils_i18n_gettext('ui.patron.edit.au.barred.suggest', 'The barred field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1913 ( 'ui.patron.edit.au.claims_never_checked_out_count.show',
1914 oils_i18n_gettext('ui.patron.edit.au.claims_never_checked_out_count.show', 'GUI: Show claims_never_checked_out_count field on patron registration', 'coust', 'label'),
1915 oils_i18n_gettext('ui.patron.edit.au.claims_never_checked_out_count.show', 'The claims_never_checked_out_count field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1917 ( 'ui.patron.edit.au.claims_never_checked_out_count.suggest',
1918 oils_i18n_gettext('ui.patron.edit.au.claims_never_checked_out_count.suggest', 'GUI: Suggest claims_never_checked_out_count field on patron registration', 'coust', 'label'),
1919 oils_i18n_gettext('ui.patron.edit.au.claims_never_checked_out_count.suggest', 'The claims_never_checked_out_count field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1921 ( 'ui.patron.edit.au.claims_returned_count.show',
1922 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.show', 'GUI: Show claims_returned_count field on patron registration', 'coust', 'label'),
1923 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.show', 'The claims_returned_count field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1925 ( 'ui.patron.edit.au.claims_returned_count.suggest',
1926 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.suggest', 'GUI: Suggest claims_returned_count field on patron registration', 'coust', 'label'),
1927 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.suggest', 'The claims_returned_count field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1929 ( 'ui.patron.edit.au.day_phone.example',
1930 oils_i18n_gettext('ui.patron.edit.au.day_phone.example', 'GUI: Example for day_phone field on patron registration', 'coust', 'label'),
1931 oils_i18n_gettext('ui.patron.edit.au.day_phone.example', 'The Example for validation on the day_phone field in patron registration.', 'coust', 'description'),
1933 ( 'ui.patron.edit.au.day_phone.regex',
1934 oils_i18n_gettext('ui.patron.edit.au.day_phone.regex', 'GUI: Regex for day_phone field on patron registration', 'coust', 'label'),
1935 oils_i18n_gettext('ui.patron.edit.au.day_phone.regex', 'The Regular Expression for validation on the day_phone field in patron registration.', 'coust', 'description'),
1937 ( 'ui.patron.edit.au.day_phone.require',
1938 oils_i18n_gettext('ui.patron.edit.au.day_phone.require', 'GUI: Require day_phone field on patron registration', 'coust', 'label'),
1939 oils_i18n_gettext('ui.patron.edit.au.day_phone.require', 'The day_phone field will be required on the patron registration screen.', 'coust', 'description'),
1941 ( 'ui.patron.edit.au.day_phone.show',
1942 oils_i18n_gettext('ui.patron.edit.au.day_phone.show', 'GUI: Show day_phone field on patron registration', 'coust', 'label'),
1943 oils_i18n_gettext('ui.patron.edit.au.day_phone.show', 'The day_phone field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1945 ( 'ui.patron.edit.au.day_phone.suggest',
1946 oils_i18n_gettext('ui.patron.edit.au.day_phone.suggest', 'GUI: Suggest day_phone field on patron registration', 'coust', 'label'),
1947 oils_i18n_gettext('ui.patron.edit.au.day_phone.suggest', 'The day_phone field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1949 ( 'ui.patron.edit.au.dob.calendar',
1950 oils_i18n_gettext('ui.patron.edit.au.dob.calendar', 'GUI: Show calendar widget for dob field on patron registration', 'coust', 'label'),
1951 oils_i18n_gettext('ui.patron.edit.au.dob.calendar', 'If set the calendar widget will appear when editing the dob field on the patron registration form.', 'coust', 'description'),
1953 ( 'ui.patron.edit.au.dob.require',
1954 oils_i18n_gettext('ui.patron.edit.au.dob.require', 'GUI: Require dob field on patron registration', 'coust', 'label'),
1955 oils_i18n_gettext('ui.patron.edit.au.dob.require', 'The dob field will be required on the patron registration screen.', 'coust', 'description'),
1957 ( 'ui.patron.edit.au.dob.show',
1958 oils_i18n_gettext('ui.patron.edit.au.dob.show', 'GUI: Show dob field on patron registration', 'coust', 'label'),
1959 oils_i18n_gettext('ui.patron.edit.au.dob.show', 'The dob field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1961 ( 'ui.patron.edit.au.dob.suggest',
1962 oils_i18n_gettext('ui.patron.edit.au.dob.suggest', 'GUI: Suggest dob field on patron registration', 'coust', 'label'),
1963 oils_i18n_gettext('ui.patron.edit.au.dob.suggest', 'The dob field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1965 ( 'ui.patron.edit.au.email.example',
1966 oils_i18n_gettext('ui.patron.edit.au.email.example', 'GUI: Example for email field on patron registration', 'coust', 'label'),
1967 oils_i18n_gettext('ui.patron.edit.au.email.example', 'The Example for validation on the email field in patron registration.', 'coust', 'description'),
1969 ( 'ui.patron.edit.au.email.regex',
1970 oils_i18n_gettext('ui.patron.edit.au.email.regex', 'GUI: Regex for email field on patron registration', 'coust', 'label'),
1971 oils_i18n_gettext('ui.patron.edit.au.email.regex', 'The Regular Expression for validation on the email field in patron registration.', 'coust', 'description'),
1973 ( 'ui.patron.edit.au.email.require',
1974 oils_i18n_gettext('ui.patron.edit.au.email.require', 'GUI: Require email field on patron registration', 'coust', 'label'),
1975 oils_i18n_gettext('ui.patron.edit.au.email.require', 'The email field will be required on the patron registration screen.', 'coust', 'description'),
1977 ( 'ui.patron.edit.au.email.show',
1978 oils_i18n_gettext('ui.patron.edit.au.email.show', 'GUI: Show email field on patron registration', 'coust', 'label'),
1979 oils_i18n_gettext('ui.patron.edit.au.email.show', 'The email field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
1981 ( 'ui.patron.edit.au.email.suggest',
1982 oils_i18n_gettext('ui.patron.edit.au.email.suggest', 'GUI: Suggest email field on patron registration', 'coust', 'label'),
1983 oils_i18n_gettext('ui.patron.edit.au.email.suggest', 'The email field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
1985 ( 'ui.patron.edit.au.evening_phone.example',
1986 oils_i18n_gettext('ui.patron.edit.au.evening_phone.example', 'GUI: Example for evening_phone field on patron registration', 'coust', 'label'),
1987 oils_i18n_gettext('ui.patron.edit.au.evening_phone.example', 'The Example for validation on the evening_phone field in patron registration.', 'coust', 'description'),
1989 ( 'ui.patron.edit.au.evening_phone.regex',
1990 oils_i18n_gettext('ui.patron.edit.au.evening_phone.regex', 'GUI: Regex for evening_phone field on patron registration', 'coust', 'label'),
1991 oils_i18n_gettext('ui.patron.edit.au.evening_phone.regex', 'The Regular Expression for validation on the evening_phone field in patron registration.', 'coust', 'description'),
1993 ( 'ui.patron.edit.au.evening_phone.require',
1994 oils_i18n_gettext('ui.patron.edit.au.evening_phone.require', 'GUI: Require evening_phone field on patron registration', 'coust', 'label'),
1995 oils_i18n_gettext('ui.patron.edit.au.evening_phone.require', 'The evening_phone field will be required on the patron registration screen.', 'coust', 'description'),
1997 ( 'ui.patron.edit.au.evening_phone.show',
1998 oils_i18n_gettext('ui.patron.edit.au.evening_phone.show', 'GUI: Show evening_phone field on patron registration', 'coust', 'label'),
1999 oils_i18n_gettext('ui.patron.edit.au.evening_phone.show', 'The evening_phone field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2001 ( 'ui.patron.edit.au.evening_phone.suggest',
2002 oils_i18n_gettext('ui.patron.edit.au.evening_phone.suggest', 'GUI: Suggest evening_phone field on patron registration', 'coust', 'label'),
2003 oils_i18n_gettext('ui.patron.edit.au.evening_phone.suggest', 'The evening_phone field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2005 ( 'ui.patron.edit.au.ident_value.show',
2006 oils_i18n_gettext('ui.patron.edit.au.ident_value.show', 'GUI: Show ident_value field on patron registration', 'coust', 'label'),
2007 oils_i18n_gettext('ui.patron.edit.au.ident_value.show', 'The ident_value field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2009 ( 'ui.patron.edit.au.ident_value.suggest',
2010 oils_i18n_gettext('ui.patron.edit.au.ident_value.suggest', 'GUI: Suggest ident_value field on patron registration', 'coust', 'label'),
2011 oils_i18n_gettext('ui.patron.edit.au.ident_value.suggest', 'The ident_value field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2013 ( 'ui.patron.edit.au.ident_value2.show',
2014 oils_i18n_gettext('ui.patron.edit.au.ident_value2.show', 'GUI: Show ident_value2 field on patron registration', 'coust', 'label'),
2015 oils_i18n_gettext('ui.patron.edit.au.ident_value2.show', 'The ident_value2 field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2017 ( 'ui.patron.edit.au.ident_value2.suggest',
2018 oils_i18n_gettext('ui.patron.edit.au.ident_value2.suggest', 'GUI: Suggest ident_value2 field on patron registration', 'coust', 'label'),
2019 oils_i18n_gettext('ui.patron.edit.au.ident_value2.suggest', 'The ident_value2 field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2021 ( 'ui.patron.edit.au.juvenile.show',
2022 oils_i18n_gettext('ui.patron.edit.au.juvenile.show', 'GUI: Show juvenile field on patron registration', 'coust', 'label'),
2023 oils_i18n_gettext('ui.patron.edit.au.juvenile.show', 'The juvenile field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2025 ( 'ui.patron.edit.au.juvenile.suggest',
2026 oils_i18n_gettext('ui.patron.edit.au.juvenile.suggest', 'GUI: Suggest juvenile field on patron registration', 'coust', 'label'),
2027 oils_i18n_gettext('ui.patron.edit.au.juvenile.suggest', 'The juvenile field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2029 ( 'ui.patron.edit.au.master_account.show',
2030 oils_i18n_gettext('ui.patron.edit.au.master_account.show', 'GUI: Show master_account field on patron registration', 'coust', 'label'),
2031 oils_i18n_gettext('ui.patron.edit.au.master_account.show', 'The master_account field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2033 ( 'ui.patron.edit.au.master_account.suggest',
2034 oils_i18n_gettext('ui.patron.edit.au.master_account.suggest', 'GUI: Suggest master_account field on patron registration', 'coust', 'label'),
2035 oils_i18n_gettext('ui.patron.edit.au.master_account.suggest', 'The master_account field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2037 ( 'ui.patron.edit.au.other_phone.example',
2038 oils_i18n_gettext('ui.patron.edit.au.other_phone.example', 'GUI: Example for other_phone field on patron registration', 'coust', 'label'),
2039 oils_i18n_gettext('ui.patron.edit.au.other_phone.example', 'The Example for validation on the other_phone field in patron registration.', 'coust', 'description'),
2041 ( 'ui.patron.edit.au.other_phone.regex',
2042 oils_i18n_gettext('ui.patron.edit.au.other_phone.regex', 'GUI: Regex for other_phone field on patron registration', 'coust', 'label'),
2043 oils_i18n_gettext('ui.patron.edit.au.other_phone.regex', 'The Regular Expression for validation on the other_phone field in patron registration.', 'coust', 'description'),
2045 ( 'ui.patron.edit.au.other_phone.require',
2046 oils_i18n_gettext('ui.patron.edit.au.other_phone.require', 'GUI: Require other_phone field on patron registration', 'coust', 'label'),
2047 oils_i18n_gettext('ui.patron.edit.au.other_phone.require', 'The other_phone field will be required on the patron registration screen.', 'coust', 'description'),
2049 ( 'ui.patron.edit.au.other_phone.show',
2050 oils_i18n_gettext('ui.patron.edit.au.other_phone.show', 'GUI: Show other_phone field on patron registration', 'coust', 'label'),
2051 oils_i18n_gettext('ui.patron.edit.au.other_phone.show', 'The other_phone field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2053 ( 'ui.patron.edit.au.other_phone.suggest',
2054 oils_i18n_gettext('ui.patron.edit.au.other_phone.suggest', 'GUI: Suggest other_phone field on patron registration', 'coust', 'label'),
2055 oils_i18n_gettext('ui.patron.edit.au.other_phone.suggest', 'The other_phone field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2057 ( 'ui.patron.edit.au.second_given_name.show',
2058 oils_i18n_gettext('ui.patron.edit.au.second_given_name.show', 'GUI: Show second_given_name field on patron registration', 'coust', 'label'),
2059 oils_i18n_gettext('ui.patron.edit.au.second_given_name.show', 'The second_given_name field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2061 ( 'ui.patron.edit.au.second_given_name.suggest',
2062 oils_i18n_gettext('ui.patron.edit.au.second_given_name.suggest', 'GUI: Suggest second_given_name field on patron registration', 'coust', 'label'),
2063 oils_i18n_gettext('ui.patron.edit.au.second_given_name.suggest', 'The second_given_name field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2065 ( 'ui.patron.edit.au.suffix.show',
2066 oils_i18n_gettext('ui.patron.edit.au.suffix.show', 'GUI: Show suffix field on patron registration', 'coust', 'label'),
2067 oils_i18n_gettext('ui.patron.edit.au.suffix.show', 'The suffix field will be shown on the patron registration screen. Showing a field makes it appear with required fields even when not required. If the field is required this setting is ignored.', 'coust', 'description'),
2069 ( 'ui.patron.edit.au.suffix.suggest',
2070 oils_i18n_gettext('ui.patron.edit.au.suffix.suggest', 'GUI: Suggest suffix field on patron registration', 'coust', 'label'),
2071 oils_i18n_gettext('ui.patron.edit.au.suffix.suggest', 'The suffix field will be suggested on the patron registration screen. Suggesting a field makes it appear when suggested fields are shown. If the field is shown or required this setting is ignored.', 'coust', 'description'),
2073 ( 'ui.patron.edit.aua.county.require',
2074 oils_i18n_gettext('ui.patron.edit.aua.county.require', 'GUI: Require county field on patron registration', 'coust', 'label'),
2075 oils_i18n_gettext('ui.patron.edit.aua.county.require', 'The county field will be required on the patron registration screen.', 'coust', 'description'),
2077 ( 'ui.patron.edit.aua.post_code.example',
2078 oils_i18n_gettext('ui.patron.edit.aua.post_code.example', 'GUI: Example for post_code field on patron registration', 'coust', 'label'),
2079 oils_i18n_gettext('ui.patron.edit.aua.post_code.example', 'The Example for validation on the post_code field in patron registration.', 'coust', 'description'),
2081 ( 'ui.patron.edit.aua.post_code.regex',
2082 oils_i18n_gettext('ui.patron.edit.aua.post_code.regex', 'GUI: Regex for post_code field on patron registration', 'coust', 'label'),
2083 oils_i18n_gettext('ui.patron.edit.aua.post_code.regex', 'The Regular Expression for validation on the post_code field in patron registration.', 'coust', 'description'),
2085 ( 'ui.patron.edit.default_suggested',
2086 oils_i18n_gettext('ui.patron.edit.default_suggested', 'GUI: Default showing suggested patron registration fields', 'coust', 'label'),
2087 oils_i18n_gettext('ui.patron.edit.default_suggested', 'Instead of All fields, show just suggested fields in patron registration by default.', 'coust', 'description'),
2089 ( 'ui.patron.edit.phone.example',
2090 oils_i18n_gettext('ui.patron.edit.phone.example', 'GUI: Example for phone fields on patron registration', 'coust', 'label'),
2091 oils_i18n_gettext('ui.patron.edit.phone.example', 'The Example for validation on phone fields in patron registration. Applies to all phone fields without their own setting.', 'coust', 'description'),
2093 ( 'ui.patron.edit.phone.regex',
2094 oils_i18n_gettext('ui.patron.edit.phone.regex', 'GUI: Regex for phone fields on patron registration', 'coust', 'label'),
2095 oils_i18n_gettext('ui.patron.edit.phone.regex', 'The Regular Expression for validation on phone fields in patron registration. Applies to all phone fields without their own setting.', 'coust', 'description'),
2098 -- update actor.usr_address indexes
2099 DROP INDEX IF EXISTS actor.actor_usr_addr_street1_idx;
2100 DROP INDEX IF EXISTS actor.actor_usr_addr_street2_idx;
2101 DROP INDEX IF EXISTS actor.actor_usr_addr_city_idx;
2102 DROP INDEX IF EXISTS actor.actor_usr_addr_state_idx;
2103 DROP INDEX IF EXISTS actor.actor_usr_addr_post_code_idx;
2105 CREATE INDEX actor_usr_addr_street1_idx ON actor.usr_address (evergreen.lowercase(street1));
2106 CREATE INDEX actor_usr_addr_street2_idx ON actor.usr_address (evergreen.lowercase(street2));
2107 CREATE INDEX actor_usr_addr_city_idx ON actor.usr_address (evergreen.lowercase(city));
2108 CREATE INDEX actor_usr_addr_state_idx ON actor.usr_address (evergreen.lowercase(state));
2109 CREATE INDEX actor_usr_addr_post_code_idx ON actor.usr_address (evergreen.lowercase(post_code));
2111 -- update actor.usr indexes
2112 DROP INDEX IF EXISTS actor.actor_usr_first_given_name_idx;
2113 DROP INDEX IF EXISTS actor.actor_usr_second_given_name_idx;
2114 DROP INDEX IF EXISTS actor.actor_usr_family_name_idx;
2115 DROP INDEX IF EXISTS actor.actor_usr_email_idx;
2116 DROP INDEX IF EXISTS actor.actor_usr_day_phone_idx;
2117 DROP INDEX IF EXISTS actor.actor_usr_evening_phone_idx;
2118 DROP INDEX IF EXISTS actor.actor_usr_other_phone_idx;
2119 DROP INDEX IF EXISTS actor.actor_usr_ident_value_idx;
2120 DROP INDEX IF EXISTS actor.actor_usr_ident_value2_idx;
2122 CREATE INDEX actor_usr_first_given_name_idx ON actor.usr (evergreen.lowercase(first_given_name));
2123 CREATE INDEX actor_usr_second_given_name_idx ON actor.usr (evergreen.lowercase(second_given_name));
2124 CREATE INDEX actor_usr_family_name_idx ON actor.usr (evergreen.lowercase(family_name));
2125 CREATE INDEX actor_usr_email_idx ON actor.usr (evergreen.lowercase(email));
2126 CREATE INDEX actor_usr_day_phone_idx ON actor.usr (evergreen.lowercase(day_phone));
2127 CREATE INDEX actor_usr_evening_phone_idx ON actor.usr (evergreen.lowercase(evening_phone));
2128 CREATE INDEX actor_usr_other_phone_idx ON actor.usr (evergreen.lowercase(other_phone));
2129 CREATE INDEX actor_usr_ident_value_idx ON actor.usr (evergreen.lowercase(ident_value));
2130 CREATE INDEX actor_usr_ident_value2_idx ON actor.usr (evergreen.lowercase(ident_value2));
2132 -- update actor.card indexes
2133 DROP INDEX IF EXISTS actor.actor_card_barcode_evergreen_lowercase_idx;
2134 CREATE INDEX actor_card_barcode_evergreen_lowercase_idx ON actor.card (evergreen.lowercase(barcode));
2136 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
2145 DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
2147 SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
2149 IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
2150 id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
2152 IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
2153 SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
2154 SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
2155 IF exact_id IS NOT NULL THEN
2156 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
2161 IF exact_id IS NULL THEN
2162 FOR attr IN SELECT a.* FROM vandelay.queued_bib_record_attr a JOIN vandelay.bib_attr_definition d ON (d.id = a.field) WHERE record = NEW.id AND d.ident IS TRUE LOOP
2164 -- All numbers? check for an id match
2165 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
2166 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
2167 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
2171 -- Looks like an ISBN? check for an isbn match
2172 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
2173 FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE evergreen.lowercase('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP
2174 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
2176 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
2180 -- subcheck for isbn-as-tcn
2181 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
2182 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2186 -- check for an OCLC tcn_value match
2187 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
2188 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
2189 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2193 -- check for a direct tcn_value match
2194 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
2195 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2198 -- check for a direct item barcode match
2201 FROM biblio.record_entry b
2202 JOIN asset.call_number cn ON (cn.record = b.id)
2203 JOIN asset.copy cp ON (cp.call_number = cn.id)
2204 WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
2206 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
2214 $func$ LANGUAGE PLPGSQL;
2218 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
2219 # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
2220 # thus could probably be considered a derived work, although nothing was
2221 # directly copied - but to err on the safe side of providing attribution:
2222 # Copyright (C) 2007 LibLime
2223 # Copyright (C) 2011 Equinox Software, Inc (Steve Callendar)
2224 # Licensed under the GPL v2 or later
2229 # Converts the callnumber to uppercase
2230 # Strips spaces from start and end of the call number
2231 # Converts anything other than letters, digits, and periods into spaces
2232 # Collapses multiple spaces into a single underscore
2233 my $callnum = uc(shift);
2234 $callnum =~ s/^\s//g;
2235 $callnum =~ s/\s$//g;
2236 # NOTE: this previously used underscores, but this caused sorting issues
2237 # for the "before" half of page 0 on CN browse, sorting CNs containing a
2238 # decimal before "whole number" CNs
2239 $callnum =~ s/[^A-Z0-9_.]/ /g;
2240 $callnum =~ s/ {2,}/ /g;
2243 $func$ LANGUAGE PLPERLU;
2248 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('language','Language (2.0 compat version)','Lang');
2249 UPDATE metabib.record_attr SET attrs = attrs || hstore('language',(attrs->'item_lang'));
2253 UPDATE asset.call_number_class
2254 SET field = '080ab,082ab,092abef'
2259 UPDATE asset.call_number_class
2260 SET field = '050ab,055ab,090abef'
2265 -- Using a tool such as pgadmin to run this script may fail
2266 -- If it does, try psql command line.
2268 -- Change this to FALSE to disable updating existing circs
2269 -- Otherwise will use the fine interval for the grace period
2275 ALTER TABLE config.rule_recurring_fine
2276 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '1 day';
2278 ALTER TABLE action.circulation
2279 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '0 seconds';
2281 ALTER TABLE action.aged_circulation
2282 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '0 seconds';
2284 -- Remove defaults needed to stop null complaints
2286 ALTER TABLE action.circulation
2287 ALTER COLUMN grace_period DROP DEFAULT;
2289 ALTER TABLE action.aged_circulation
2290 ALTER COLUMN grace_period DROP DEFAULT;
2294 DROP VIEW action.all_circulation;
2295 DROP VIEW action.open_circulation;
2296 DROP VIEW action.billable_circulations;
2300 CREATE OR REPLACE VIEW action.all_circulation AS
2301 SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
2302 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
2303 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
2304 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
2305 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
2306 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
2307 FROM action.aged_circulation
2309 SELECT DISTINCT circ.id,COALESCE(a.post_code,b.post_code) AS usr_post_code, p.home_ou AS usr_home_ou, p.profile AS usr_profile, EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year,
2310 cp.call_number AS copy_call_number, cp.location AS copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
2311 cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
2312 circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
2313 circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
2314 circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
2316 FROM action.circulation circ
2317 JOIN asset.copy cp ON (circ.target_copy = cp.id)
2318 JOIN asset.call_number cn ON (cp.call_number = cn.id)
2319 JOIN actor.usr p ON (circ.usr = p.id)
2320 LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
2321 LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
2323 CREATE OR REPLACE VIEW action.open_circulation AS
2325 FROM action.circulation
2326 WHERE checkin_time IS NULL
2330 CREATE OR REPLACE VIEW action.billable_circulations AS
2332 FROM action.circulation
2333 WHERE xact_finish IS NULL;
2335 -- Drop Functions that rely on types
2337 DROP FUNCTION action.item_user_circ_test(INT, BIGINT, INT, BOOL);
2338 DROP FUNCTION action.item_user_circ_test(INT, BIGINT, INT);
2339 DROP FUNCTION action.item_user_renew_test(INT, BIGINT, INT);
2341 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
2343 user_object actor.usr%ROWTYPE;
2344 standing_penalty config.standing_penalty%ROWTYPE;
2345 item_object asset.copy%ROWTYPE;
2346 item_status_object config.copy_status%ROWTYPE;
2347 item_location_object asset.copy_location%ROWTYPE;
2348 result action.circ_matrix_test_result;
2349 circ_test action.found_circ_matrix_matchpoint;
2350 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
2351 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
2352 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
2353 hold_ratio action.hold_stats%ROWTYPE;
2356 context_org_list INT[];
2359 -- Assume success unless we hit a failure condition
2360 result.success := TRUE;
2362 -- Fail if the user is BARRED
2363 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
2365 -- Fail if we couldn't find the user
2366 IF user_object.id IS NULL THEN
2367 result.fail_part := 'no_user';
2368 result.success := FALSE;
2374 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
2376 -- Fail if we couldn't find the item
2377 IF item_object.id IS NULL THEN
2378 result.fail_part := 'no_item';
2379 result.success := FALSE;
2385 IF user_object.barred IS TRUE THEN
2386 result.fail_part := 'actor.usr.barred';
2387 result.success := FALSE;
2392 -- Fail if the item can't circulate
2393 IF item_object.circulate IS FALSE THEN
2394 result.fail_part := 'asset.copy.circulate';
2395 result.success := FALSE;
2400 -- Fail if the item isn't in a circulateable status on a non-renewal
2401 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
2402 result.fail_part := 'asset.copy.status';
2403 result.success := FALSE;
2406 ELSIF renewal AND item_object.status <> 1 THEN
2407 result.fail_part := 'asset.copy.status';
2408 result.success := FALSE;
2413 -- Fail if the item can't circulate because of the shelving location
2414 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
2415 IF item_location_object.circulate IS FALSE THEN
2416 result.fail_part := 'asset.copy_location.circulate';
2417 result.success := FALSE;
2422 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
2424 circ_matchpoint := circ_test.matchpoint;
2425 result.matchpoint := circ_matchpoint.id;
2426 result.circulate := circ_matchpoint.circulate;
2427 result.duration_rule := circ_matchpoint.duration_rule;
2428 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
2429 result.max_fine_rule := circ_matchpoint.max_fine_rule;
2430 result.hard_due_date := circ_matchpoint.hard_due_date;
2431 result.renewals := circ_matchpoint.renewals;
2432 result.grace_period := circ_matchpoint.grace_period;
2433 result.buildrows := circ_test.buildrows;
2435 -- Fail if we couldn't find a matchpoint
2436 IF circ_test.success = false THEN
2437 result.fail_part := 'no_matchpoint';
2438 result.success := FALSE;
2441 RETURN; -- All tests after this point require a matchpoint. No sense in running on an incomplete or missing one.
2444 -- Apparently....use the circ matchpoint org unit to determine what org units are valid.
2445 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_matchpoint.org_unit );
2448 penalty_type = '%RENEW%';
2450 penalty_type = '%CIRC%';
2453 FOR standing_penalty IN
2454 SELECT DISTINCT csp.*
2455 FROM actor.usr_standing_penalty usp
2456 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
2457 WHERE usr = match_user
2458 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
2459 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
2460 AND csp.block_list LIKE penalty_type LOOP
2462 result.fail_part := standing_penalty.name;
2463 result.success := FALSE;
2468 -- Fail if the test is set to hard non-circulating
2469 IF circ_matchpoint.circulate IS FALSE THEN
2470 result.fail_part := 'config.circ_matrix_test.circulate';
2471 result.success := FALSE;
2476 -- Fail if the total copy-hold ratio is too low
2477 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
2478 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
2479 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
2480 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
2481 result.success := FALSE;
2487 -- Fail if the available copy-hold ratio is too low
2488 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
2489 IF hold_ratio.hold_count IS NULL THEN
2490 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
2492 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
2493 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
2494 result.success := FALSE;
2500 -- Fail if the user has too many items with specific circ_modifiers checked out
2501 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
2502 SELECT INTO items_out COUNT(*)
2503 FROM action.circulation circ
2504 JOIN asset.copy cp ON (cp.id = circ.target_copy)
2505 WHERE circ.usr = match_user
2506 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
2507 AND circ.checkin_time IS NULL
2508 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
2509 AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
2510 IF items_out >= out_by_circ_mod.items_out THEN
2511 result.fail_part := 'config.circ_matrix_circ_mod_test';
2512 result.success := FALSE;
2518 -- If we passed everything, return the successful matchpoint id
2525 $func$ LANGUAGE plpgsql;
2527 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
2528 SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
2529 $func$ LANGUAGE SQL;
2531 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
2532 SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
2533 $func$ LANGUAGE SQL;
2535 -- Update recurring fine rules
2536 UPDATE config.rule_recurring_fine SET grace_period=recurrence_interval;
2538 -- Update Circulation Data
2539 -- Only update if we were told to and the circ hasn't been checked in
2540 UPDATE action.circulation SET grace_period=fine_interval WHERE :CircGrace AND (checkin_time IS NULL);
2543 CREATE TABLE biblio.monograph_part (
2544 id SERIAL PRIMARY KEY,
2545 record BIGINT NOT NULL REFERENCES biblio.record_entry (id),
2546 label TEXT NOT NULL,
2547 label_sortkey TEXT NOT NULL,
2548 CONSTRAINT record_label_unique UNIQUE (record,label)
2551 CREATE OR REPLACE FUNCTION biblio.normalize_biblio_monograph_part_sortkey () RETURNS TRIGGER AS $$
2553 NEW.label_sortkey := REGEXP_REPLACE(
2554 evergreen.lpad_number_substrings(
2555 naco_normalize(NEW.label),
2565 $$ LANGUAGE PLPGSQL;
2567 CREATE TRIGGER norm_sort_label BEFORE INSERT OR UPDATE ON biblio.monograph_part FOR EACH ROW EXECUTE PROCEDURE biblio.normalize_biblio_monograph_part_sortkey();
2569 CREATE TABLE asset.copy_part_map (
2570 id SERIAL PRIMARY KEY,
2571 target_copy BIGINT NOT NULL, -- points o asset.copy
2572 part INT NOT NULL REFERENCES biblio.monograph_part (id) ON DELETE CASCADE
2574 CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part);
2576 CREATE TABLE asset.call_number_prefix (
2577 id SERIAL PRIMARY KEY,
2578 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
2579 label TEXT NOT NULL, -- i18n
2583 CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$
2585 NEW.label_sortkey := REGEXP_REPLACE(
2586 evergreen.lpad_number_substrings(
2587 naco_normalize(NEW.label),
2597 $$ LANGUAGE PLPGSQL;
2599 CREATE TRIGGER prefix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_prefix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
2600 CREATE UNIQUE INDEX asset_call_number_prefix_once_per_lib ON asset.call_number_prefix (label, owning_lib);
2601 CREATE INDEX asset_call_number_prefix_sortkey_idx ON asset.call_number_prefix (label_sortkey);
2603 CREATE TABLE asset.call_number_suffix (
2604 id SERIAL PRIMARY KEY,
2605 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
2606 label TEXT NOT NULL, -- i18n
2609 CREATE TRIGGER suffix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_suffix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
2610 CREATE UNIQUE INDEX asset_call_number_suffix_once_per_lib ON asset.call_number_suffix (label, owning_lib);
2611 CREATE INDEX asset_call_number_suffix_sortkey_idx ON asset.call_number_suffix (label_sortkey);
2613 INSERT INTO asset.call_number_suffix (id, owning_lib, label) VALUES (-1, 1, '');
2614 INSERT INTO asset.call_number_prefix (id, owning_lib, label) VALUES (-1, 1, '');
2616 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
2618 ALTER TABLE asset.call_number
2619 ADD COLUMN prefix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_prefix(id) DEFERRABLE INITIALLY DEFERRED,
2620 ADD COLUMN suffix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_suffix(id) DEFERRABLE INITIALLY DEFERRED;
2622 ALTER TABLE auditor.asset_call_number_history
2623 ADD COLUMN prefix INT NOT NULL DEFAULT -1,
2624 ADD COLUMN suffix INT NOT NULL DEFAULT -1;
2626 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;
2628 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2629 'ui.cat.volume_copy_editor.horizontal',
2631 'ui.cat.volume_copy_editor.horizontal',
2632 'GUI: Horizontal layout for Volume/Copy Creator/Editor.',
2635 'ui.cat.volume_copy_editor.horizontal',
2636 'The main entry point for this interface is in Holdings Maintenance, Actions for Selected Rows, Edit Item Attributes / Call Numbers / Replace Barcodes. This setting changes the top and bottom panes for that interface into left and right panes.',
2637 'coust', 'description'),
2644 ALTER FUNCTION actor.org_unit_descendants( INT, INT ) ROWS 1;
2645 ALTER FUNCTION actor.org_unit_descendants( INT ) ROWS 1;
2646 ALTER FUNCTION actor.org_unit_descendants_distance( INT ) ROWS 1;
2647 ALTER FUNCTION actor.org_unit_ancestors( INT ) ROWS 1;
2648 ALTER FUNCTION actor.org_unit_ancestors_distance( INT ) ROWS 1;
2649 ALTER FUNCTION actor.org_unit_full_path ( INT ) ROWS 2;
2650 ALTER FUNCTION actor.org_unit_full_path ( INT, INT ) ROWS 2;
2651 ALTER FUNCTION actor.org_unit_combined_ancestors ( INT, INT ) ROWS 1;
2652 ALTER FUNCTION actor.org_unit_common_ancestors ( INT, INT ) ROWS 1;
2653 ALTER FUNCTION actor.org_unit_ancestor_setting( TEXT, INT ) ROWS 1;
2654 ALTER FUNCTION permission.grp_ancestors ( INT ) ROWS 1;
2655 ALTER FUNCTION permission.grp_ancestors_distance( INT ) ROWS 1;
2656 ALTER FUNCTION permission.grp_descendants_distance( INT ) ROWS 1;
2657 ALTER FUNCTION permission.usr_perms ( INT ) ROWS 10;
2658 ALTER FUNCTION permission.usr_has_perm_at_nd ( INT, TEXT) ROWS 1;
2659 ALTER FUNCTION permission.usr_has_perm_at_all_nd ( INT, TEXT ) ROWS 1;
2660 ALTER FUNCTION permission.usr_has_perm_at ( INT, TEXT ) ROWS 1;
2661 ALTER FUNCTION permission.usr_has_perm_at_all ( INT, TEXT ) ROWS 1;
2664 DROP TRIGGER IF EXISTS facet_force_nfc_tgr ON metabib.facet_entry;
2665 CREATE TRIGGER facet_force_nfc_tgr
2666 BEFORE UPDATE OR INSERT ON metabib.facet_entry
2667 FOR EACH ROW EXECUTE PROCEDURE evergreen.facet_force_nfc();
2669 DROP FUNCTION IF EXISTS public.force_unicode_normal_form (TEXT,TEXT);
2670 DROP FUNCTION IF EXISTS public.facet_force_nfc ();
2672 DROP TRIGGER b_maintain_901 ON biblio.record_entry;
2673 DROP TRIGGER b_maintain_901 ON authority.record_entry;
2674 DROP TRIGGER b_maintain_901 ON serial.record_entry;
2676 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2677 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2678 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2680 DROP FUNCTION IF EXISTS public.maintain_901 ();
2682 ------ Backporting note: 2.1+ only beyond here --------
2684 CREATE SCHEMA unapi;
2686 CREATE TABLE unapi.bre_output_layout (
2687 name TEXT PRIMARY KEY,
2688 transform TEXT REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
2689 mime_type TEXT NOT NULL,
2690 feed_top TEXT NOT NULL,
2691 holdings_element TEXT,
2693 description_element TEXT,
2694 creator_element TEXT,
2695 update_ts_element TEXT
2698 INSERT INTO unapi.bre_output_layout
2699 (name, transform, mime_type, holdings_element, feed_top, title_element, description_element, creator_element, update_ts_element)
2701 ('holdings_xml', NULL, 'application/xml', NULL, 'hxml', NULL, NULL, NULL, NULL),
2702 ('marcxml', 'marcxml', 'application/marc+xml', 'record', 'collection', NULL, NULL, NULL, NULL),
2703 ('mods32', 'mods32', 'application/mods+xml', 'mods', 'modsCollection', NULL, NULL, NULL, NULL)
2706 -- Dummy functions, so we can create the real ones out of order
2707 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2708 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2709 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2710 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2711 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2712 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2713 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2714 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2715 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2716 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2717 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2718 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2719 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2720 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2721 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2722 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2723 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2724 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2725 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2726 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2727 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2729 CREATE OR REPLACE FUNCTION unapi.holdings_xml ( bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2730 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL;
2732 CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
2738 'id' || COALESCE(obj_id::TEXT,'') ||
2739 'format' || COALESCE(format::TEXT,'') ||
2740 'ename' || COALESCE(ename::TEXT,'') ||
2741 'includes' || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
2742 'org' || COALESCE(org::TEXT,'') ||
2743 'depth' || COALESCE(depth::TEXT,'') ||
2744 'slimit' || COALESCE(slimit::TEXT,'') ||
2745 'soffset' || COALESCE(soffset::TEXT,'') ||
2746 'include_xmlns' || COALESCE(include_xmlns::TEXT,'');
2747 -- RAISE NOTICE 'memoize key: %', key;
2750 -- RAISE NOTICE 'memoize hash: %', key;
2752 -- XXX cache logic ... memcached? table?
2754 EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
2757 $F$ LANGUAGE PLPGSQL;
2759 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE, title TEXT DEFAULT NULL, description TEXT DEFAULT NULL, creator TEXT DEFAULT NULL, update_ts TEXT DEFAULT NULL, unapi_url TEXT DEFAULT NULL, header_xml XML DEFAULT NULL ) RETURNS XML AS $F$
2761 layout unapi.bre_output_layout%ROWTYPE;
2762 transform config.xml_transform%ROWTYPE;
2765 xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1';
2767 element_list TEXT[];
2770 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
2771 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
2773 IF layout.name IS NULL THEN
2777 SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
2778 xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
2780 -- Gather the bib xml
2781 SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
2783 IF layout.title_element IS NOT NULL THEN
2784 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title, include_xmlns;
2787 IF layout.description_element IS NOT NULL THEN
2788 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description, include_xmlns;
2791 IF layout.creator_element IS NOT NULL THEN
2792 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator, include_xmlns;
2795 IF layout.update_ts_element IS NOT NULL THEN
2796 EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts, include_xmlns;
2799 IF unapi_url IS NOT NULL THEN
2800 EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML;
2803 IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
2805 element_list := regexp_split_to_array(layout.feed_top,E'\\.');
2806 FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
2807 EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', CASE WHEN $4 THEN XMLATTRIBUTES( $1 AS xmlns) ELSE NULL END, $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, include_xmlns;
2810 RETURN tmp_xml::XML;
2812 $F$ LANGUAGE PLPGSQL;
2814 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
2816 me biblio.record_entry%ROWTYPE;
2817 layout unapi.bre_output_layout%ROWTYPE;
2818 xfrm config.xml_transform%ROWTYPE;
2826 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
2828 IF ouid IS NULL THEN
2832 IF format = 'holdings_xml' THEN -- the special case
2833 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
2837 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
2839 IF layout.name IS NULL THEN
2843 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
2845 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
2847 -- grab hodlings if we need them
2848 IF ('holdings_xml' = ANY (includes)) THEN
2849 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
2855 -- generate our item node
2858 IF format = 'marcxml' THEN
2860 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
2861 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
2864 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
2867 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
2869 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
2870 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
2873 IF ('bre.unapi' = ANY (includes)) THEN
2874 output := REGEXP_REPLACE(
2876 '</' || top_el || '>(.*?)',
2880 'http://www.w3.org/1999/xhtml' AS xmlns,
2881 'unapi-id' AS class,
2882 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
2884 )::TEXT || '</' || top_el || E'>\\1'
2892 $F$ LANGUAGE PLPGSQL;
2894 CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
2898 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2899 CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
2903 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
2906 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
2908 FROM asset.opac_ou_record_copy_count($2, $1)
2912 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
2914 FROM asset.staff_ou_record_copy_count($2, $1)
2919 WHEN ('bmp' = ANY ($5)) THEN
2920 XMLELEMENT( name monograph_parts,
2921 XMLAGG((SELECT unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) FROM biblio.monograph_part WHERE record = $1))
2925 CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
2928 (SELECT XMLAGG(acn) FROM (
2929 SELECT unapi.acn(acn.id,'xml','volume', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value('{acn,auri}'::TEXT[] || $5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
2930 FROM asset.call_number acn
2931 WHERE acn.record = $1
2935 JOIN actor.org_unit_descendants(
2940 FROM actor.org_unit_type aout
2941 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
2944 ) aoud ON (acp.circ_lib = aoud.id)
2947 ORDER BY label_sortkey
2953 CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
2956 (SELECT XMLAGG(ssub) FROM (
2957 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
2958 FROM serial.subscription
2959 WHERE record_entry = $1
2966 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
2970 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2971 'tag:open-ils.org:U2@ssub/' || id AS id,
2972 start_date AS start, end_date AS end, expected_date_offset
2974 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
2975 XMLELEMENT( name distributions,
2977 WHEN ('sdist' = ANY ($4)) THEN
2978 XMLAGG((SELECT unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE) FROM serial.distribution WHERE subscription = ssub.id))
2983 FROM serial.subscription ssub
2985 GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
2988 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
2992 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2993 'tag:open-ils.org:U2@sdist/' || id AS id,
2994 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
2995 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
2996 unit_label_prefix, label, unit_label_suffix, summary_method
2998 unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
2999 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3000 XMLELEMENT( name streams,
3002 WHEN ('sstr' = ANY ($4)) THEN
3003 XMLAGG((SELECT unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.stream WHERE distribution = sdist.id))
3007 XMLELEMENT( name summaries,
3009 WHEN ('ssum' = ANY ($4)) THEN
3010 XMLAGG((SELECT unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.basic_summary WHERE distribution = sdist.id))
3014 WHEN ('ssum' = ANY ($4)) THEN
3015 XMLAGG((SELECT unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.index_summary WHERE distribution = sdist.id))
3019 WHEN ('ssum' = ANY ($4)) THEN
3020 XMLAGG((SELECT unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) FROM serial.supplement_summary WHERE distribution = sdist.id))
3025 FROM serial.distribution sdist
3027 GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
3030 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3034 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3035 'tag:open-ils.org:U2@sstr/' || id AS id,
3038 CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3039 XMLELEMENT( name items,
3041 WHEN ('sitem' = ANY ($4)) THEN
3042 XMLAGG((SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) FROM serial.item WHERE stream = sstr.id))
3047 FROM serial.stream sstr
3049 GROUP BY id, routing_label, distribution;
3052 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3056 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3057 'tag:open-ils.org:U2@siss/' || id AS id,
3058 create_date, edit_date, label, date_published,
3059 holding_code, holding_type, holding_link_id
3061 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3062 XMLELEMENT( name items,
3064 WHEN ('sitem' = ANY ($4)) THEN
3065 XMLAGG((SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) FROM serial.item WHERE issuance = sstr.id))
3070 FROM serial.issuance sstr
3072 GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
3075 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3079 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3080 'tag:open-ils.org:U2@sitem/' || id AS id,
3081 'tag:open-ils.org:U2@siss/' || issuance AS issuance,
3082 date_expected, date_received
3084 CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3085 CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3086 CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( stream, $2, 'serial_unit', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3087 CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
3088 -- XMLELEMENT( name notes,
3090 -- WHEN ('acpn' = ANY ($4)) THEN
3091 -- XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
3096 FROM serial.item sitem
3101 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3103 name serial_summary,
3105 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3106 'tag:open-ils.org:U2@sbsum/' || id AS id,
3107 'sssum' AS type, generated_coverage, textual_holdings, show_generated
3109 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
3111 FROM serial.supplement_summary ssum
3113 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3116 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3118 name serial_summary,
3120 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3121 'tag:open-ils.org:U2@sbsum/' || id AS id,
3122 'sbsum' AS type, generated_coverage, textual_holdings, show_generated
3124 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
3126 FROM serial.basic_summary ssum
3128 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3131 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3133 name serial_summary,
3135 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3136 'tag:open-ils.org:U2@sbsum/' || id AS id,
3137 'sisum' AS type, generated_coverage, textual_holdings, show_generated
3139 CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
3141 FROM serial.index_summary ssum
3143 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3147 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3151 IF ename = 'circlib' THEN
3155 'http://open-ils.org/spec/actors/v1' AS xmlns,
3160 FROM actor.org_unit aou
3163 EXECUTE $$SELECT XMLELEMENT(
3164 name $$ || ename || $$,
3166 'http://open-ils.org/spec/actors/v1' AS xmlns,
3167 'tag:open-ils.org:U2@aou/' || id AS id,
3168 shortname, name, opac_visible
3171 FROM actor.org_unit aou
3172 WHERE id = $1 $$ INTO output USING obj_id;
3178 $F$ LANGUAGE PLPGSQL;
3180 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3184 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3189 FROM asset.copy_location
3193 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3197 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3202 FROM config.copy_status
3206 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3210 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3211 create_date AS date,
3216 FROM asset.copy_note
3220 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3224 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3230 FROM asset.stat_cat_entry asce
3231 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
3235 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3237 name monograph_part,
3239 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3240 'tag:open-ils.org:U2@bmp/' || id AS id,
3244 'tag:open-ils.org:U2@bre/' || record AS record
3247 WHEN ('acp' = ANY ($4)) THEN
3248 XMLELEMENT( name copies,
3249 (SELECT XMLAGG(acp) FROM (
3250 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
3252 JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
3254 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
3261 CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
3263 FROM biblio.monograph_part
3265 GROUP BY id, label, label_sortkey, record;
3268 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3272 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3273 'tag:open-ils.org:U2@acp/' || id AS id,
3274 create_date, edit_date, copy_number, circulate, deposit,
3275 ref, holdable, deleted, deposit_amount, price, barcode,
3276 circ_modifier, circ_as_type, opac_visible
3278 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3279 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3280 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3281 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3282 CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3283 XMLELEMENT( name copy_notes,
3285 WHEN ('acpn' = ANY ($4)) THEN
3286 XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
3290 XMLELEMENT( name statcats,
3292 WHEN ('ascecm' = ANY ($4)) THEN
3293 XMLAGG((SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id))
3298 WHEN ('bmp' = ANY ($4)) THEN
3299 XMLELEMENT( name monograph_parts,
3300 XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_part_map WHERE target_copy = cp.id))
3307 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
3310 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3314 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3315 'tag:open-ils.org:U2@acp/' || id AS id,
3316 create_date, edit_date, copy_number, circulate, deposit,
3317 ref, holdable, deleted, deposit_amount, price, barcode,
3318 circ_modifier, circ_as_type, opac_visible, status_changed_time,
3319 floating, mint_condition, detailed_contents, sort_key, summary_contents, cost
3321 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
3322 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
3323 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
3324 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
3325 CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3326 XMLELEMENT( name copy_notes,
3328 WHEN ('acpn' = ANY ($4)) THEN
3329 XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
3333 XMLELEMENT( name statcats,
3335 WHEN ('ascecm' = ANY ($4)) THEN
3336 XMLAGG((SELECT unapi.acpn( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id))
3343 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
3344 deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
3347 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3351 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3352 'tag:open-ils.org:U2@acn/' || acn.id AS id,
3354 o.opac_visible AS opac_visible,
3355 deleted, label, label_sortkey, label_class, record
3357 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
3358 XMLELEMENT( name copies,
3360 WHEN ('acp' = ANY ($4)) THEN
3361 (SELECT XMLAGG(acp) FROM (
3362 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
3364 JOIN actor.org_unit_descendants(
3365 (SELECT id FROM actor.org_unit WHERE shortname = $5),
3366 (COALESCE($6,(SELECT aout.depth FROM actor.org_unit_type aout JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.shortname = $5))))
3367 ) aoud ON (cp.circ_lib = aoud.id)
3368 WHERE cp.call_number = acn.id
3369 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
3378 (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
3380 CASE WHEN ('acnp' = ANY ($4)) THEN unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3381 CASE WHEN ('acns' = ANY ($4)) THEN unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3382 CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
3384 FROM asset.call_number acn
3385 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
3387 GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
3390 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3392 name call_number_prefix,
3394 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3399 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8)
3401 FROM asset.call_number_prefix
3405 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3407 name call_number_suffix,
3409 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3414 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acns'), $5, $6, $7, $8)
3416 FROM asset.call_number_suffix
3420 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3424 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3425 'tag:open-ils.org:U2@auri/' || uri.id AS id,
3430 XMLELEMENT( name copies,
3432 WHEN ('acn' = ANY ($4)) THEN
3433 (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
3440 GROUP BY uri.id, use_restriction, href, label;
3443 DROP FUNCTION IF EXISTS public.array_remove_item_by_value(ANYARRAY,ANYELEMENT);
3445 DROP FUNCTION IF EXISTS public.lpad_number_substrings(TEXT,TEXT,INT);
3449 CREATE OR REPLACE FUNCTION evergreen.fake_fkey_tgr () RETURNS TRIGGER AS $F$
3453 EXECUTE 'SELECT ($1).' || quote_ident(TG_ARGV[0]) INTO copy_id USING NEW;
3454 PERFORM * FROM asset.copy WHERE id = copy_id;
3456 RAISE EXCEPTION 'Key (%.%=%) does not exist in asset.copy', TG_TABLE_SCHEMA, TG_TABLE_NAME, copy_id;
3460 $F$ LANGUAGE PLPGSQL;
3462 DROP TRIGGER IF EXISTS action_circulation_target_copy_trig ON action.circulation;
3463 CREATE TRIGGER action_circulation_target_copy_trig AFTER INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE evergreen.fake_fkey_tgr('target_copy');
3466 CREATE TABLE biblio.peer_type (
3467 id SERIAL PRIMARY KEY,
3468 name TEXT NOT NULL UNIQUE -- i18n
3471 CREATE TABLE biblio.peer_bib_copy_map (
3472 id SERIAL PRIMARY KEY,
3473 peer_type INT NOT NULL REFERENCES biblio.peer_type (id),
3474 peer_record BIGINT NOT NULL REFERENCES biblio.record_entry (id),
3475 target_copy BIGINT NOT NULL -- can't use fkey because of acp subtables
3477 CREATE INDEX peer_bib_copy_map_record_idx ON biblio.peer_bib_copy_map (peer_record);
3478 CREATE INDEX peer_bib_copy_map_copy_idx ON biblio.peer_bib_copy_map (target_copy);
3480 DROP TABLE asset.opac_visible_copies;
3481 CREATE TABLE asset.opac_visible_copies (
3482 id BIGSERIAL primary key,
3488 INSERT INTO biblio.peer_type (id,name) VALUES
3489 (1,oils_i18n_gettext(1,'Bound Volume','bpt','name')),
3490 (2,oils_i18n_gettext(2,'Bilingual','bpt','name')),
3491 (3,oils_i18n_gettext(3,'Back-to-back','bpt','name')),
3492 (4,oils_i18n_gettext(4,'Set','bpt','name')),
3493 (5,oils_i18n_gettext(5,'e-Reader Preload','bpt','name'));
3495 SELECT SETVAL('biblio.peer_type_id_seq'::TEXT, 100);
3497 CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
3501 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3502 CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
3506 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
3509 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
3511 FROM asset.opac_ou_record_copy_count($2, $1)
3515 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
3517 FROM asset.staff_ou_record_copy_count($2, $1)
3522 WHEN ('bmp' = ANY ($5)) THEN
3523 XMLELEMENT( name monograph_parts,
3524 XMLAGG((SELECT unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE) FROM biblio.monograph_part WHERE record = $1))
3528 CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
3531 (SELECT XMLAGG(acn) FROM (
3532 SELECT unapi.acn(acn.id,'xml','volume',evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value('{acn,auri}'::TEXT[] || $5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
3533 FROM asset.call_number acn
3534 WHERE acn.record = $1
3538 JOIN actor.org_unit_descendants(
3543 FROM actor.org_unit_type aout
3544 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
3547 ) aoud ON (acp.circ_lib = aoud.id)
3550 ORDER BY label_sortkey
3556 CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
3559 (SELECT XMLAGG(ssub) FROM (
3560 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
3561 FROM serial.subscription
3562 WHERE record_entry = $1
3566 CASE WHEN ('acp' = ANY ($5)) THEN
3568 name foreign_copies,
3569 (SELECT XMLAGG(acp) FROM (
3570 SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
3571 FROM biblio.peer_bib_copy_map p
3572 JOIN asset.copy c ON (p.target_copy = c.id)
3573 WHERE NOT c.deleted AND peer_record = $1
3580 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
3584 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3585 'tag:open-ils.org:U2@acp/' || id AS id,
3586 create_date, edit_date, copy_number, circulate, deposit,
3587 ref, holdable, deleted, deposit_amount, price, barcode,
3588 circ_modifier, circ_as_type, opac_visible
3590 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3591 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3592 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3593 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3594 CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
3595 XMLELEMENT( name copy_notes,
3597 WHEN ('acpn' = ANY ($4)) THEN
3598 XMLAGG((SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_note WHERE owning_copy = cp.id AND pub))
3602 XMLELEMENT( name statcats,
3604 WHEN ('ascecm' = ANY ($4)) THEN
3605 XMLAGG((SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.stat_cat_entry_copy_map WHERE owning_copy = cp.id))
3609 XMLELEMENT( name foreign_records,
3611 WHEN ('bre' = ANY ($4)) THEN
3612 XMLAGG((SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE) FROM biblio.peer_bib_copy_map WHERE target_copy = cp.id))
3618 WHEN ('bmp' = ANY ($4)) THEN
3619 XMLELEMENT( name monograph_parts,
3620 XMLAGG((SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) FROM asset.copy_part_map WHERE target_copy = cp.id))
3627 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
3630 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
3632 TRUNCATE TABLE asset.opac_visible_copies;
3634 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
3635 SELECT cp.id, cp.circ_lib, cn.record
3637 JOIN asset.call_number cn ON (cn.id = cp.call_number)
3638 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3639 JOIN asset.copy_location cl ON (cp.location = cl.id)
3640 JOIN config.copy_status cs ON (cp.status = cs.id)
3641 JOIN biblio.record_entry b ON (cn.record = b.id)
3642 WHERE NOT cp.deleted
3650 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record
3652 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
3653 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3654 JOIN asset.copy_location cl ON (cp.location = cl.id)
3655 JOIN config.copy_status cs ON (cp.status = cs.id)
3656 WHERE NOT cp.deleted
3663 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
3664 Rebuild the copy OPAC visibility cache. Useful during migrations.
3667 SELECT asset.refresh_opac_visible_copies_mat_view();
3668 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
3669 CREATE INDEX opac_visible_copies_copy_id_idx on asset.opac_visible_copies (copy_id);
3670 CREATE UNIQUE INDEX opac_visible_copies_once_per_record_idx on asset.opac_visible_copies (copy_id, record);
3672 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
3676 do_add BOOLEAN := false;
3677 do_remove BOOLEAN := false;
3680 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
3681 SELECT id, circ_lib, record FROM (
3682 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number
3684 JOIN asset.call_number cn ON (cn.id = cp.call_number)
3685 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3686 JOIN asset.copy_location cl ON (cp.location = cl.id)
3687 JOIN config.copy_status cs ON (cp.status = cs.id)
3688 JOIN biblio.record_entry b ON (cn.record = b.id)
3689 WHERE NOT cp.deleted
3697 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number
3699 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
3700 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3701 JOIN asset.copy_location cl ON (cp.location = cl.id)
3702 JOIN config.copy_status cs ON (cp.status = cs.id)
3703 WHERE NOT cp.deleted
3712 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
3714 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
3715 IF TG_OP = 'INSERT' THEN
3716 add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';';
3720 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
3721 EXECUTE remove_query;
3726 IF TG_OP = 'INSERT' THEN
3728 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
3729 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3737 -- handle items first, since with circulation activity
3738 -- their statuses change frequently
3739 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
3741 IF OLD.location <> NEW.location OR
3742 OLD.call_number <> NEW.call_number OR
3743 OLD.status <> NEW.status OR
3744 OLD.circ_lib <> NEW.circ_lib THEN
3745 -- any of these could change visibility, but
3746 -- we'll save some queries and not try to calculate
3747 -- the change directly
3752 IF OLD.deleted <> NEW.deleted THEN
3760 IF OLD.opac_visible <> NEW.opac_visible THEN
3761 IF OLD.opac_visible THEN
3763 ELSIF NOT do_remove THEN -- handle edge case where deleted item
3764 -- is also marked opac_visible
3772 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
3775 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3783 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
3785 IF OLD.deleted AND NEW.deleted THEN -- do nothing
3789 ELSIF NEW.deleted THEN -- remove rows
3791 IF TG_TABLE_NAME = 'call_number' THEN
3792 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
3793 ELSIF TG_TABLE_NAME = 'record_entry' THEN
3794 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
3799 ELSIF OLD.deleted THEN -- add rows
3801 IF TG_TABLE_NAME IN ('copy','unit') THEN
3802 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3803 ELSIF TG_TABLE_NAME = 'call_number' THEN
3804 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
3805 ELSIF TG_TABLE_NAME = 'record_entry' THEN
3806 add_query := add_query || 'WHERE x.record = ' || NEW.id || ';';
3816 IF TG_TABLE_NAME = 'call_number' THEN
3818 IF OLD.record <> NEW.record THEN
3819 -- call number is linked to different bib
3820 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
3821 EXECUTE remove_query;
3822 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
3830 IF TG_TABLE_NAME IN ('record_entry') THEN
3831 RETURN NEW; -- don't have 'opac_visible'
3834 -- actor.org_unit, asset.copy_location, asset.copy_status
3835 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
3839 ELSIF NEW.opac_visible THEN -- add rows
3841 IF TG_TABLE_NAME = 'org_unit' THEN
3842 add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
3843 ELSIF TG_TABLE_NAME = 'copy_location' THEN
3844 add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
3845 ELSIF TG_TABLE_NAME = 'copy_status' THEN
3846 add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
3853 IF TG_TABLE_NAME = 'org_unit' THEN
3854 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
3855 ELSIF TG_TABLE_NAME = 'copy_location' THEN
3856 remove_query := remove_query || 'location = ' || NEW.id || ');';
3857 ELSIF TG_TABLE_NAME = 'copy_status' THEN
3858 remove_query := remove_query || 'status = ' || NEW.id || ');';
3861 EXECUTE remove_query;
3867 $func$ LANGUAGE PLPGSQL;
3868 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
3869 Trigger function to update the copy OPAC visiblity cache.
3872 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR DELETE ON biblio.peer_bib_copy_map FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
3874 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
3876 transformed_xml TEXT;
3879 xfrm config.xml_transform%ROWTYPE;
3881 new_attrs HSTORE := ''::HSTORE;
3882 attr_def config.record_attr_definition%ROWTYPE;
3885 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
3886 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
3887 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
3888 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
3889 DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
3890 RETURN NEW; -- and we're done
3893 IF TG_OP = 'UPDATE' THEN -- re-ingest?
3894 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
3896 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
3901 -- Record authority linking
3902 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
3904 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
3907 -- Flatten and insert the mfr data
3908 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
3910 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
3912 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
3913 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
3915 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
3917 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
3918 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
3919 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
3920 WHERE record = NEW.id
3921 AND tag LIKE attr_def.tag
3923 WHEN attr_def.sf_list IS NOT NULL
3924 THEN POSITION(subfield IN attr_def.sf_list) > 0
3931 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
3932 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
3934 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
3936 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
3938 -- See if we can skip the XSLT ... it's expensive
3939 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
3940 -- Can't skip the transform
3941 IF xfrm.xslt <> '---' THEN
3942 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
3944 transformed_xml := NEW.marc;
3947 prev_xfrm := xfrm.name;
3950 IF xfrm.name IS NULL THEN
3951 -- just grab the marcxml (empty) transform
3952 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
3953 prev_xfrm := xfrm.name;
3956 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
3958 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
3959 SELECT value::TEXT INTO attr_value
3960 FROM biblio.marc21_physical_characteristics(NEW.id)
3961 WHERE subfield = attr_def.phys_char_sf
3962 LIMIT 1; -- Just in case ...
3966 -- apply index normalizers to attr_value
3968 SELECT n.func AS func,
3969 n.param_count AS param_count,
3971 FROM config.index_normalizer n
3972 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
3973 WHERE attr = attr_def.name
3975 EXECUTE 'SELECT ' || normalizer.func || '(' ||
3976 quote_literal( attr_value ) ||
3978 WHEN normalizer.param_count > 0
3979 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
3982 ')' INTO attr_value;
3986 -- Add the new value to the hstore
3987 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
3991 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
3992 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
3994 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
4000 -- Gather and insert the field entry data
4001 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
4003 -- Located URI magic
4004 IF TG_OP = 'INSERT' THEN
4005 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4007 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4010 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4012 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4016 -- (re)map metarecord-bib linking
4017 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
4018 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
4020 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4022 ELSE -- we're doing an update, and we're not deleted, remap
4023 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
4025 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4031 $func$ LANGUAGE PLPGSQL;
4034 CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
4038 CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
4039 'tag:open-ils.org:U2@mra/' || mra.id AS id,
4040 'tag:open-ils.org:U2@bre/' || mra.id AS record
4042 (SELECT XMLAGG(foo.y)
4043 FROM (SELECT XMLELEMENT(
4047 cvm.value AS "coded-value",
4053 FROM EACH(mra.attrs) AS x
4054 JOIN config.record_attr_definition rad ON (x.key = rad.name)
4055 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
4059 FROM metabib.record_attr mra
4063 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
4065 me biblio.record_entry%ROWTYPE;
4066 layout unapi.bre_output_layout%ROWTYPE;
4067 xfrm config.xml_transform%ROWTYPE;
4076 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
4078 IF ouid IS NULL THEN
4082 IF format = 'holdings_xml' THEN -- the special case
4083 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
4087 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
4089 IF layout.name IS NULL THEN
4093 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
4095 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
4097 -- grab SVF if we need them
4098 IF ('mra' = ANY (includes)) THEN
4099 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
4104 -- grab hodlings if we need them
4105 IF ('holdings_xml' = ANY (includes)) THEN
4106 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
4112 -- generate our item node
4115 IF format = 'marcxml' THEN
4117 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
4118 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
4121 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
4124 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
4126 IF axml IS NOT NULL THEN
4127 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
4130 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
4131 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
4134 IF ('bre.unapi' = ANY (includes)) THEN
4135 output := REGEXP_REPLACE(
4137 '</' || top_el || '>(.*?)',
4141 'http://www.w3.org/1999/xhtml' AS xmlns,
4142 'unapi-id' AS class,
4143 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
4145 )::TEXT || '</' || top_el || E'>\\1'
4153 $F$ LANGUAGE PLPGSQL;
4157 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
4159 me biblio.record_entry%ROWTYPE;
4160 layout unapi.bre_output_layout%ROWTYPE;
4161 xfrm config.xml_transform%ROWTYPE;
4170 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
4172 IF ouid IS NULL THEN
4176 IF format = 'holdings_xml' THEN -- the special case
4177 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
4181 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
4183 IF layout.name IS NULL THEN
4187 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
4189 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
4191 -- grab SVF if we need them
4192 IF ('mra' = ANY (includes)) THEN
4193 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
4198 -- grab hodlings if we need them
4199 IF ('holdings_xml' = ANY (includes)) THEN
4200 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
4206 -- generate our item node
4209 IF format = 'marcxml' THEN
4211 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
4212 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
4215 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
4218 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
4220 IF axml IS NOT NULL THEN
4221 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
4224 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
4225 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
4228 IF ('bre.unapi' = ANY (includes)) THEN
4229 output := REGEXP_REPLACE(
4231 '</' || top_el || '>(.*?)',
4235 'http://www.w3.org/1999/xhtml' AS xmlns,
4236 'unapi-id' AS class,
4237 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
4239 )::TEXT || '</' || top_el || E'>\\1'
4245 output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
4248 $F$ LANGUAGE PLPGSQL;
4253 CREATE OR REPLACE FUNCTION public.extract_acq_marc_field ( BIGINT, TEXT, TEXT) RETURNS TEXT AS $$
4254 SELECT extract_marc_field('acq.lineitem', $1, $2, $3);
4258 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
4265 rtype := (vandelay.marc21_record_type( marc )).code;
4266 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
4267 IF ff_pos.tag = 'ldr' THEN
4268 val := oils_xpath_string('//*[local-name()="leader"]', marc);
4269 IF val IS NOT NULL THEN
4270 val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
4274 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
4275 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
4279 val := REPEAT( ff_pos.default_val, ff_pos.length );
4285 $func$ LANGUAGE PLPGSQL;
4289 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
4294 output biblio.record_ff_map%ROWTYPE;
4296 rtype := (vandelay.marc21_record_type( marc )).code;
4298 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
4299 output.ff_name := ff_pos.fixed_field;
4300 output.ff_value := NULL;
4302 IF ff_pos.tag = 'ldr' THEN
4303 output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
4304 IF output.ff_value IS NOT NULL THEN
4305 output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
4307 output.ff_value := NULL;
4310 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
4311 output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
4312 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
4314 output.ff_value := NULL;
4322 $func$ LANGUAGE PLPGSQL;
4324 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
4331 uri_owner_list TEXT[];
4339 -- Clear any URI mappings and call numbers for this bib.
4340 -- This leads to acn / auricnm inflation, but also enables
4341 -- old acn/auricnm's to go away and for bibs to be deleted.
4342 FOR uri_cn_id IN SELECT id FROM asset.call_number WHERE record = bib_id AND label = '##URI##' AND NOT deleted LOOP
4343 DELETE FROM asset.uri_call_number_map WHERE call_number = uri_cn_id;
4344 DELETE FROM asset.call_number WHERE id = uri_cn_id;
4347 uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
4348 IF ARRAY_UPPER(uris,1) > 0 THEN
4349 FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
4350 -- First we pull info out of the 856
4353 uri_href := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
4354 uri_label := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()',uri_xml))[1];
4355 uri_use := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
4357 IF uri_label IS NULL THEN
4358 uri_label := uri_href;
4360 CONTINUE WHEN uri_href IS NULL;
4362 -- Get the distinct list of libraries wanting to use
4364 DISTINCT REGEXP_REPLACE(
4366 $re$^.*?\((\w+)\).*$$re$,
4369 ) INTO uri_owner_list
4372 '//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',
4377 IF ARRAY_UPPER(uri_owner_list,1) > 0 THEN
4379 -- look for a matching uri
4380 IF uri_use IS NULL THEN
4381 SELECT id INTO uri_id
4383 WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active
4384 ORDER BY id LIMIT 1;
4385 IF NOT FOUND THEN -- create one
4386 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
4387 SELECT id INTO uri_id
4389 WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active;
4392 SELECT id INTO uri_id
4394 WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active
4395 ORDER BY id LIMIT 1;
4396 IF NOT FOUND THEN -- create one
4397 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
4398 SELECT id INTO uri_id
4400 WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
4404 FOR j IN 1 .. ARRAY_UPPER(uri_owner_list, 1) LOOP
4405 uri_owner := uri_owner_list[j];
4407 SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
4408 CONTINUE WHEN NOT FOUND;
4410 -- we need a call number to link through
4411 SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
4413 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
4414 VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
4415 SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
4418 -- now, link them if they're not already
4419 SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
4421 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
4433 $func$ LANGUAGE PLPGSQL;
4436 UPDATE config.org_unit_setting_type SET datatype = 'string' WHERE name = 'ui.general.button_bar';
4438 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype) VALUES ('ui.general.hotkeyset', 'GUI: Default Hotkeyset', 'Default Hotkeyset for clients (filename without the .keyset). Examples: Default, Minimal, and None', 'string');
4440 UPDATE actor.org_unit_setting SET value='"circ"' WHERE name = 'ui.general.button_bar' AND value='true';
4442 UPDATE actor.org_unit_setting SET value='"none"' WHERE name = 'ui.general.button_bar' AND value='false';
4446 INSERT into config.org_unit_setting_type
4447 ( name, label, description, datatype, fm_class ) VALUES
4448 ( 'cat.default_copy_status_fast',
4449 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Cataloging: Default copy status (fast add)', 'coust', 'label'),
4450 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Default status when a copy is created using the "Fast Add" interface.', 'coust', 'description'),
4454 INSERT into config.org_unit_setting_type
4455 ( name, label, description, datatype, fm_class ) VALUES
4456 ( 'cat.default_copy_status_normal',
4457 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Cataloging: Default copy status (normal)', 'coust', 'label'),
4458 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Default status when a copy is created using the normal volume/copy creator interface.', 'coust', 'description'),
4463 INSERT into config.org_unit_setting_type
4464 ( name, label, description, datatype ) VALUES
4465 ( 'ui.unified_volume_copy_editor',
4466 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'GUI: Unified Volume/Item Creator/Editor', 'coust', 'label'),
4467 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'If true combines the Volume/Copy Creator and Item Attribute Editor in some instances.', 'coust', 'description'),
4472 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
4474 transformed_xml TEXT;
4477 xfrm config.xml_transform%ROWTYPE;
4479 new_attrs HSTORE := ''::HSTORE;
4480 attr_def config.record_attr_definition%ROWTYPE;
4483 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
4484 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
4485 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
4486 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
4487 DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
4488 RETURN NEW; -- and we're done
4491 IF TG_OP = 'UPDATE' THEN -- re-ingest?
4492 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
4494 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
4499 -- Record authority linking
4500 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
4502 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
4505 -- Flatten and insert the mfr data
4506 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
4508 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
4510 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
4511 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
4513 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
4515 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
4516 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
4517 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
4518 WHERE record = NEW.id
4519 AND tag LIKE attr_def.tag
4521 WHEN attr_def.sf_list IS NOT NULL
4522 THEN POSITION(subfield IN attr_def.sf_list) > 0
4529 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
4530 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
4532 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
4534 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
4536 -- See if we can skip the XSLT ... it's expensive
4537 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
4538 -- Can't skip the transform
4539 IF xfrm.xslt <> '---' THEN
4540 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
4542 transformed_xml := NEW.marc;
4545 prev_xfrm := xfrm.name;
4548 IF xfrm.name IS NULL THEN
4549 -- just grab the marcxml (empty) transform
4550 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
4551 prev_xfrm := xfrm.name;
4554 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
4556 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
4557 SELECT m.value INTO attr_value
4558 FROM biblio.marc21_physical_characteristics(NEW.id) v
4559 JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
4560 WHERE v.subfield = attr_def.phys_char_sf
4561 LIMIT 1; -- Just in case ...
4565 -- apply index normalizers to attr_value
4567 SELECT n.func AS func,
4568 n.param_count AS param_count,
4570 FROM config.index_normalizer n
4571 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
4572 WHERE attr = attr_def.name
4574 EXECUTE 'SELECT ' || normalizer.func || '(' ||
4575 quote_literal( attr_value ) ||
4577 WHEN normalizer.param_count > 0
4578 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
4581 ')' INTO attr_value;
4585 -- Add the new value to the hstore
4586 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
4590 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
4591 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
4593 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
4599 -- Gather and insert the field entry data
4600 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
4602 -- Located URI magic
4603 IF TG_OP = 'INSERT' THEN
4604 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4606 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4609 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4611 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4615 -- (re)map metarecord-bib linking
4616 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
4617 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
4619 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4621 ELSE -- we're doing an update, and we're not deleted, remap
4622 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
4624 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4630 $func$ LANGUAGE PLPGSQL;
4632 ALTER TABLE config.circ_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6,2) NOT NULL DEFAULT 0.0;
4634 UPDATE config.circ_matrix_weights
4635 SET marc_bib_level = marc_vr_format;
4637 ALTER TABLE config.hold_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6, 2) NOT NULL DEFAULT 0.0;
4639 UPDATE config.hold_matrix_weights
4640 SET marc_bib_level = marc_vr_format;
4642 ALTER TABLE config.circ_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4644 ALTER TABLE config.hold_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4646 CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, item_object asset.copy, user_object actor.usr, renewal BOOL ) RETURNS action.found_circ_matrix_matchpoint AS $func$
4648 cn_object asset.call_number%ROWTYPE;
4649 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4650 cur_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4651 matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4652 weights config.circ_matrix_weights%ROWTYPE;
4654 denominator NUMERIC(6,2);
4656 result action.found_circ_matrix_matchpoint;
4659 result.success = false;
4661 -- Fetch useful data
4662 SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4663 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
4665 -- Pre-generate this so we only calc it once
4666 IF user_object.dob IS NOT NULL THEN
4667 SELECT INTO user_age age(user_object.dob);
4670 -- Grab the closest set circ weight setting.
4671 SELECT INTO weights cw.*
4672 FROM config.weight_assoc wa
4673 JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
4674 JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
4679 -- No weights? Bad admin! Defaults to handle that anyway.
4680 IF weights.id IS NULL THEN
4681 weights.grp := 11.0;
4682 weights.org_unit := 10.0;
4683 weights.circ_modifier := 5.0;
4684 weights.marc_type := 4.0;
4685 weights.marc_form := 3.0;
4686 weights.marc_bib_level := 2.0;
4687 weights.marc_vr_format := 2.0;
4688 weights.copy_circ_lib := 8.0;
4689 weights.copy_owning_lib := 8.0;
4690 weights.user_home_ou := 8.0;
4691 weights.ref_flag := 1.0;
4692 weights.juvenile_flag := 6.0;
4693 weights.is_renewal := 7.0;
4694 weights.usr_age_lower_bound := 0.0;
4695 weights.usr_age_upper_bound := 0.0;
4698 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4699 -- If you break your org tree with funky parenting this may be wrong
4700 -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
4701 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4702 WITH all_distance(distance) AS (
4703 SELECT depth AS distance FROM actor.org_unit_type
4705 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4707 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4709 -- Loop over all the potential matchpoints
4710 FOR cur_matchpoint IN
4712 FROM config.circ_matrix_matchpoint m
4713 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
4714 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
4715 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
4716 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
4717 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4719 -- Permission Groups
4720 -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
4722 -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
4723 AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
4724 AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
4725 AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
4727 AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
4728 -- Static User Checks
4729 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4730 AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
4731 AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
4732 -- Static Item Checks
4733 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4734 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4735 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4736 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4737 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4738 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4740 -- Permission Groups
4741 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
4743 CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
4744 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
4745 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
4746 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4747 -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
4748 CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
4749 -- Static User Checks
4750 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4751 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
4752 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
4753 -- Static Item Checks
4754 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4755 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4756 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4757 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4758 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4759 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4760 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4763 -- Record the full matching row list
4764 row_list := row_list || cur_matchpoint.id;
4766 -- No matchpoint yet?
4767 IF matchpoint.id IS NULL THEN
4768 -- Take the entire matchpoint as a starting point
4769 matchpoint := cur_matchpoint;
4770 CONTINUE; -- No need to look at this row any more.
4773 -- Incomplete matchpoint?
4774 IF matchpoint.circulate IS NULL THEN
4775 matchpoint.circulate := cur_matchpoint.circulate;
4777 IF matchpoint.duration_rule IS NULL THEN
4778 matchpoint.duration_rule := cur_matchpoint.duration_rule;
4780 IF matchpoint.recurring_fine_rule IS NULL THEN
4781 matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
4783 IF matchpoint.max_fine_rule IS NULL THEN
4784 matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
4786 IF matchpoint.hard_due_date IS NULL THEN
4787 matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
4789 IF matchpoint.total_copy_hold_ratio IS NULL THEN
4790 matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
4792 IF matchpoint.available_copy_hold_ratio IS NULL THEN
4793 matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
4795 IF matchpoint.renewals IS NULL THEN
4796 matchpoint.renewals := cur_matchpoint.renewals;
4798 IF matchpoint.grace_period IS NULL THEN
4799 matchpoint.grace_period := cur_matchpoint.grace_period;
4803 -- Check required fields
4804 IF matchpoint.circulate IS NOT NULL AND
4805 matchpoint.duration_rule IS NOT NULL AND
4806 matchpoint.recurring_fine_rule IS NOT NULL AND
4807 matchpoint.max_fine_rule IS NOT NULL THEN
4808 -- All there? We have a completed match.
4809 result.success := true;
4812 -- Include the assembled matchpoint, even if it isn't complete
4813 result.matchpoint := matchpoint;
4815 -- Include (for debugging) the full list of matching rows
4816 result.buildrows := row_list;
4818 -- Hand the result back to caller
4821 $func$ LANGUAGE plpgsql;
4823 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
4827 requestor_object actor.usr%ROWTYPE;
4828 user_object actor.usr%ROWTYPE;
4829 item_object asset.copy%ROWTYPE;
4830 item_cn_object asset.call_number%ROWTYPE;
4831 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4832 matchpoint config.hold_matrix_matchpoint%ROWTYPE;
4833 weights config.hold_matrix_weights%ROWTYPE;
4834 denominator NUMERIC(6,2);
4836 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
4837 SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
4838 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
4839 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4840 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
4842 -- The item's owner should probably be the one determining if the item is holdable
4843 -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
4844 -- This flag will allow for setting it to the owning library (where the call number "lives")
4845 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
4847 -- Grab the closest set circ weight setting.
4849 -- Default to circ library
4850 SELECT INTO weights hw.*
4851 FROM config.weight_assoc wa
4852 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4853 JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
4858 -- Flag is set, use owning library
4859 SELECT INTO weights hw.*
4860 FROM config.weight_assoc wa
4861 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4862 JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
4868 -- No weights? Bad admin! Defaults to handle that anyway.
4869 IF weights.id IS NULL THEN
4870 weights.user_home_ou := 5.0;
4871 weights.request_ou := 5.0;
4872 weights.pickup_ou := 5.0;
4873 weights.item_owning_ou := 5.0;
4874 weights.item_circ_ou := 5.0;
4875 weights.usr_grp := 7.0;
4876 weights.requestor_grp := 8.0;
4877 weights.circ_modifier := 4.0;
4878 weights.marc_type := 3.0;
4879 weights.marc_form := 2.0;
4880 weights.marc_bib_level := 1.0;
4881 weights.marc_vr_format := 1.0;
4882 weights.juvenile_flag := 4.0;
4883 weights.ref_flag := 0.0;
4886 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4887 -- If you break your org tree with funky parenting this may be wrong
4888 -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
4889 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4890 WITH all_distance(distance) AS (
4891 SELECT depth AS distance FROM actor.org_unit_type
4893 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4895 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4897 -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
4898 -- This may be better implemented as part of the upgrade script?
4899 -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
4900 -- Then remove this flag, of course.
4901 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
4904 -- Note: This, to me, is REALLY hacky. I put it in anyway.
4905 -- If you can't tell, this is a single call swap on two variables.
4906 SELECT INTO user_object.profile, requestor_object.profile
4907 requestor_object.profile, user_object.profile;
4910 -- Select the winning matchpoint into the matchpoint variable for returning
4911 SELECT INTO matchpoint m.*
4912 FROM config.hold_matrix_matchpoint m
4913 /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
4914 LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
4915 LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
4916 LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
4917 LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
4918 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
4919 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4921 -- Permission Groups
4922 -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
4923 AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
4925 AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
4926 AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
4927 AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
4928 AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
4929 AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
4930 -- Static User Checks
4931 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4932 -- Static Item Checks
4933 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4934 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4935 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4936 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4937 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4938 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4940 -- Permission Groups
4941 CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
4942 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
4944 CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
4945 CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
4946 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
4947 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
4948 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4949 -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
4950 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4951 -- Static Item Checks
4952 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4953 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4954 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4955 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4956 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4957 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4958 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4961 -- Return just the ID for now
4962 RETURN matchpoint.id;
4964 $func$ LANGUAGE 'plpgsql';
4967 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
4970 use MARC::File::XML (BinaryEncoding => 'UTF-8');
4973 use Unicode::Normalize;
4975 MARC::Charset->assume_unicode(1);
4977 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
4978 my $schema = $_TD->{table_schema};
4979 my $rec_id = $_TD->{new}{id};
4981 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
4982 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
4983 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
4987 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
4988 my $ou_cni = 'EVRGRN';
4991 if ($schema eq 'serial') {
4992 $owner = $_TD->{new}{owning_lib};
4994 # are.owner and bre.owner can be null, so fall back to the consortial setting
4995 $owner = $_TD->{new}{owner} || 1;
4998 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
4999 if ($ous_rv->{processed}) {
5000 $ou_cni = $ous_rv->{rows}[0]->{value};
5001 $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
5003 # Fall back to the shortname of the OU if there was no OU setting
5004 $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
5005 if ($ous_rv->{processed}) {
5006 $ou_cni = $ous_rv->{rows}[0]->{shortname};
5010 my ($create, $munge) = (0, 0);
5012 my @scns = $record->field('035');
5014 foreach my $id_field ('001', '003') {
5016 my @controls = $record->field($id_field);
5018 if ($id_field eq '001') {
5019 $spec_value = $rec_id;
5021 $spec_value = $ou_cni;
5024 # Create the 001/003 if none exist
5025 if (scalar(@controls) == 1) {
5026 # Only one field; check to see if we need to munge it
5027 unless (grep $_->data() eq $spec_value, @controls) {
5031 # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
5032 foreach my $control (@controls) {
5033 unless ($control->data() eq $spec_value) {
5034 $record->delete_field($control);
5037 $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
5042 # Now, if we need to munge the 001, we will first push the existing 001/003
5043 # into the 035; but if the record did not have one (and one only) 001 and 003
5044 # to begin with, skip this process
5045 if ($munge and not $create) {
5046 my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
5048 # Do not create duplicate 035 fields
5049 unless (grep $_->subfield('a') eq $scn, @scns) {
5050 $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
5054 # Set the 001/003 and update the MARC
5055 if ($create or $munge) {
5056 $record->field('001')->data($rec_id);
5057 $record->field('003')->data($ou_cni);
5059 my $xml = $record->as_xml_record();
5061 $xml =~ s/^<\?xml.+\?\s*>//go;
5062 $xml =~ s/>\s+</></go;
5063 $xml =~ s/\p{Cc}//go;
5065 # Embed a version of OpenILS::Application::AppUtils->entityize()
5066 # to avoid having to set PERL5LIB for PostgreSQL as well
5068 # If we are going to convert non-ASCII characters to XML entities,
5069 # we had better be dealing with a UTF8 string to begin with
5070 $xml = decode_utf8($xml);
5074 # Convert raw ampersands to entities
5075 $xml =~ s/&(?!\S+;)/&/gso;
5077 # Convert Unicode characters to entities
5078 $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
5080 $xml =~ s/[\x00-\x1f]//go;
5081 $_TD->{new}{marc} = $xml;
5087 $func$ LANGUAGE PLPERLU;
5089 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
5092 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5095 MARC::Charset->assume_unicode(1);
5098 my $r = MARC::Record->new_from_xml( $xml );
5100 return undef unless ($r);
5102 my $id = shift() || $r->subfield( '901' => 'c' );
5103 $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
5104 return undef unless ($id); # We need an ID!
5106 my $tmpl = MARC::Record->new();
5107 $tmpl->encoding( 'UTF-8' );
5110 for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
5112 my $tag = $field->tag;
5113 my $i1 = $field->indicator(1);
5114 my $i2 = $field->indicator(2);
5115 my $sf = join '', map { $_->[0] } $field->subfields;
5116 my @data = map { @$_ } $field->subfields;
5120 # Map the authority field to bib fields it can control.
5121 if ($tag >= 100 and $tag <= 111) { # names
5122 @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
5123 } elsif ($tag eq '130') { # uniform title
5124 @replace_them = qw/130 240 440 730 830/;
5125 } elsif ($tag >= 150 and $tag <= 155) { # subjects
5126 @replace_them = ($tag + 500);
5127 } elsif ($tag >= 180 and $tag <= 185) { # floating subdivisions
5128 @replace_them = qw/100 400 600 700 800 110 410 610 710 810 111 411 611 711 811 130 240 440 730 830 650 651 655/;
5133 # Dummy up the bib-side data
5134 $tmpl->append_fields(
5136 MARC::Field->new( $_, $i1, $i2, @data )
5140 # Construct some 'replace' rules
5141 push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
5144 # Insert the replace rules into the template
5145 $tmpl->append_fields(
5146 MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
5149 $xml = $tmpl->as_xml_record;
5150 $xml =~ s/^<\?.+?\?>$//mo;
5152 $xml =~ s/>\s+</></sgo;
5156 $func$ LANGUAGE PLPERLU;
5158 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
5161 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5165 MARC::Charset->assume_unicode(1);
5167 my $target_xml = shift;
5168 my $source_xml = shift;
5169 my $field_spec = shift;
5170 my $force_add = shift || 0;
5172 my $target_r = MARC::Record->new_from_xml( $target_xml );
5173 my $source_r = MARC::Record->new_from_xml( $source_xml );
5175 return $target_xml unless ($target_r && $source_r);
5177 my @field_list = split(',', $field_spec);
5180 for my $f (@field_list) {
5181 $f =~ s/^\s*//; $f =~ s/\s*$//;
5182 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5188 $match =~ s/^\s*//; $match =~ s/\s*$//;
5189 $fields{$field} = { sf => [ split('', $sf) ] };
5191 my ($msf,$mre) = split('~', $match);
5192 if (length($msf) > 0 and length($mre) > 0) {
5193 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5194 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5195 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5201 for my $f ( keys %fields) {
5202 if ( @{$fields{$f}{sf}} ) {
5203 for my $from_field ($source_r->field( $f )) {
5204 my @tos = $target_r->field( $f );
5206 next if (exists($fields{$f}{match}) and !$force_add);
5207 my @new_fields = map { $_->clone } $source_r->field( $f );
5208 $target_r->insert_fields_ordered( @new_fields );
5210 for my $to_field (@tos) {
5211 if (exists($fields{$f}{match})) {
5212 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5214 my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
5215 $to_field->add_subfields( @new_sf );
5220 my @new_fields = map { $_->clone } $source_r->field( $f );
5221 $target_r->insert_fields_ordered( @new_fields );
5225 $target_xml = $target_r->as_xml_record;
5226 $target_xml =~ s/^<\?.+?\?>$//mo;
5227 $target_xml =~ s/\n//sgo;
5228 $target_xml =~ s/>\s+</></sgo;
5232 $_$ LANGUAGE PLPERLU;
5234 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
5240 use MARC::File::XML (BinaryEncoding => 'UTF8');
5242 use UUID::Tiny ':std';
5244 MARC::Charset->assume_unicode(1);
5246 my $xml = shift() or return undef;
5250 # Prevent errors in XML parsing from blowing out ungracefully
5252 $r = MARC::Record->new_from_xml( $xml );
5255 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5259 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5262 # From http://www.loc.gov/standards/sourcelist/subject.html
5263 my $thes_code_map = {
5269 n => 'notapplicable',
5275 # Default to "No attempt to code" if the leader is horribly broken
5276 my $fixed_field = $r->field('008');
5277 my $thes_char = '|';
5279 $thes_char = substr($fixed_field->data(), 11, 1) || '|';
5282 my $thes_code = 'UNDEFINED';
5284 if ($thes_char eq 'z') {
5285 # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
5286 $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
5287 } elsif ($thes_code_map->{$thes_char}) {
5288 $thes_code = $thes_code_map->{$thes_char};
5292 my $head = $r->field('1..');
5294 # Concatenate all of these subfields together, prefixed by their code
5295 # to prevent collisions along the lines of "Fiction, North Carolina"
5296 foreach my $sf ($head->subfields()) {
5297 $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
5302 my $stmt = spi_prepare('SELECT public.naco_normalize($1) AS norm_text', 'TEXT');
5303 my $result = spi_exec_prepared($stmt, $auth_txt);
5304 my $norm_txt = $result->{rows}[0]->{norm_text};
5305 spi_freeplan($stmt);
5307 return $head->tag() . "_" . $thes_code . " " . $norm_txt;
5310 return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
5311 $func$ LANGUAGE 'plperlu' IMMUTABLE;
5313 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
5316 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5320 MARC::Charset->assume_unicode(1);
5323 my $r = MARC::Record->new_from_xml( $xml );
5325 return $xml unless ($r);
5327 my $field_spec = shift;
5328 my @field_list = split(',', $field_spec);
5331 for my $f (@field_list) {
5332 $f =~ s/^\s*//; $f =~ s/\s*$//;
5333 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5339 $match =~ s/^\s*//; $match =~ s/\s*$//;
5340 $fields{$field} = { sf => [ split('', $sf) ] };
5342 my ($msf,$mre) = split('~', $match);
5343 if (length($msf) > 0 and length($mre) > 0) {
5344 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5345 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5346 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5352 for my $f ( keys %fields) {
5353 for my $to_field ($r->field( $f )) {
5354 if (exists($fields{$f}{match})) {
5355 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5358 if ( @{$fields{$f}{sf}} ) {
5359 $to_field->delete_subfield(code => $fields{$f}{sf});
5361 $r->delete_field( $to_field );
5366 $xml = $r->as_xml_record;
5367 $xml =~ s/^<\?.+?\?>$//mo;
5369 $xml =~ s/>\s+</></sgo;
5373 $_$ LANGUAGE PLPERLU;
5375 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5378 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5381 MARC::Charset->assume_unicode(1);
5384 my $r = MARC::Record->new_from_xml( $xml );
5386 return_next( { tag => 'LDR', value => $r->leader } );
5388 for my $f ( $r->fields ) {
5389 if ($f->is_control_field) {
5390 return_next({ tag => $f->tag, value => $f->data });
5392 for my $s ($f->subfields) {
5395 ind1 => $f->indicator(1),
5396 ind2 => $f->indicator(2),
5397 subfield => $s->[0],
5401 if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5402 my $trim = $f->indicator(2) || 0;
5405 ind1 => $f->indicator(1),
5406 ind2 => $f->indicator(2),
5408 value => substr( $s->[1], $trim )
5417 $func$ LANGUAGE PLPERLU;
5419 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
5422 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5425 MARC::Charset->assume_unicode(1);
5428 my $r = MARC::Record->new_from_xml( $xml );
5430 return_next( { tag => 'LDR', value => $r->leader } );
5432 for my $f ( $r->fields ) {
5433 if ($f->is_control_field) {
5434 return_next({ tag => $f->tag, value => $f->data });
5436 for my $s ($f->subfields) {
5439 ind1 => $f->indicator(1),
5440 ind2 => $f->indicator(2),
5441 subfield => $s->[0],
5451 $func$ LANGUAGE PLPERLU;
5454 CREATE INDEX actor_usr_day_phone_idx_numeric ON actor.usr USING BTREE
5455 (evergreen.lowercase(REGEXP_REPLACE(day_phone, '[^0-9]', '', 'g')));
5457 CREATE INDEX actor_usr_evening_phone_idx_numeric ON actor.usr USING BTREE
5458 (evergreen.lowercase(REGEXP_REPLACE(evening_phone, '[^0-9]', '', 'g')));
5460 CREATE INDEX actor_usr_other_phone_idx_numeric ON actor.usr USING BTREE
5461 (evergreen.lowercase(REGEXP_REPLACE(other_phone, '[^0-9]', '', 'g')));
5464 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5469 -- If there are any renewals for this circulation, don't archive or delete
5470 -- it yet. We'll do so later, when we archive and delete the renewals.
5472 SELECT 'Y' INTO found
5473 FROM action.circulation
5474 WHERE parent_circ = OLD.id
5478 RETURN NULL; -- don't delete
5481 -- Archive a copy of the old row to action.aged_circulation
5483 INSERT INTO action.aged_circulation
5484 (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5485 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5486 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5487 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5488 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5489 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5491 id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5492 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5493 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5494 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5495 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5496 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5497 FROM action.all_circulation WHERE id = OLD.id;
5501 $$ LANGUAGE 'plpgsql';
5504 CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT, retargetting BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
5507 user_object actor.usr%ROWTYPE;
5508 age_protect_object config.rule_age_hold_protect%ROWTYPE;
5509 standing_penalty config.standing_penalty%ROWTYPE;
5510 transit_range_ou_type actor.org_unit_type%ROWTYPE;
5511 transit_source actor.org_unit%ROWTYPE;
5512 item_object asset.copy%ROWTYPE;
5513 item_cn_object asset.call_number%ROWTYPE;
5514 ou_skip actor.org_unit_setting%ROWTYPE;
5515 result action.matrix_test_result;
5516 hold_test config.hold_matrix_matchpoint%ROWTYPE;
5518 hold_transit_prox INT;
5519 frozen_hold_count INT;
5520 context_org_list INT[];
5523 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5524 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5526 result.success := TRUE;
5528 -- Fail if we couldn't find a user
5529 IF user_object.id IS NULL THEN
5530 result.fail_part := 'no_user';
5531 result.success := FALSE;
5537 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5539 -- Fail if we couldn't find a copy
5540 IF item_object.id IS NULL THEN
5541 result.fail_part := 'no_item';
5542 result.success := FALSE;
5548 SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5549 result.matchpoint := matchpoint_id;
5551 SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5553 -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5554 IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5555 result.fail_part := 'circ.holds.target_skip_me';
5556 result.success := FALSE;
5562 -- Fail if user is barred
5563 IF user_object.barred IS TRUE THEN
5564 result.fail_part := 'actor.usr.barred';
5565 result.success := FALSE;
5571 -- Fail if we couldn't find any matchpoint (requires a default)
5572 IF matchpoint_id IS NULL THEN
5573 result.fail_part := 'no_matchpoint';
5574 result.success := FALSE;
5580 SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5582 IF hold_test.holdable IS FALSE THEN
5583 result.fail_part := 'config.hold_matrix_test.holdable';
5584 result.success := FALSE;
5589 IF hold_test.transit_range IS NOT NULL THEN
5590 SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5591 IF hold_test.distance_is_from_owner THEN
5592 SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number;
5594 SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5597 PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5600 result.fail_part := 'transit_range';
5601 result.success := FALSE;
5607 FOR standing_penalty IN
5608 SELECT DISTINCT csp.*
5609 FROM actor.usr_standing_penalty usp
5610 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5611 WHERE usr = match_user
5612 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5613 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5614 AND csp.block_list LIKE '%HOLD%' LOOP
5616 result.fail_part := standing_penalty.name;
5617 result.success := FALSE;
5622 IF hold_test.stop_blocked_user IS TRUE THEN
5623 FOR standing_penalty IN
5624 SELECT DISTINCT csp.*
5625 FROM actor.usr_standing_penalty usp
5626 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5627 WHERE usr = match_user
5628 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5629 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5630 AND csp.block_list LIKE '%CIRC%' LOOP
5632 result.fail_part := standing_penalty.name;
5633 result.success := FALSE;
5639 IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
5640 SELECT INTO hold_count COUNT(*)
5641 FROM action.hold_request
5642 WHERE usr = match_user
5643 AND fulfillment_time IS NULL
5644 AND cancel_time IS NULL
5645 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5647 IF hold_count >= hold_test.max_holds THEN
5648 result.fail_part := 'config.hold_matrix_test.max_holds';
5649 result.success := FALSE;
5655 IF item_object.age_protect IS NOT NULL THEN
5656 SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5658 IF item_object.create_date + age_protect_object.age > NOW() THEN
5659 IF hold_test.distance_is_from_owner THEN
5660 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
5661 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5663 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5666 IF hold_transit_prox > age_protect_object.prox THEN
5667 result.fail_part := 'config.rule_age_hold_protect.prox';
5668 result.success := FALSE;
5681 $func$ LANGUAGE plpgsql;
5683 -- do potentially large updates last to save time if upgrader needs
5684 -- to manually tweak the upgrade script to resolve errors
5687 UPDATE metabib.facet_entry SET value = evergreen.force_unicode_normal_form(value,'NFC');
5689 -- Update reporter.materialized_simple_record with normalized ISBN values
5690 -- This might not get all of them, but most ISBNs will have more than one hyphen
5691 DELETE FROM reporter.materialized_simple_record WHERE id IN (
5692 SELECT record FROM metabib.full_rec WHERE tag = '020' AND subfield IN ('a', 'z') AND value LIKE '%-%-%'
5695 INSERT INTO reporter.materialized_simple_record
5696 SELECT DISTINCT rossr.* FROM reporter.old_super_simple_record rossr INNER JOIN metabib.full_rec mfr ON mfr.record = rossr.id
5697 WHERE mfr.tag = '020' AND mfr.subfield IN ('a', 'z') AND mfr.value LIKE '%-%-%'
5700 INSERT INTO config.upgrade_log (version) VALUES ('0542'); -- phasefx
5702 INSERT INTO permission.perm_list
5705 (485, 'CREATE_VOLUME_SUFFIX', oils_i18n_gettext(485, 'Create suffix label definition.', 'ppl', 'description'))
5706 ,(486, 'UPDATE_VOLUME_SUFFIX', oils_i18n_gettext(486, 'Update suffix label definition.', 'ppl', 'description'))
5707 ,(487, 'DELETE_VOLUME_SUFFIX', oils_i18n_gettext(487, 'Delete suffix label definition.', 'ppl', 'description'))
5708 ,(488, 'CREATE_VOLUME_PREFIX', oils_i18n_gettext(488, 'Create prefix label definition.', 'ppl', 'description'))
5709 ,(489, 'UPDATE_VOLUME_PREFIX', oils_i18n_gettext(489, 'Update prefix label definition.', 'ppl', 'description'))
5710 ,(490, 'DELETE_VOLUME_PREFIX', oils_i18n_gettext(490, 'Delete prefix label definition.', 'ppl', 'description'))
5711 ,(491, 'CREATE_MONOGRAPH_PART', oils_i18n_gettext(491, 'Create monograph part definition.', 'ppl', 'description'))
5712 ,(492, 'UPDATE_MONOGRAPH_PART', oils_i18n_gettext(492, 'Update monograph part definition.', 'ppl', 'description'))
5713 ,(493, 'DELETE_MONOGRAPH_PART', oils_i18n_gettext(493, 'Delete monograph part definition.', 'ppl', 'description'))
5714 ,(494, 'ADMIN_CODED_VALUE', oils_i18n_gettext(494, 'Create/Update/Delete SVF Record Attribute Coded Value Map', 'ppl', 'description'))
5715 ,(495, 'ADMIN_SERIAL_ITEM', oils_i18n_gettext(495, 'Create/Retrieve/Update/Delete Serial Item', 'ppl', 'description'))
5716 ,(496, 'ADMIN_SVF', oils_i18n_gettext(496, 'Create/Update/Delete SVF Record Attribute Defintion', 'ppl', 'description'))
5717 ,(497, 'CREATE_BIB_PTYPE', oils_i18n_gettext(497, 'Create Bibliographic Record Peer Type', 'ppl', 'description'))
5718 ,(498, 'CREATE_PURCHASE_REQUEST', oils_i18n_gettext(498, 'Create User Purchase Request', 'ppl', 'description'))
5719 ,(499, 'DELETE_BIB_PTYPE', oils_i18n_gettext(499, 'Delete Bibliographic Record Peer Type', 'ppl', 'description'))
5720 ,(500, 'MAP_MONOGRAPH_PART', oils_i18n_gettext(500, 'Create/Update/Delete Copy Monograph Part Map', 'ppl', 'description'))
5721 ,(501, 'MARK_ITEM_MISSING_PIECES', oils_i18n_gettext(501, 'Allows the Mark Item Missing Pieces action.', 'ppl', 'description'))
5722 ,(502, 'UPDATE_BIB_PTYPE', oils_i18n_gettext(502, 'Update Bibliographic Record Peer Type', 'ppl', 'description'))
5723 ,(503, 'UPDATE_HOLD_REQUEST_TIME', oils_i18n_gettext(503, 'Allows editing of a hold''s request time, and/or its Cut-in-line/Top-of-queue flag.', 'ppl', 'description'))
5724 ,(504, 'UPDATE_PICKLIST', oils_i18n_gettext(504, 'Allows update/re-use of an acquisitions pick/selection list.', 'ppl', 'description'))
5725 ,(505, 'UPDATE_WORKSTATION', oils_i18n_gettext(505, 'Allows update of a workstation during workstation registration override.', 'ppl', 'description'))
5726 ,(506, 'VIEW_USER_SETTING_TYPE', oils_i18n_gettext(506, 'Allows viewing of configurable user setting types.', 'ppl', 'description'))
5727 ) AS np(id,code,description)
5728 LEFT JOIN permission.perm_list pl USING (code)
5729 WHERE pl.id IS NULL;
5733 -- add new perms AND catch up on some missed upgrade data, if needed
5735 -- Prevent conflicts with existing custom permission groups; as of 2.0.10,
5736 -- highest permission.grp_tree ID was 10 for Local System Administrator
5741 SELECT id FROM permission.grp_tree WHERE id > 10 ORDER BY id DESC
5743 UPDATE permission.grp_tree SET id = id + 100 WHERE id = i;
5747 UPDATE permission.grp_tree SET parent = parent + 100 WHERE parent > 10;
5748 UPDATE actor.usr SET profile = profile + 100 WHERE profile > 10;
5749 UPDATE permission.grp_perm_map SET grp = grp + 100 WHERE grp > 10;
5750 UPDATE permission.usr_grp_map SET grp = grp + 100 WHERE grp > 10;
5751 UPDATE permission.grp_penalty_threshold SET grp = grp + 100 WHERE grp > 10;
5752 UPDATE config.circ_matrix_matchpoint SET grp = grp + 100 WHERE grp > 10;
5753 UPDATE config.hold_matrix_matchpoint SET requestor_grp = requestor_grp + 100 WHERE requestor_grp > 10;
5754 UPDATE config.hold_matrix_matchpoint SET usr_grp = usr_grp + 100 WHERE usr_grp > 10;
5756 -- we could get away from these fixed-id inserts here, but then this
5757 -- upgrade would be ahead of the mainline, I think
5759 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5760 SELECT 8, oils_i18n_gettext(8, 'Cataloging Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.cat_admin'
5763 FROM permission.grp_tree
5768 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5769 SELECT 9, oils_i18n_gettext(9, 'Circulation Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.circ_admin'
5772 FROM permission.grp_tree
5777 UPDATE permission.grp_tree SET description = oils_i18n_gettext(10, 'Can do anything at the Branch level', 'pgt', 'description') WHERE id = 10;
5779 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5780 SELECT 11, oils_i18n_gettext(11, 'Serials', 'pgt', 'name'), 3, oils_i18n_gettext(11, 'Serials (includes admin features)', 'pgt', 'description'), '3 years', TRUE, 'group_application.user.staff.serials'
5783 FROM permission.grp_tree
5788 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5789 SELECT 12, oils_i18n_gettext(12, 'System Administrator', 'pgt', 'name'), 3, oils_i18n_gettext(12, 'Can do anything at the System level', 'pgt', 'description'), '3 years', TRUE, 'group_application.user.staff.admin.system_admin'
5792 FROM permission.grp_tree
5797 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5798 SELECT 13, oils_i18n_gettext(13, 'Global Administrator', 'pgt', 'name'), 3, oils_i18n_gettext(13, 'Can do anything at the Consortium level', 'pgt', 'description'), '3 years', TRUE, 'group_application.user.staff.admin.global_admin'
5801 FROM permission.grp_tree
5806 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5807 SELECT 14, oils_i18n_gettext(14, 'Data Review', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.data_review'
5810 FROM permission.grp_tree
5815 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5816 SELECT 15, oils_i18n_gettext(15, 'Volunteers', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.volunteers'
5819 FROM permission.grp_tree
5824 SELECT SETVAL('permission.grp_tree_id_seq'::TEXT, (SELECT MAX(id) FROM permission.grp_tree));
5826 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5828 pgt.id, perm.id, aout.depth, TRUE
5830 permission.grp_tree pgt,
5831 permission.perm_list perm,
5832 actor.org_unit_type aout
5834 pgt.name = 'Cataloging Administrator' AND
5835 aout.name = 'Consortium' AND
5837 'ADMIN_IMPORT_ITEM_ATTR_DEF',
5838 'ADMIN_MERGE_PROFILE',
5839 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
5840 'CREATE_BIB_IMPORT_FIELD_DEF',
5842 'CREATE_BIB_SOURCE',
5843 'CREATE_IMPORT_ITEM_ATTR_DEF',
5844 'CREATE_IMPORT_TRASH_FIELD',
5845 'CREATE_MERGE_PROFILE',
5846 'CREATE_MONOGRAPH_PART',
5847 'CREATE_VOLUME_PREFIX',
5848 'CREATE_VOLUME_SUFFIX',
5849 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5851 'DELETE_BIB_SOURCE',
5852 'DELETE_IMPORT_ITEM_ATTR_DEF',
5853 'DELETE_IMPORT_TRASH_FIELD',
5854 'DELETE_MERGE_PROFILE',
5855 'DELETE_MONOGRAPH_PART',
5856 'DELETE_VOLUME_PREFIX',
5857 'DELETE_VOLUME_SUFFIX',
5858 'MAP_MONOGRAPH_PART',
5859 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5860 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
5862 'UPDATE_IMPORT_ITEM_ATTR_DEF',
5863 'UPDATE_IMPORT_TRASH_FIELD',
5864 'UPDATE_MERGE_PROFILE',
5865 'UPDATE_MONOGRAPH_PART',
5866 'UPDATE_VOLUME_PREFIX',
5867 'UPDATE_VOLUME_SUFFIX'
5870 FROM permission.grp_perm_map AS map
5873 AND map.perm = perm.id
5876 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5878 pgt.id, perm.id, aout.depth, TRUE
5880 permission.grp_tree pgt,
5881 permission.perm_list perm,
5882 actor.org_unit_type aout
5884 pgt.name = 'Circulation Administrator' AND
5885 aout.name = 'Branch' AND
5890 FROM permission.grp_perm_map AS map
5893 AND map.perm = perm.id
5896 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5898 pgt.id, perm.id, aout.depth, TRUE
5900 permission.grp_tree pgt,
5901 permission.perm_list perm,
5902 actor.org_unit_type aout
5904 pgt.name = 'Circulation Administrator' AND
5905 aout.name = 'Consortium' AND
5907 'ADMIN_MAX_FINE_RULE',
5908 'CREATE_CIRC_DURATION',
5909 'DELETE_CIRC_DURATION',
5910 'MARK_ITEM_MISSING_PIECES',
5911 'UPDATE_CIRC_DURATION',
5912 'UPDATE_HOLD_REQUEST_TIME',
5913 'UPDATE_NET_ACCESS_LEVEL',
5914 'VIEW_CIRC_MATRIX_MATCHPOINT',
5915 'VIEW_HOLD_MATRIX_MATCHPOINT'
5918 FROM permission.grp_perm_map AS map
5921 AND map.perm = perm.id
5924 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5926 pgt.id, perm.id, aout.depth, TRUE
5928 permission.grp_tree pgt,
5929 permission.perm_list perm,
5930 actor.org_unit_type aout
5932 pgt.name = 'Circulation Administrator' AND
5933 aout.name = 'System' AND
5935 'ADMIN_BOOKING_RESERVATION',
5936 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
5937 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
5938 'ADMIN_BOOKING_RESOURCE',
5939 'ADMIN_BOOKING_RESOURCE_ATTR',
5940 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
5941 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
5942 'ADMIN_BOOKING_RESOURCE_TYPE',
5943 'ADMIN_COPY_LOCATION_ORDER',
5944 'ADMIN_HOLD_CANCEL_CAUSE',
5945 'ASSIGN_GROUP_PERM',
5948 'COPY_TRANSIT_RECEIVE',
5950 'CREATE_BILLING_TYPE',
5951 'CREATE_NON_CAT_TYPE',
5952 'CREATE_PATRON_STAT_CAT',
5953 'CREATE_PATRON_STAT_CAT_ENTRY',
5954 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
5955 'CREATE_USER_GROUP_LINK',
5956 'DELETE_BILLING_TYPE',
5957 'DELETE_NON_CAT_TYPE',
5958 'DELETE_PATRON_STAT_CAT',
5959 'DELETE_PATRON_STAT_CAT_ENTRY',
5960 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
5962 'group_application.user.staff',
5964 'MARK_ITEM_AVAILABLE',
5965 'MARK_ITEM_BINDERY',
5966 'MARK_ITEM_CHECKED_OUT',
5968 'MARK_ITEM_IN_PROCESS',
5969 'MARK_ITEM_IN_TRANSIT',
5971 'MARK_ITEM_MISSING',
5972 'MARK_ITEM_ON_HOLDS_SHELF',
5973 'MARK_ITEM_ON_ORDER',
5974 'MARK_ITEM_RESHELVING',
5976 'money.collections_tracker.create',
5977 'money.collections_tracker.delete',
5981 'REMOVE_USER_GROUP_LINK',
5982 'SET_CIRC_CLAIMS_RETURNED',
5983 'SET_CIRC_CLAIMS_RETURNED.override',
5988 'UPDATE_NON_CAT_TYPE',
5989 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
5990 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
5991 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
5992 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
5994 'VIEW_REPORT_OUTPUT',
5995 'VIEW_STANDING_PENALTY',
6000 FROM permission.grp_perm_map AS map
6003 AND map.perm = perm.id
6006 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6008 pgt.id, perm.id, aout.depth, TRUE
6010 permission.grp_tree pgt,
6011 permission.perm_list perm,
6012 actor.org_unit_type aout
6014 pgt.name = 'Local Administrator' AND
6015 aout.name = 'Branch' AND
6020 FROM permission.grp_perm_map AS map
6023 AND map.perm = perm.id
6026 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6028 pgt.id, perm.id, aout.depth, FALSE
6030 permission.grp_tree pgt,
6031 permission.perm_list perm,
6032 actor.org_unit_type aout
6034 pgt.name = 'Serials' AND
6035 aout.name = 'System' AND
6037 'ADMIN_ASSET_COPY_TEMPLATE',
6038 'ADMIN_SERIAL_CAPTION_PATTERN',
6039 'ADMIN_SERIAL_DISTRIBUTION',
6040 'ADMIN_SERIAL_ITEM',
6041 'ADMIN_SERIAL_STREAM',
6042 'ADMIN_SERIAL_SUBSCRIPTION',
6047 FROM permission.grp_perm_map AS map
6050 AND map.perm = perm.id
6053 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6055 pgt.id, perm.id, aout.depth, TRUE
6057 permission.grp_tree pgt,
6058 permission.perm_list perm,
6059 actor.org_unit_type aout
6061 pgt.name = 'System Administrator' AND
6062 aout.name = 'System' AND
6067 FROM permission.grp_perm_map AS map
6070 AND map.perm = perm.id
6073 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6075 pgt.id, perm.id, aout.depth, FALSE
6077 permission.grp_tree pgt,
6078 permission.perm_list perm,
6079 actor.org_unit_type aout
6081 pgt.name = 'System Administrator' AND
6082 aout.name = 'Consortium' AND
6083 perm.code ~ '^VIEW_TRIGGER'
6086 FROM permission.grp_perm_map AS map
6089 AND map.perm = perm.id
6092 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6094 pgt.id, perm.id, aout.depth, TRUE
6096 permission.grp_tree pgt,
6097 permission.perm_list perm,
6098 actor.org_unit_type aout
6100 pgt.name = 'Global Administrator' AND
6101 aout.name = 'Consortium' AND
6106 FROM permission.grp_perm_map AS map
6109 AND map.perm = perm.id
6112 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6114 pgt.id, perm.id, aout.depth, FALSE
6116 permission.grp_tree pgt,
6117 permission.perm_list perm,
6118 actor.org_unit_type aout
6120 pgt.name = 'Data Review' AND
6121 aout.name = 'Consortium' AND
6123 'CREATE_COPY_TRANSIT',
6124 'VIEW_BILLING_TYPE',
6125 'VIEW_CIRCULATIONS',
6128 'VIEW_ORG_SETTINGS',
6132 'VIEW_USER_FINES_SUMMARY',
6133 'VIEW_USER_TRANSACTIONS',
6134 'VIEW_VOLUME_NOTES',
6138 FROM permission.grp_perm_map AS map
6141 AND map.perm = perm.id
6144 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6146 pgt.id, perm.id, aout.depth, FALSE
6148 permission.grp_tree pgt,
6149 permission.perm_list perm,
6150 actor.org_unit_type aout
6152 pgt.name = 'Data Review' AND
6153 aout.name = 'System' AND
6157 'CREATE_IN_HOUSE_USE',
6158 'CREATE_TRANSACTION',
6165 FROM permission.grp_perm_map AS map
6168 AND map.perm = perm.id
6171 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6173 pgt.id, perm.id, aout.depth, FALSE
6175 permission.grp_tree pgt,
6176 permission.perm_list perm,
6177 actor.org_unit_type aout
6179 pgt.name = 'Volunteers' AND
6180 aout.name = 'Branch' AND
6184 'CREATE_IN_HOUSE_USE',
6186 'VIEW_BILLING_TYPE',
6188 'VIEW_COPY_CHECKOUT',
6193 'VIEW_USER_FINES_SUMMARY',
6194 'VIEW_USER_TRANSACTIONS'
6197 FROM permission.grp_perm_map AS map
6200 AND map.perm = perm.id
6203 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6205 pgt.id, perm.id, aout.depth, FALSE
6207 permission.grp_tree pgt,
6208 permission.perm_list perm,
6209 actor.org_unit_type aout
6211 pgt.name = 'Volunteers' AND
6212 aout.name = 'Consortium' AND
6214 'CREATE_COPY_TRANSIT',
6215 'CREATE_TRANSACTION',
6222 FROM permission.grp_perm_map AS map
6225 AND map.perm = perm.id
6229 -- stock Users group
6230 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6232 pgt.id, perm.id, aout.depth, FALSE
6234 permission.grp_tree pgt,
6235 permission.perm_list perm,
6236 actor.org_unit_type aout
6238 pgt.name = 'Users' AND
6239 aout.name = 'Consortium' AND
6241 'CREATE_PURCHASE_REQUEST'
6244 FROM permission.grp_perm_map AS map
6247 AND map.perm = perm.id
6250 -- stock Staff group
6251 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6253 pgt.id, perm.id, aout.depth, FALSE
6255 permission.grp_tree pgt,
6256 permission.perm_list perm,
6257 actor.org_unit_type aout
6259 pgt.name = 'Staff' AND
6260 aout.name = 'Consortium' AND
6262 'VIEW_USER_SETTING_TYPE'
6265 FROM permission.grp_perm_map AS map
6268 AND map.perm = perm.id
6271 -- stock Circulators group
6272 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6274 pgt.id, perm.id, aout.depth, FALSE
6276 permission.grp_tree pgt,
6277 permission.perm_list perm,
6278 actor.org_unit_type aout
6280 pgt.name = 'Circulators' AND
6281 aout.name = 'Branch' AND
6283 'MARK_ITEM_MISSING_PIECES'
6286 FROM permission.grp_perm_map AS map
6289 AND map.perm = perm.id
6292 -- stock Catalogers group
6293 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6295 pgt.id, perm.id, aout.depth, FALSE
6297 permission.grp_tree pgt,
6298 permission.perm_list perm,
6299 actor.org_unit_type aout
6301 pgt.name = 'Catalogers' AND
6302 aout.name = 'System' AND
6304 'MAP_MONOGRAPH_PART'
6307 FROM permission.grp_perm_map AS map
6310 AND map.perm = perm.id
6313 -- stock Acquisitions group
6314 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6316 pgt.id, perm.id, aout.depth, FALSE
6318 permission.grp_tree pgt,
6319 permission.perm_list perm,
6320 actor.org_unit_type aout
6322 pgt.name = 'Acquisitions' AND
6323 aout.name = 'Consortium' AND
6328 FROM permission.grp_perm_map AS map
6331 AND map.perm = perm.id
6334 -- stock Acq Admin group
6335 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6337 pgt.id, perm.id, aout.depth, TRUE
6339 permission.grp_tree pgt,
6340 permission.perm_list perm,
6341 actor.org_unit_type aout
6343 pgt.name = 'Acquisitions Administrator' AND
6344 aout.name = 'Consortium' AND
6349 FROM permission.grp_perm_map AS map
6352 AND map.perm = perm.id
6355 INSERT INTO config.upgrade_log (version) VALUES ('0547'); -- dbwells
6357 -- account for spelling errors (Admin != Administrator)
6358 \qecho This might not insert much if you passed through 0542 on your way here,
6359 \qecho but one group was missed there as well
6361 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6363 pgt.id, perm.id, aout.depth, TRUE
6365 permission.grp_tree pgt,
6366 permission.perm_list perm,
6367 actor.org_unit_type aout
6369 pgt.name = 'Cataloging Administrator' AND
6370 aout.name = 'Consortium' AND
6372 'ADMIN_IMPORT_ITEM_ATTR_DEF',
6373 'ADMIN_MERGE_PROFILE',
6374 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
6375 'CREATE_BIB_IMPORT_FIELD_DEF',
6377 'CREATE_BIB_SOURCE',
6378 'CREATE_IMPORT_ITEM_ATTR_DEF',
6379 'CREATE_IMPORT_TRASH_FIELD',
6380 'CREATE_MERGE_PROFILE',
6381 'CREATE_MONOGRAPH_PART',
6382 'CREATE_VOLUME_PREFIX',
6383 'CREATE_VOLUME_SUFFIX',
6384 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6386 'DELETE_BIB_SOURCE',
6387 'DELETE_IMPORT_ITEM_ATTR_DEF',
6388 'DELETE_IMPORT_TRASH_FIELD',
6389 'DELETE_MERGE_PROFILE',
6390 'DELETE_MONOGRAPH_PART',
6391 'DELETE_VOLUME_PREFIX',
6392 'DELETE_VOLUME_SUFFIX',
6393 'MAP_MONOGRAPH_PART',
6394 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6395 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
6397 'UPDATE_IMPORT_ITEM_ATTR_DEF',
6398 'UPDATE_IMPORT_TRASH_FIELD',
6399 'UPDATE_MERGE_PROFILE',
6400 'UPDATE_MONOGRAPH_PART',
6401 'UPDATE_VOLUME_PREFIX',
6402 'UPDATE_VOLUME_SUFFIX'
6405 FROM permission.grp_perm_map AS map
6408 AND map.perm = perm.id
6411 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6413 pgt.id, perm.id, aout.depth, TRUE
6415 permission.grp_tree pgt,
6416 permission.perm_list perm,
6417 actor.org_unit_type aout
6419 pgt.name = 'Cataloging Administrator' AND
6420 aout.name = 'System' AND
6422 'CREATE_COPY_STAT_CAT',
6423 'CREATE_COPY_STAT_CAT_ENTRY',
6424 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
6426 'SHARE_REPORT_FOLDER',
6427 'UPDATE_COPY_LOCATION',
6428 'UPDATE_COPY_STAT_CAT',
6429 'UPDATE_COPY_STAT_CAT_ENTRY',
6430 'VIEW_REPORT_OUTPUT'
6433 FROM permission.grp_perm_map AS map
6436 AND map.perm = perm.id
6439 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6441 pgt.id, perm.id, aout.depth, TRUE
6443 permission.grp_tree pgt,
6444 permission.perm_list perm,
6445 actor.org_unit_type aout
6447 pgt.name = 'Circulation Administrator' AND
6448 aout.name = 'Branch' AND
6453 FROM permission.grp_perm_map AS map
6456 AND map.perm = perm.id
6459 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6461 pgt.id, perm.id, aout.depth, TRUE
6463 permission.grp_tree pgt,
6464 permission.perm_list perm,
6465 actor.org_unit_type aout
6467 pgt.name = 'Circulation Administrator' AND
6468 aout.name = 'Consortium' AND
6470 'ADMIN_MAX_FINE_RULE',
6471 'CREATE_CIRC_DURATION',
6472 'DELETE_CIRC_DURATION',
6473 'MARK_ITEM_MISSING_PIECES',
6474 'UPDATE_CIRC_DURATION',
6475 'UPDATE_HOLD_REQUEST_TIME',
6476 'UPDATE_NET_ACCESS_LEVEL',
6477 'VIEW_CIRC_MATRIX_MATCHPOINT',
6478 'VIEW_HOLD_MATRIX_MATCHPOINT'
6481 FROM permission.grp_perm_map AS map
6484 AND map.perm = perm.id
6487 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6489 pgt.id, perm.id, aout.depth, TRUE
6491 permission.grp_tree pgt,
6492 permission.perm_list perm,
6493 actor.org_unit_type aout
6495 pgt.name = 'Circulation Administrator' AND
6496 aout.name = 'System' AND
6498 'ADMIN_BOOKING_RESERVATION',
6499 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
6500 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
6501 'ADMIN_BOOKING_RESOURCE',
6502 'ADMIN_BOOKING_RESOURCE_ATTR',
6503 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
6504 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
6505 'ADMIN_BOOKING_RESOURCE_TYPE',
6506 'ADMIN_COPY_LOCATION_ORDER',
6507 'ADMIN_HOLD_CANCEL_CAUSE',
6508 'ASSIGN_GROUP_PERM',
6511 'COPY_TRANSIT_RECEIVE',
6513 'CREATE_BILLING_TYPE',
6514 'CREATE_NON_CAT_TYPE',
6515 'CREATE_PATRON_STAT_CAT',
6516 'CREATE_PATRON_STAT_CAT_ENTRY',
6517 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
6518 'CREATE_USER_GROUP_LINK',
6519 'DELETE_BILLING_TYPE',
6520 'DELETE_NON_CAT_TYPE',
6521 'DELETE_PATRON_STAT_CAT',
6522 'DELETE_PATRON_STAT_CAT_ENTRY',
6523 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
6525 'group_application.user.staff',
6527 'MARK_ITEM_AVAILABLE',
6528 'MARK_ITEM_BINDERY',
6529 'MARK_ITEM_CHECKED_OUT',
6531 'MARK_ITEM_IN_PROCESS',
6532 'MARK_ITEM_IN_TRANSIT',
6534 'MARK_ITEM_MISSING',
6535 'MARK_ITEM_ON_HOLDS_SHELF',
6536 'MARK_ITEM_ON_ORDER',
6537 'MARK_ITEM_RESHELVING',
6539 'money.collections_tracker.create',
6540 'money.collections_tracker.delete',
6544 'REMOVE_USER_GROUP_LINK',
6545 'SET_CIRC_CLAIMS_RETURNED',
6546 'SET_CIRC_CLAIMS_RETURNED.override',
6551 'UPDATE_NON_CAT_TYPE',
6552 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
6553 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
6554 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
6555 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
6557 'VIEW_REPORT_OUTPUT',
6558 'VIEW_STANDING_PENALTY',
6563 FROM permission.grp_perm_map AS map
6566 AND map.perm = perm.id
6569 INSERT INTO config.upgrade_log (version) VALUES ('0557'); -- miker
6571 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
6575 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6579 label_prefix AS prefix,
6580 label_suffix AS suffix
6584 FROM asset.copy_location
6588 INSERT INTO config.upgrade_log (version) VALUES ('0558'); -- miker
6590 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
6594 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6601 FROM config.copy_status
6605 INSERT INTO config.upgrade_log (version) VALUES ('0560'); -- miker
6607 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
6611 do_add BOOLEAN := false;
6612 do_remove BOOLEAN := false;
6615 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
6616 SELECT id, circ_lib, record FROM (
6617 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
6619 JOIN asset.call_number cn ON (cn.id = cp.call_number)
6620 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6621 JOIN asset.copy_location cl ON (cp.location = cl.id)
6622 JOIN config.copy_status cs ON (cp.status = cs.id)
6623 JOIN biblio.record_entry b ON (cn.record = b.id)
6624 WHERE NOT cp.deleted
6632 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
6634 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
6635 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6636 JOIN asset.copy_location cl ON (cp.location = cl.id)
6637 JOIN config.copy_status cs ON (cp.status = cs.id)
6638 WHERE NOT cp.deleted
6647 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
6649 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
6650 IF TG_OP = 'INSERT' THEN
6651 add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';';
6655 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
6656 EXECUTE remove_query;
6661 IF TG_OP = 'INSERT' THEN
6663 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6664 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6672 -- handle items first, since with circulation activity
6673 -- their statuses change frequently
6674 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6676 IF OLD.location <> NEW.location OR
6677 OLD.call_number <> NEW.call_number OR
6678 OLD.status <> NEW.status OR
6679 OLD.circ_lib <> NEW.circ_lib THEN
6680 -- any of these could change visibility, but
6681 -- we'll save some queries and not try to calculate
6682 -- the change directly
6687 IF OLD.deleted <> NEW.deleted THEN
6695 IF OLD.opac_visible <> NEW.opac_visible THEN
6696 IF OLD.opac_visible THEN
6698 ELSIF NOT do_remove THEN -- handle edge case where deleted item
6699 -- is also marked opac_visible
6707 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
6710 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6718 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
6720 IF OLD.deleted AND NEW.deleted THEN -- do nothing
6724 ELSIF NEW.deleted THEN -- remove rows
6726 IF TG_TABLE_NAME = 'call_number' THEN
6727 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
6728 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6729 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
6734 ELSIF OLD.deleted THEN -- add rows
6736 IF TG_TABLE_NAME IN ('copy','unit') THEN
6737 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6738 ELSIF TG_TABLE_NAME = 'call_number' THEN
6739 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6740 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6741 add_query := add_query || 'WHERE x.record = ' || NEW.id || ';';
6751 IF TG_TABLE_NAME = 'call_number' THEN
6753 IF OLD.record <> NEW.record THEN
6754 -- call number is linked to different bib
6755 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
6756 EXECUTE remove_query;
6757 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6765 IF TG_TABLE_NAME IN ('record_entry') THEN
6766 RETURN NEW; -- don't have 'opac_visible'
6769 -- actor.org_unit, asset.copy_location, asset.copy_status
6770 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
6774 ELSIF NEW.opac_visible THEN -- add rows
6776 IF TG_TABLE_NAME = 'org_unit' THEN
6777 add_query := add_query || 'WHERE x.circ_lib = ' || NEW.id || ';';
6778 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6779 add_query := add_query || 'WHERE x.location = ' || NEW.id || ';';
6780 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6781 add_query := add_query || 'WHERE x.status = ' || NEW.id || ';';
6788 IF TG_TABLE_NAME = 'org_unit' THEN
6789 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
6790 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6791 remove_query := remove_query || 'location = ' || NEW.id || ');';
6792 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6793 remove_query := remove_query || 'status = ' || NEW.id || ');';
6796 EXECUTE remove_query;
6802 $func$ LANGUAGE PLPGSQL;
6804 INSERT INTO config.upgrade_log (version) VALUES ('0563');
6806 INSERT INTO permission.perm_list ( id, code, description )
6807 VALUES ( 510, 'UPDATE_PATRON_COLLECTIONS_EXEMPT', oils_i18n_gettext(510,
6808 'Allows a user to indicate that a patron is exempt from collections processing', 'ppl', 'description'));
6810 --- stock Circulation Administrator group
6812 INSERT INTO permission.grp_perm_map ( grp, perm, depth, grantable )
6818 FROM permission.perm_list
6819 WHERE code in ('UPDATE_PATRON_COLLECTIONS_EXEMPT');
6821 INSERT INTO config.upgrade_log (version) VALUES ('0566');
6823 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
6825 me biblio.record_entry%ROWTYPE;
6826 layout unapi.bre_output_layout%ROWTYPE;
6827 xfrm config.xml_transform%ROWTYPE;
6836 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6838 IF ouid IS NULL THEN
6842 IF format = 'holdings_xml' THEN -- the special case
6843 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6847 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6849 IF layout.name IS NULL THEN
6853 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6855 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6857 -- grab SVF if we need them
6858 IF ('mra' = ANY (includes)) THEN
6859 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6864 -- grab hodlings if we need them
6865 IF ('holdings_xml' = ANY (includes)) THEN
6866 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6872 -- generate our item node
6875 IF format = 'marcxml' THEN
6877 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6878 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6881 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6884 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6886 IF axml IS NOT NULL THEN
6887 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6890 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6891 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6894 IF ('bre.unapi' = ANY (includes)) THEN
6895 output := REGEXP_REPLACE(
6897 '</' || top_el || '>(.*?)',
6901 'http://www.w3.org/1999/xhtml' AS xmlns,
6902 'unapi-id' AS class,
6903 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6905 )::TEXT || '</' || top_el || E'>\\1'
6911 output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6914 $F$ LANGUAGE PLPGSQL;
6916 CREATE OR REPLACE FUNCTION unapi.holdings_xml (bid BIGINT, ouid INT, org TEXT, depth INT DEFAULT NULL, includes TEXT[] DEFAULT NULL::TEXT[], slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE) RETURNS XML AS $F$
6920 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6921 CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6925 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
6928 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6930 FROM asset.opac_ou_record_copy_count($2, $1)
6934 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6936 FROM asset.staff_ou_record_copy_count($2, $1)
6941 WHEN ('bmp' = ANY ($5)) THEN
6943 name monograph_parts,
6944 (SELECT XMLAGG(bmp) FROM (
6945 SELECT unapi.bmp( id, 'xml', 'monograph_part', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE)
6946 FROM biblio.monograph_part
6954 (SELECT XMLAGG(acn) FROM (
6955 SELECT unapi.acn(acn.id,'xml','volume', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
6956 FROM asset.call_number acn
6957 WHERE acn.record = $1
6961 JOIN actor.org_unit_descendants(
6966 FROM actor.org_unit_type aout
6967 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6970 ) aoud ON (acp.circ_lib = aoud.id)
6973 ORDER BY label_sortkey
6978 CASE WHEN ('ssub' = ANY ($5)) THEN
6981 (SELECT XMLAGG(ssub) FROM (
6982 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6983 FROM serial.subscription
6984 WHERE record_entry = $1
6988 CASE WHEN ('acp' = ANY ($5)) THEN
6990 name foreign_copies,
6991 (SELECT XMLAGG(acp) FROM (
6992 SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6993 FROM biblio.peer_bib_copy_map p
6994 JOIN asset.copy c ON (p.target_copy = c.id)
6995 WHERE NOT c.deleted AND peer_record = $1
7002 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7006 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7007 'tag:open-ils.org:U2@ssub/' || id AS id,
7008 start_date AS start, end_date AS end, expected_date_offset
7010 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
7011 XMLELEMENT( name distributions,
7013 WHEN ('sdist' = ANY ($4)) THEN
7014 (SELECT XMLAGG(sdist) FROM (
7015 SELECT unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
7016 FROM serial.distribution
7017 WHERE subscription = ssub.id
7023 FROM serial.subscription ssub
7025 GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
7028 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7032 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7033 'tag:open-ils.org:U2@sdist/' || id AS id,
7034 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
7035 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
7036 unit_label_prefix, label, unit_label_suffix, summary_method
7038 unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
7039 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7040 XMLELEMENT( name streams,
7042 WHEN ('sstr' = ANY ($4)) THEN
7043 (SELECT XMLAGG(sstr) FROM (
7044 SELECT unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7046 WHERE distribution = sdist.id
7051 XMLELEMENT( name summaries,
7053 WHEN ('ssum' = ANY ($4)) THEN
7054 (SELECT XMLAGG(sbsum) FROM (
7055 SELECT unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7056 FROM serial.basic_summary
7057 WHERE distribution = sdist.id
7062 WHEN ('ssum' = ANY ($4)) THEN
7063 (SELECT XMLAGG(sisum) FROM (
7064 SELECT unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7065 FROM serial.index_summary
7066 WHERE distribution = sdist.id
7071 WHEN ('ssum' = ANY ($4)) THEN
7072 (SELECT XMLAGG(sssum) FROM (
7073 SELECT unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7074 FROM serial.supplement_summary
7075 WHERE distribution = sdist.id
7081 FROM serial.distribution sdist
7083 GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
7086 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7090 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7091 'tag:open-ils.org:U2@sstr/' || id AS id,
7094 CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7095 XMLELEMENT( name items,
7097 WHEN ('sitem' = ANY ($4)) THEN
7098 (SELECT XMLAGG(sitem) FROM (
7099 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
7101 WHERE stream = sstr.id
7107 FROM serial.stream sstr
7109 GROUP BY id, routing_label, distribution;
7112 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7116 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7117 'tag:open-ils.org:U2@siss/' || id AS id,
7118 create_date, edit_date, label, date_published,
7119 holding_code, holding_type, holding_link_id
7121 CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7122 XMLELEMENT( name items,
7124 WHEN ('sitem' = ANY ($4)) THEN
7125 (SELECT XMLAGG(sitem) FROM (
7126 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
7128 WHERE issuance = sstr.id
7134 FROM serial.issuance sstr
7136 GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
7139 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7143 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7144 'tag:open-ils.org:U2@sitem/' || id AS id,
7145 'tag:open-ils.org:U2@siss/' || issuance AS issuance,
7146 date_expected, date_received
7148 CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7149 CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7150 CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( stream, $2, 'serial_unit', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7151 CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
7152 -- XMLELEMENT( name notes,
7154 -- WHEN ('acpn' = ANY ($4)) THEN
7155 -- (SELECT XMLAGG(acpn) FROM (
7156 -- SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
7157 -- FROM asset.copy_note
7158 -- WHERE owning_copy = cp.id AND pub
7164 FROM serial.item sitem
7169 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7171 name monograph_part,
7173 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7174 'tag:open-ils.org:U2@bmp/' || id AS id,
7178 'tag:open-ils.org:U2@bre/' || record AS record
7181 WHEN ('acp' = ANY ($4)) THEN
7182 XMLELEMENT( name copies,
7183 (SELECT XMLAGG(acp) FROM (
7184 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
7186 JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
7188 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
7195 CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
7197 FROM biblio.monograph_part
7199 GROUP BY id, label, label_sortkey, record;
7202 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7206 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7207 'tag:open-ils.org:U2@acp/' || id AS id,
7208 create_date, edit_date, copy_number, circulate, deposit,
7209 ref, holdable, deleted, deposit_amount, price, barcode,
7210 circ_modifier, circ_as_type, opac_visible
7212 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7213 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7214 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7215 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7216 CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7217 XMLELEMENT( name copy_notes,
7219 WHEN ('acpn' = ANY ($4)) THEN
7220 (SELECT XMLAGG(acpn) FROM (
7221 SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7222 FROM asset.copy_note
7223 WHERE owning_copy = cp.id AND pub
7228 XMLELEMENT( name statcats,
7230 WHEN ('ascecm' = ANY ($4)) THEN
7231 (SELECT XMLAGG(ascecm) FROM (
7232 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7233 FROM asset.stat_cat_entry_copy_map
7234 WHERE owning_copy = cp.id
7239 XMLELEMENT( name foreign_records,
7241 WHEN ('bre' = ANY ($4)) THEN
7242 (SELECT XMLAGG(bre) FROM (
7243 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7244 FROM biblio.peer_bib_copy_map
7245 WHERE target_copy = cp.id
7252 WHEN ('bmp' = ANY ($4)) THEN
7253 XMLELEMENT( name monograph_parts,
7254 (SELECT XMLAGG(bmp) FROM (
7255 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7256 FROM asset.copy_part_map
7257 WHERE target_copy = cp.id
7265 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible;
7268 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7272 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7273 'tag:open-ils.org:U2@acp/' || id AS id,
7274 create_date, edit_date, copy_number, circulate, deposit,
7275 ref, holdable, deleted, deposit_amount, price, barcode,
7276 circ_modifier, circ_as_type, opac_visible, status_changed_time,
7277 floating, mint_condition, detailed_contents, sort_key, summary_contents, cost
7279 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
7280 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
7281 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
7282 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8),
7283 CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
7284 XMLELEMENT( name copy_notes,
7286 WHEN ('acpn' = ANY ($4)) THEN
7287 (SELECT XMLAGG(acpn) FROM (
7288 SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE)
7289 FROM asset.copy_note
7290 WHERE owning_copy = cp.id AND pub
7295 XMLELEMENT( name statcats,
7297 WHEN ('ascecm' = ANY ($4)) THEN
7298 (SELECT XMLAGG(ascecm) FROM (
7299 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7300 FROM asset.stat_cat_entry_copy_map
7301 WHERE owning_copy = cp.id
7306 XMLELEMENT( name foreign_records,
7308 WHEN ('bre' = ANY ($4)) THEN
7309 (SELECT XMLAGG(bre) FROM (
7310 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7311 FROM biblio.peer_bib_copy_map
7312 WHERE target_copy = cp.id
7319 WHEN ('bmp' = ANY ($4)) THEN
7320 XMLELEMENT( name monograph_parts,
7321 (SELECT XMLAGG(bmp) FROM (
7322 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7323 FROM asset.copy_part_map
7324 WHERE target_copy = cp.id
7332 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
7333 deposit, ref, holdable, deleted, deposit_amount, price, barcode, circ_modifier, circ_as_type, opac_visible, status_changed_time, detailed_contents, sort_key, summary_contents, cost;
7336 INSERT INTO config.upgrade_log (version) VALUES ('0568'); -- miker for tsbere
7338 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7342 add_base_query TEXT;
7343 add_peer_query TEXT;
7345 do_add BOOLEAN := false;
7346 do_remove BOOLEAN := false;
7348 add_base_query := $$
7349 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7351 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7352 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7353 JOIN asset.copy_location cl ON (cp.location = cl.id)
7354 JOIN config.copy_status cs ON (cp.status = cs.id)
7355 JOIN biblio.record_entry b ON (cn.record = b.id)
7356 WHERE NOT cp.deleted
7364 add_peer_query := $$
7365 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7367 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7368 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7369 JOIN asset.copy_location cl ON (cp.location = cl.id)
7370 JOIN config.copy_status cs ON (cp.status = cs.id)
7371 WHERE NOT cp.deleted
7378 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7379 SELECT id, circ_lib, record FROM (
7385 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7387 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7388 IF TG_OP = 'INSERT' THEN
7389 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7390 EXECUTE add_front || add_peer_query || add_back;
7393 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7394 EXECUTE remove_query;
7399 IF TG_OP = 'INSERT' THEN
7401 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7402 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7403 EXECUTE add_front || add_base_query || add_back;
7410 -- handle items first, since with circulation activity
7411 -- their statuses change frequently
7412 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7414 IF OLD.location <> NEW.location OR
7415 OLD.call_number <> NEW.call_number OR
7416 OLD.status <> NEW.status OR
7417 OLD.circ_lib <> NEW.circ_lib THEN
7418 -- any of these could change visibility, but
7419 -- we'll save some queries and not try to calculate
7420 -- the change directly
7425 IF OLD.deleted <> NEW.deleted THEN
7433 IF OLD.opac_visible <> NEW.opac_visible THEN
7434 IF OLD.opac_visible THEN
7436 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7437 -- is also marked opac_visible
7445 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7448 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7449 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7450 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7457 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7459 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7463 ELSIF NEW.deleted THEN -- remove rows
7465 IF TG_TABLE_NAME = 'call_number' THEN
7466 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7467 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7468 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7473 ELSIF OLD.deleted THEN -- add rows
7475 IF TG_TABLE_NAME = 'call_number' THEN
7476 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7477 EXECUTE add_front || add_base_query || add_back;
7478 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7479 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7480 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7481 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7490 IF TG_TABLE_NAME = 'call_number' THEN
7492 IF OLD.record <> NEW.record THEN
7493 -- call number is linked to different bib
7494 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7495 EXECUTE remove_query;
7496 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7497 EXECUTE add_front || add_base_query || add_back;
7504 IF TG_TABLE_NAME IN ('record_entry') THEN
7505 RETURN NEW; -- don't have 'opac_visible'
7508 -- actor.org_unit, asset.copy_location, asset.copy_status
7509 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7513 ELSIF NEW.opac_visible THEN -- add rows
7515 IF TG_TABLE_NAME = 'org_unit' THEN
7516 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7517 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7518 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7519 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id || ';';
7520 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id || ';';
7521 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7522 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id || ';';
7523 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id || ';';
7526 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7530 IF TG_TABLE_NAME = 'org_unit' THEN
7531 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7532 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7533 remove_query := remove_query || 'location = ' || NEW.id || ');';
7534 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7535 remove_query := remove_query || 'status = ' || NEW.id || ');';
7538 EXECUTE remove_query;
7544 $func$ LANGUAGE PLPGSQL;
7546 INSERT INTO config.upgrade_log (version) VALUES ('0569'); --miker
7548 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
7552 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7553 'tag:open-ils.org:U2@auri/' || uri.id AS id,
7558 XMLELEMENT( name copies,
7560 WHEN ('acn' = ANY ($4)) THEN
7561 (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
7568 GROUP BY uri.id, use_restriction, href, label;
7571 INSERT INTO config.upgrade_log (version) VALUES ('0570');
7573 -- Not everything in 1XX tags should become part of the authorsort field
7574 -- ($0 for example). The list of subfields chosen here is a superset of all
7575 -- the fields found in the LoC authority mappin definitions for 1XX fields.
7576 -- Anyway, if more fields should be here, add them.
7578 UPDATE config.record_attr_definition
7579 SET sf_list = 'abcdefgklmnopqrstvxyz'
7580 WHERE name='authorsort' AND sf_list IS NULL;
7582 INSERT INTO config.upgrade_log (version) VALUES ('0571');
7584 -- FIXME: add/check SQL statements to perform the upgrade
7585 CREATE OR REPLACE FUNCTION metabib.facet_normalize_trigger () RETURNS TRIGGER AS $$
7590 facet_text := NEW.value;
7593 SELECT n.func AS func,
7594 n.param_count AS param_count,
7596 FROM config.index_normalizer n
7597 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7598 WHERE m.field = NEW.field AND m.pos < 0
7601 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7602 quote_literal( facet_text ) ||
7604 WHEN normalizer.param_count > 0
7605 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7608 ')' INTO facet_text;
7612 NEW.value = facet_text;
7616 $$ LANGUAGE PLPGSQL;
7618 CREATE TRIGGER facet_normalize_tgr
7619 BEFORE UPDATE OR INSERT ON metabib.facet_entry
7620 FOR EACH ROW EXECUTE PROCEDURE metabib.facet_normalize_trigger();
7624 INSERT INTO config.upgrade_log (version) VALUES ('0578'); -- tsbere via miker
7626 CREATE OR REPLACE VIEW reporter.hold_request_record AS
7631 WHEN hold_type = 'T'
7633 WHEN hold_type = 'I'
7634 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
7635 WHEN hold_type = 'V'
7636 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
7637 WHEN hold_type IN ('C','R','F')
7638 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
7639 WHEN hold_type = 'M'
7640 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
7641 WHEN hold_type = 'P'
7642 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
7644 FROM action.hold_request ahr;
7646 INSERT INTO config.upgrade_log (version) VALUES ('0583');
7648 CREATE OR REPLACE VIEW action.all_circulation AS
7649 SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
7650 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
7651 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
7652 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
7653 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
7654 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
7655 FROM action.aged_circulation
7657 SELECT DISTINCT circ.id,COALESCE(a.post_code,b.post_code) AS usr_post_code, p.home_ou AS usr_home_ou, p.profile AS usr_profile, EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year,
7658 cp.call_number AS copy_call_number, cp.location AS copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
7659 cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
7660 circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
7661 circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
7662 circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
7664 FROM action.circulation circ
7665 JOIN asset.copy cp ON (circ.target_copy = cp.id)
7666 JOIN asset.call_number cn ON (cp.call_number = cn.id)
7667 JOIN actor.usr p ON (circ.usr = p.id)
7668 LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
7669 LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
7673 INSERT INTO config.upgrade_log (version) VALUES ('0590'); -- miker/tsbere
7675 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7679 add_base_query TEXT;
7680 add_peer_query TEXT;
7682 do_add BOOLEAN := false;
7683 do_remove BOOLEAN := false;
7685 add_base_query := $$
7686 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7688 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7689 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7690 JOIN asset.copy_location cl ON (cp.location = cl.id)
7691 JOIN config.copy_status cs ON (cp.status = cs.id)
7692 JOIN biblio.record_entry b ON (cn.record = b.id)
7693 WHERE NOT cp.deleted
7701 add_peer_query := $$
7702 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7704 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7705 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7706 JOIN asset.copy_location cl ON (cp.location = cl.id)
7707 JOIN config.copy_status cs ON (cp.status = cs.id)
7708 WHERE NOT cp.deleted
7715 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7716 SELECT id, circ_lib, record FROM (
7722 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7724 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7725 IF TG_OP = 'INSERT' THEN
7726 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7727 EXECUTE add_front || add_peer_query || add_back;
7730 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7731 EXECUTE remove_query;
7736 IF TG_OP = 'INSERT' THEN
7738 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7739 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7740 EXECUTE add_front || add_base_query || add_back;
7747 -- handle items first, since with circulation activity
7748 -- their statuses change frequently
7749 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7751 IF OLD.location <> NEW.location OR
7752 OLD.call_number <> NEW.call_number OR
7753 OLD.status <> NEW.status OR
7754 OLD.circ_lib <> NEW.circ_lib THEN
7755 -- any of these could change visibility, but
7756 -- we'll save some queries and not try to calculate
7757 -- the change directly
7762 IF OLD.deleted <> NEW.deleted THEN
7770 IF OLD.opac_visible <> NEW.opac_visible THEN
7771 IF OLD.opac_visible THEN
7773 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7774 -- is also marked opac_visible
7782 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7785 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7786 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7787 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7794 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7796 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7800 ELSIF NEW.deleted THEN -- remove rows
7802 IF TG_TABLE_NAME = 'call_number' THEN
7803 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7804 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7805 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7810 ELSIF OLD.deleted THEN -- add rows
7812 IF TG_TABLE_NAME = 'call_number' THEN
7813 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7814 EXECUTE add_front || add_base_query || add_back;
7815 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7816 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7817 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7818 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7827 IF TG_TABLE_NAME = 'call_number' THEN
7829 IF OLD.record <> NEW.record THEN
7830 -- call number is linked to different bib
7831 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7832 EXECUTE remove_query;
7833 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7834 EXECUTE add_front || add_base_query || add_back;
7841 IF TG_TABLE_NAME IN ('record_entry') THEN
7842 RETURN NEW; -- don't have 'opac_visible'
7845 -- actor.org_unit, asset.copy_location, asset.copy_status
7846 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7850 ELSIF NEW.opac_visible THEN -- add rows
7852 IF TG_TABLE_NAME = 'org_unit' THEN
7853 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
7854 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
7855 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7856 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
7857 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
7858 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7859 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
7860 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
7863 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7867 IF TG_TABLE_NAME = 'org_unit' THEN
7868 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7869 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7870 remove_query := remove_query || 'location = ' || NEW.id || ');';
7871 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7872 remove_query := remove_query || 'status = ' || NEW.id || ');';
7875 EXECUTE remove_query;
7881 $func$ LANGUAGE PLPGSQL;
7883 INSERT INTO config.upgrade_log (version) VALUES ('0591'); -- berick/miker
7885 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
7887 c action.circulation%ROWTYPE;
7889 usr_view_age actor.usr_setting%ROWTYPE;
7890 usr_view_start actor.usr_setting%ROWTYPE;
7892 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
7893 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
7895 IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
7896 -- User opted in and supplied a retention age
7897 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7898 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7900 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7902 ELSIF usr_view_start.value IS NOT NULL THEN
7904 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7906 -- User did not opt in
7912 FROM action.circulation
7914 AND parent_circ IS NULL
7915 AND xact_start > NOW() - view_age
7916 ORDER BY xact_start DESC
7923 $func$ LANGUAGE PLPGSQL;
7925 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
7927 h action.hold_request%ROWTYPE;
7930 usr_view_count actor.usr_setting%ROWTYPE;
7931 usr_view_age actor.usr_setting%ROWTYPE;
7932 usr_view_start actor.usr_setting%ROWTYPE;
7934 SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
7935 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
7936 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
7940 FROM action.hold_request
7942 AND fulfillment_time IS NULL
7943 AND cancel_time IS NULL
7944 ORDER BY request_time DESC
7949 IF usr_view_start.value IS NULL THEN
7953 IF usr_view_age.value IS NOT NULL THEN
7954 -- User opted in and supplied a retention age
7955 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7956 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7958 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7962 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7965 IF usr_view_count.value IS NOT NULL THEN
7966 view_count := oils_json_to_text(usr_view_count.value)::INT;
7971 -- show some fulfilled/canceled holds
7974 FROM action.hold_request
7976 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
7977 AND request_time > NOW() - view_age
7978 ORDER BY request_time DESC
7986 $func$ LANGUAGE PLPGSQL;
7988 INSERT INTO config.upgrade_log (version) VALUES ('0599'); -- miker/gmc
7990 UPDATE config.metabib_field
7991 SET xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role/mods32:roleTerm[text()='creator'])]$$
7992 WHERE field_class = 'author'
7994 AND xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role)]$$
7995 AND format = 'mods32';
7997 \qecho To reindex bibs that use the author|other index definition,
7998 \qecho you can run something like this:
8000 \qecho SELECT metabib.reingest_metabib_field_entries(record)
8002 \qecho SELECT DISTINCT record
8003 \qecho FROM metabib.real_full_rec
8004 \qecho WHERE tag IN ('600', '700', '720', '800')
8005 \qecho AND subfield IN ('4', 'e')
8008 -- Resolves an error in calculating copy counts for org lassos
8010 INSERT INTO config.upgrade_log (version) VALUES ('0603');
8012 -- FIXME: add/check SQL statements to perform the upgrade
8013 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$
8018 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;
8020 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
8025 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
8029 actor.org_unit_descendants(ans.id) d
8030 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
8031 JOIN asset.copy cp ON (cp.id = av.copy_id)
8035 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8042 $f$ LANGUAGE PLPGSQL;
8045 -- Staff record copy counts also triggered an SQL error for org lassos
8048 INSERT INTO config.upgrade_log (version) VALUES ('0604');
8050 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$
8055 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;
8057 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
8062 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
8066 actor.org_unit_descendants(ans.id) d
8067 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
8068 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
8072 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8079 $f$ LANGUAGE PLPGSQL;
8081 INSERT INTO config.upgrade_log (version) VALUES ('0614'); --miker/phasefx
8083 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
8087 add_base_query TEXT;
8088 add_peer_query TEXT;
8090 do_add BOOLEAN := false;
8091 do_remove BOOLEAN := false;
8093 add_base_query := $$
8094 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
8096 JOIN asset.call_number cn ON (cn.id = cp.call_number)
8097 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8098 JOIN asset.copy_location cl ON (cp.location = cl.id)
8099 JOIN config.copy_status cs ON (cp.status = cs.id)
8100 JOIN biblio.record_entry b ON (cn.record = b.id)
8101 WHERE NOT cp.deleted
8109 add_peer_query := $$
8110 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
8112 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
8113 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8114 JOIN asset.copy_location cl ON (cp.location = cl.id)
8115 JOIN config.copy_status cs ON (cp.status = cs.id)
8116 WHERE NOT cp.deleted
8123 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
8124 SELECT id, circ_lib, record FROM (
8130 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
8132 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
8133 IF TG_OP = 'INSERT' THEN
8134 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.peer_record = ' || NEW.peer_record;
8135 EXECUTE add_front || add_peer_query || add_back;
8138 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
8139 EXECUTE remove_query;
8144 IF TG_OP = 'INSERT' THEN
8146 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8147 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8148 EXECUTE add_front || add_base_query || add_back;
8155 -- handle items first, since with circulation activity
8156 -- their statuses change frequently
8157 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8159 IF OLD.location <> NEW.location OR
8160 OLD.call_number <> NEW.call_number OR
8161 OLD.status <> NEW.status OR
8162 OLD.circ_lib <> NEW.circ_lib THEN
8163 -- any of these could change visibility, but
8164 -- we'll save some queries and not try to calculate
8165 -- the change directly
8170 IF OLD.deleted <> NEW.deleted THEN
8178 IF OLD.opac_visible <> NEW.opac_visible THEN
8179 IF OLD.opac_visible THEN
8181 ELSIF NOT do_remove THEN -- handle edge case where deleted item
8182 -- is also marked opac_visible
8190 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
8193 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8194 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
8195 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8202 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
8204 IF OLD.deleted AND NEW.deleted THEN -- do nothing
8208 ELSIF NEW.deleted THEN -- remove rows
8210 IF TG_TABLE_NAME = 'call_number' THEN
8211 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
8212 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8213 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
8218 ELSIF OLD.deleted THEN -- add rows
8220 IF TG_TABLE_NAME = 'call_number' THEN
8221 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8222 EXECUTE add_front || add_base_query || add_back;
8223 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8224 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
8225 add_peer_query := add_peer_query || ' AND pbcm.peer_record = ' || NEW.id;
8226 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8235 IF TG_TABLE_NAME = 'call_number' THEN
8237 IF OLD.record <> NEW.record THEN
8238 -- call number is linked to different bib
8239 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
8240 EXECUTE remove_query;
8241 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8242 EXECUTE add_front || add_base_query || add_back;
8249 IF TG_TABLE_NAME IN ('record_entry') THEN
8250 RETURN NEW; -- don't have 'opac_visible'
8253 -- actor.org_unit, asset.copy_location, asset.copy_status
8254 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
8258 ELSIF NEW.opac_visible THEN -- add rows
8260 IF TG_TABLE_NAME = 'org_unit' THEN
8261 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
8262 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
8263 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8264 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
8265 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
8266 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8267 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
8268 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
8271 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8275 IF TG_TABLE_NAME = 'org_unit' THEN
8276 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
8277 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8278 remove_query := remove_query || 'location = ' || NEW.id || ');';
8279 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8280 remove_query := remove_query || 'status = ' || NEW.id || ');';
8283 EXECUTE remove_query;
8289 $func$ LANGUAGE PLPGSQL;
8291 INSERT INTO config.upgrade_log (version) VALUES ('0579'); -- superceded by 0620
8292 INSERT INTO config.upgrade_log (version) VALUES ('0620'); -- tsbere via miker
8294 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.circ_matrix_test_result AS $func$
8296 user_object actor.usr%ROWTYPE;
8297 standing_penalty config.standing_penalty%ROWTYPE;
8298 item_object asset.copy%ROWTYPE;
8299 item_status_object config.copy_status%ROWTYPE;
8300 item_location_object asset.copy_location%ROWTYPE;
8301 result action.circ_matrix_test_result;
8302 circ_test action.found_circ_matrix_matchpoint;
8303 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
8304 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
8305 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
8306 hold_ratio action.hold_stats%ROWTYPE;
8309 context_org_list INT[];
8312 -- Assume success unless we hit a failure condition
8313 result.success := TRUE;
8315 -- Need user info to look up matchpoints
8316 SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
8318 -- (Insta)Fail if we couldn't find the user
8319 IF user_object.id IS NULL THEN
8320 result.fail_part := 'no_user';
8321 result.success := FALSE;
8327 -- Need item info to look up matchpoints
8328 SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
8330 -- (Insta)Fail if we couldn't find the item
8331 IF item_object.id IS NULL THEN
8332 result.fail_part := 'no_item';
8333 result.success := FALSE;
8339 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
8341 circ_matchpoint := circ_test.matchpoint;
8342 result.matchpoint := circ_matchpoint.id;
8343 result.circulate := circ_matchpoint.circulate;
8344 result.duration_rule := circ_matchpoint.duration_rule;
8345 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
8346 result.max_fine_rule := circ_matchpoint.max_fine_rule;
8347 result.hard_due_date := circ_matchpoint.hard_due_date;
8348 result.renewals := circ_matchpoint.renewals;
8349 result.grace_period := circ_matchpoint.grace_period;
8350 result.buildrows := circ_test.buildrows;
8352 -- (Insta)Fail if we couldn't find a matchpoint
8353 IF circ_test.success = false THEN
8354 result.fail_part := 'no_matchpoint';
8355 result.success := FALSE;
8361 -- All failures before this point are non-recoverable
8362 -- Below this point are possibly overridable failures
8364 -- Fail if the user is barred
8365 IF user_object.barred IS TRUE THEN
8366 result.fail_part := 'actor.usr.barred';
8367 result.success := FALSE;
8372 -- Fail if the item can't circulate
8373 IF item_object.circulate IS FALSE THEN
8374 result.fail_part := 'asset.copy.circulate';
8375 result.success := FALSE;
8380 -- Fail if the item isn't in a circulateable status on a non-renewal
8381 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
8382 result.fail_part := 'asset.copy.status';
8383 result.success := FALSE;
8386 -- Alternately, fail if the item isn't checked out on a renewal
8387 ELSIF renewal AND item_object.status <> 1 THEN
8388 result.fail_part := 'asset.copy.status';
8389 result.success := FALSE;
8394 -- Fail if the item can't circulate because of the shelving location
8395 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8396 IF item_location_object.circulate IS FALSE THEN
8397 result.fail_part := 'asset.copy_location.circulate';
8398 result.success := FALSE;
8403 -- Use Circ OU for penalties and such
8404 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_ou );
8407 penalty_type = '%RENEW%';
8409 penalty_type = '%CIRC%';
8412 FOR standing_penalty IN
8413 SELECT DISTINCT csp.*
8414 FROM actor.usr_standing_penalty usp
8415 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8416 WHERE usr = match_user
8417 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8418 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8419 AND csp.block_list LIKE penalty_type LOOP
8421 result.fail_part := standing_penalty.name;
8422 result.success := FALSE;
8427 -- Fail if the test is set to hard non-circulating
8428 IF circ_matchpoint.circulate IS FALSE THEN
8429 result.fail_part := 'config.circ_matrix_test.circulate';
8430 result.success := FALSE;
8435 -- Fail if the total copy-hold ratio is too low
8436 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
8437 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8438 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
8439 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8440 result.success := FALSE;
8446 -- Fail if the available copy-hold ratio is too low
8447 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
8448 IF hold_ratio.hold_count IS NULL THEN
8449 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8451 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
8452 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8453 result.success := FALSE;
8459 -- Fail if the user has too many items with specific circ_modifiers checked out
8460 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
8461 SELECT INTO items_out COUNT(*)
8462 FROM action.circulation circ
8463 JOIN asset.copy cp ON (cp.id = circ.target_copy)
8464 WHERE circ.usr = match_user
8465 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8466 AND circ.checkin_time IS NULL
8467 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8468 AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
8469 IF items_out >= out_by_circ_mod.items_out THEN
8470 result.fail_part := 'config.circ_matrix_circ_mod_test';
8471 result.success := FALSE;
8477 -- If we passed everything, return the successful matchpoint
8484 $func$ LANGUAGE plpgsql;
8488 INSERT INTO config.upgrade_log (version) VALUES ('0628');
8490 -- acq.fund_combined_balance and acq.fund_spent_balance are unchanged,
8491 -- however we need to drop them to recreate the other views.
8492 -- we need to drop all our views because we change the number of columns
8493 -- for example, debit_total does not need an encumberance column when we
8494 -- have a sepearate total for that.
8496 DROP VIEW acq.fund_spent_balance;
8497 DROP VIEW acq.fund_combined_balance;
8498 DROP VIEW acq.fund_encumbrance_total;
8499 DROP VIEW acq.fund_spent_total;
8500 DROP VIEW acq.fund_debit_total;
8502 CREATE OR REPLACE VIEW acq.fund_debit_total AS
8503 SELECT fund.id AS fund,
8504 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8506 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8509 CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS
8512 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8514 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8515 WHERE fund_debit.encumbrance GROUP BY fund.id;
8517 CREATE OR REPLACE VIEW acq.fund_spent_total AS
8518 SELECT fund.id AS fund,
8519 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8521 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8522 WHERE NOT fund_debit.encumbrance
8525 CREATE OR REPLACE VIEW acq.fund_combined_balance AS
8527 c.amount - COALESCE(d.amount, 0.0) AS amount
8528 FROM acq.fund_allocation_total c
8529 LEFT JOIN acq.fund_debit_total d USING (fund);
8531 CREATE OR REPLACE VIEW acq.fund_spent_balance AS
8533 c.amount - COALESCE(d.amount,0.0) AS amount
8534 FROM acq.fund_allocation_total c
8535 LEFT JOIN acq.fund_spent_total d USING (fund);
8539 INSERT INTO config.upgrade_log (version) VALUES ('0631');
8541 CREATE OR REPLACE FUNCTION search.query_parser_fts (
8543 param_search_ou INT,
8546 param_statuses INT[],
8547 param_locations INT[],
8554 ) RETURNS SETOF search.search_result AS $func$
8557 current_res search.search_result%ROWTYPE;
8558 search_org_list INT[];
8559 luri_org_list INT[];
8568 core_cursor REFCURSOR;
8569 core_rel_query TEXT;
8571 total_count INT := 0;
8572 check_count INT := 0;
8573 deleted_count INT := 0;
8574 visible_count INT := 0;
8575 excluded_count INT := 0;
8579 check_limit := COALESCE( param_check, 1000 );
8580 core_limit := COALESCE( param_limit, 25000 );
8581 core_offset := COALESCE( param_offset, 0 );
8583 -- core_skip_chk := COALESCE( param_skip_chk, 1 );
8585 IF param_search_ou > 0 THEN
8586 IF param_depth IS NOT NULL THEN
8587 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
8589 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
8592 SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
8594 ELSIF param_search_ou < 0 THEN
8595 SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
8597 FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
8598 SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
8599 luri_org_list := luri_org_list || tmp_int_list;
8602 SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
8604 ELSIF param_search_ou = 0 THEN
8605 -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
8608 OPEN core_cursor FOR EXECUTE param_query;
8612 FETCH core_cursor INTO core_result;
8613 EXIT WHEN NOT FOUND;
8614 EXIT WHEN total_count >= core_limit;
8616 total_count := total_count + 1;
8618 CONTINUE WHEN total_count NOT BETWEEN core_offset + 1 AND check_limit + core_offset;
8620 check_count := check_count + 1;
8622 PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8624 -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
8625 deleted_count := deleted_count + 1;
8630 FROM biblio.record_entry b
8631 JOIN config.bib_source s ON (b.source = s.id)
8632 WHERE s.transcendant
8633 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8636 -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
8637 visible_count := visible_count + 1;
8639 current_res.id = core_result.id;
8640 current_res.rel = core_result.rel;
8644 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8648 current_res.record = core_result.records[1];
8650 current_res.record = NULL;
8653 RETURN NEXT current_res;
8659 FROM asset.call_number cn
8660 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
8661 JOIN asset.uri uri ON (map.uri = uri.id)
8662 WHERE NOT cn.deleted
8663 AND cn.label = '##URI##'
8665 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
8666 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8667 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
8671 -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
8672 visible_count := visible_count + 1;
8674 current_res.id = core_result.id;
8675 current_res.rel = core_result.rel;
8679 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8683 current_res.record = core_result.records[1];
8685 current_res.record = NULL;
8688 RETURN NEXT current_res;
8693 IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
8696 FROM asset.call_number cn
8697 JOIN asset.copy cp ON (cp.call_number = cn.id)
8698 WHERE NOT cn.deleted
8700 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8701 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8702 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8707 FROM biblio.peer_bib_copy_map pr
8708 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8709 WHERE NOT cp.deleted
8710 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8711 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8712 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8716 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
8717 excluded_count := excluded_count + 1;
8724 IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
8727 FROM asset.call_number cn
8728 JOIN asset.copy cp ON (cp.call_number = cn.id)
8729 WHERE NOT cn.deleted
8731 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8732 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8733 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8738 FROM biblio.peer_bib_copy_map pr
8739 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8740 WHERE NOT cp.deleted
8741 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8742 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8743 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8747 -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
8748 excluded_count := excluded_count + 1;
8755 IF staff IS NULL OR NOT staff THEN
8758 FROM asset.opac_visible_copies
8759 WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8760 AND record IN ( SELECT * FROM unnest( core_result.records ) )
8765 FROM biblio.peer_bib_copy_map pr
8766 JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
8767 WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8768 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8773 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8774 excluded_count := excluded_count + 1;
8782 FROM asset.call_number cn
8783 JOIN asset.copy cp ON (cp.call_number = cn.id)
8784 WHERE NOT cn.deleted
8786 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8787 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8793 FROM biblio.peer_bib_copy_map pr
8794 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8795 WHERE NOT cp.deleted
8796 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8797 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8803 FROM asset.call_number cn
8804 JOIN asset.copy cp ON (cp.call_number = cn.id)
8805 WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8810 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8811 excluded_count := excluded_count + 1;
8820 visible_count := visible_count + 1;
8822 current_res.id = core_result.id;
8823 current_res.rel = core_result.rel;
8827 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8831 current_res.record = core_result.records[1];
8833 current_res.record = NULL;
8836 RETURN NEXT current_res;
8838 IF visible_count % 1000 = 0 THEN
8839 -- RAISE NOTICE ' % visible so far ... ', visible_count;
8844 current_res.id = NULL;
8845 current_res.rel = NULL;
8846 current_res.record = NULL;
8847 current_res.total = total_count;
8848 current_res.checked = check_count;
8849 current_res.deleted = deleted_count;
8850 current_res.visible = visible_count;
8851 current_res.excluded = excluded_count;
8855 RETURN NEXT current_res;
8858 $func$ LANGUAGE PLPGSQL;
8861 INSERT INTO config.upgrade_log (version) VALUES ('0633');
8862 INSERT INTO config.upgrade_log (version) VALUES ('0634');
8867 INSERT into config.org_unit_setting_type
8868 ( name, grp, label, description, datatype ) VALUES
8870 'print.custom_js_file', 'circ',
8872 'print.custom_js_file',
8873 'Printing: Custom Javascript File',
8878 'print.custom_js_file',
8879 'Full URL path to a Javascript File to be loaded when printing. Should'
8880 || ' implement a print_custom function for DOM manipulation. Can change'
8881 || ' the value of the do_print variable to false to cancel printing.',
8890 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8891 ( 513, 'DEBUG_CLIENT', oils_i18n_gettext( 513,
8892 'Allows a user to use debug functions in the staff client', 'ppl', 'description' ));
8894 UPDATE asset.call_number SET id = id WHERE deleted IS FALSE OR deleted = FALSE;
8897 INSERT INTO config.org_unit_setting_type
8898 ( name, label, description, datatype ) VALUES
8899 ( 'circ.user_merge.delete_addresses',
8900 'Circ: Patron Merge Address Delete',
8901 'Delete address(es) of subordinate user(s) in a patron merge',
8905 INSERT INTO config.org_unit_setting_type
8906 ( name, label, description, datatype ) VALUES
8907 ( 'circ.user_merge.delete_cards',
8908 'Circ: Patron Merge Barcode Delete',
8909 'Delete barcode(s) of subordinate user(s) in a patron merge',
8913 INSERT INTO config.org_unit_setting_type
8914 ( name, label, description, datatype ) VALUES
8915 ( 'circ.user_merge.deactivate_cards',
8916 'Circ: Patron Merge Deactivate Card',
8917 'Mark barcode(s) of subordinate user(s) in a patron merge as inactive',
8921 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.cash_payment;
8922 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.cash_payment;
8923 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.cash_payment;
8925 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('cash_payment');
8926 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('cash_payment');
8927 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('cash_payment');
8929 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.check_payment;
8930 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.check_payment;
8931 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.check_payment;
8933 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('check_payment');
8934 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('check_payment');
8935 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('check_payment');
8938 UPDATE metabib.record_attr
8939 SET attrs = attrs || asort
8940 FROM (SELECT record,
8941 HSTORE('authorsort',FIRST(value)) AS asort
8942 FROM metabib.full_rec
8945 WHERE x.record = metabib.record_attr.id;
8947 UPDATE metabib.record_attr
8948 SET attrs = attrs || tsort
8949 FROM (SELECT record,
8950 HSTORE('titlesort',FIRST(value)) AS tsort
8951 FROM metabib.full_rec
8954 WHERE x.record = metabib.record_attr.id;
8956 UPDATE metabib.record_attr
8957 SET attrs = attrs || ('pubdate' => (attrs->'date1'))
8958 WHERE defined(attrs, 'pubdate') IS FALSE
8959 AND defined(attrs, 'date1') IS TRUE;