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 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
316 specified_dest_usr IN INTEGER
320 renamable_row RECORD;
324 IF specified_dest_usr IS NULL THEN
325 dest_usr := 1; -- Admin user on stock installs
327 dest_usr := specified_dest_usr;
333 mailing_address = NULL,
334 billing_address = NULL
338 UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
339 UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
340 UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
341 UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
342 UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
343 UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
344 DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
346 -- Update with a rename to avoid collisions
350 WHERE owner = src_usr
352 suffix := ' (' || src_usr || ')';
356 SET owner = dest_usr, name = name || suffix
357 WHERE id = renamable_row.id;
358 EXCEPTION WHEN unique_violation THEN
359 suffix := suffix || ' ';
366 UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
367 UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
368 UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
369 UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
370 UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
371 UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
372 UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
373 UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
376 DELETE FROM action.circulation WHERE usr = src_usr;
377 UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
378 UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
379 UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
380 UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
381 UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
382 DELETE FROM action.hold_request WHERE usr = src_usr;
383 UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
384 UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
385 DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
386 UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
387 DELETE FROM action.survey_response WHERE usr = src_usr;
388 UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
391 DELETE FROM actor.card WHERE usr = src_usr;
392 DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
394 -- The following update is intended to avoid transient violations of a foreign
395 -- key constraint, whereby actor.usr_address references itself. It may not be
396 -- necessary, but it does no harm.
397 UPDATE actor.usr_address SET replaces = NULL
398 WHERE usr = src_usr AND replaces IS NOT NULL;
399 DELETE FROM actor.usr_address WHERE usr = src_usr;
400 DELETE FROM actor.usr_note WHERE usr = src_usr;
401 UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
402 DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
403 UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
404 DELETE FROM actor.usr_setting WHERE usr = src_usr;
405 DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
406 UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
409 UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
410 UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
411 UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
412 UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
413 UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
414 UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
417 DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
418 DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
419 UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
420 UPDATE auditor.asset_call_number_history SET editor = dest_usr WHERE editor = src_usr;
421 UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
422 UPDATE auditor.asset_copy_history SET editor = dest_usr WHERE editor = src_usr;
423 UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
424 UPDATE auditor.biblio_record_entry_history SET editor = dest_usr WHERE editor = src_usr;
427 UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
428 UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
429 UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
430 UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
433 -- Update buckets with a rename to avoid collisions
436 FROM container.biblio_record_entry_bucket
437 WHERE owner = src_usr
439 suffix := ' (' || src_usr || ')';
442 UPDATE container.biblio_record_entry_bucket
443 SET owner = dest_usr, name = name || suffix
444 WHERE id = renamable_row.id;
445 EXCEPTION WHEN unique_violation THEN
446 suffix := suffix || ' ';
455 FROM container.call_number_bucket
456 WHERE owner = src_usr
458 suffix := ' (' || src_usr || ')';
461 UPDATE container.call_number_bucket
462 SET owner = dest_usr, name = name || suffix
463 WHERE id = renamable_row.id;
464 EXCEPTION WHEN unique_violation THEN
465 suffix := suffix || ' ';
474 FROM container.copy_bucket
475 WHERE owner = src_usr
477 suffix := ' (' || src_usr || ')';
480 UPDATE container.copy_bucket
481 SET owner = dest_usr, name = name || suffix
482 WHERE id = renamable_row.id;
483 EXCEPTION WHEN unique_violation THEN
484 suffix := suffix || ' ';
493 FROM container.user_bucket
494 WHERE owner = src_usr
496 suffix := ' (' || src_usr || ')';
499 UPDATE container.user_bucket
500 SET owner = dest_usr, name = name || suffix
501 WHERE id = renamable_row.id;
502 EXCEPTION WHEN unique_violation THEN
503 suffix := suffix || ' ';
510 DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
513 DELETE FROM money.billable_xact WHERE usr = src_usr;
514 DELETE FROM money.collections_tracker WHERE usr = src_usr;
515 UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
518 DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
519 DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
520 DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
521 DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
524 -- Update with a rename to avoid collisions
528 FROM reporter.output_folder
529 WHERE owner = src_usr
531 suffix := ' (' || src_usr || ')';
534 UPDATE reporter.output_folder
535 SET owner = dest_usr, name = name || suffix
536 WHERE id = renamable_row.id;
537 EXCEPTION WHEN unique_violation THEN
538 suffix := suffix || ' ';
544 EXCEPTION WHEN undefined_table THEN
549 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
550 EXCEPTION WHEN undefined_table THEN
554 -- Update with a rename to avoid collisions
558 FROM reporter.report_folder
559 WHERE owner = src_usr
561 suffix := ' (' || src_usr || ')';
564 UPDATE reporter.report_folder
565 SET owner = dest_usr, name = name || suffix
566 WHERE id = renamable_row.id;
567 EXCEPTION WHEN unique_violation THEN
568 suffix := suffix || ' ';
574 EXCEPTION WHEN undefined_table THEN
579 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
580 EXCEPTION WHEN undefined_table THEN
585 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
586 EXCEPTION WHEN undefined_table THEN
590 -- Update with a rename to avoid collisions
594 FROM reporter.template_folder
595 WHERE owner = src_usr
597 suffix := ' (' || src_usr || ')';
600 UPDATE reporter.template_folder
601 SET owner = dest_usr, name = name || suffix
602 WHERE id = renamable_row.id;
603 EXCEPTION WHEN unique_violation THEN
604 suffix := suffix || ' ';
610 EXCEPTION WHEN undefined_table THEN
615 -- Update with a rename to avoid collisions
619 WHERE owner = src_usr
621 suffix := ' (' || src_usr || ')';
624 UPDATE vandelay.queue
625 SET owner = dest_usr, name = name || suffix
626 WHERE id = renamable_row.id;
627 EXCEPTION WHEN unique_violation THEN
628 suffix := suffix || ' ';
638 -- 0482, 0487, and parts of others
639 -- Circ matchpoint table changes
640 ALTER TABLE config.circ_matrix_matchpoint
641 ALTER COLUMN circulate DROP NOT NULL, -- Fallthrough enable
642 ALTER COLUMN circulate DROP DEFAULT, -- Stop defaulting to true to enable default to fallthrough
643 ALTER COLUMN duration_rule DROP NOT NULL, -- Fallthrough enable
644 ALTER COLUMN recurring_fine_rule DROP NOT NULL, -- Fallthrough enable
645 ALTER COLUMN max_fine_rule DROP NOT NULL, -- Fallthrough enable
646 ADD COLUMN renewals INT, -- Renewals override
647 ADD COLUMN user_home_ou INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
648 ADD COLUMN grace_period INTERVAL,
649 ADD COLUMN marc_bib_level text,
650 DROP CONSTRAINT ep_once_per_grp_loc_mod_marc,
651 DROP CONSTRAINT circ_matrix_matchpoint_marc_form_fkey,
652 DROP CONSTRAINT circ_matrix_matchpoint_marc_type_fkey,
653 DROP CONSTRAINT circ_matrix_matchpoint_marc_vr_format_fkey;
655 -- Clean up tables before making normalized index
657 CREATE OR REPLACE FUNCTION action.cleanup_matrix_matchpoints() RETURNS void AS $func$
663 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
664 FROM config.circ_matrix_matchpoint
666 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
667 HAVING COUNT(id) > 1 LOOP
669 UPDATE config.circ_matrix_matchpoint SET active=false
670 WHERE id > temp_row.firstrow
671 AND org_unit = temp_row.org_unit
672 AND grp = temp_row.grp
673 AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
674 AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
675 AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
676 AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
677 AND copy_circ_lib IS NOT DISTINCT FROM temp_row.copy_circ_lib
678 AND copy_owning_lib IS NOT DISTINCT FROM temp_row.copy_owning_lib
679 AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
680 AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag
681 AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
682 AND is_renewal IS NOT DISTINCT FROM temp_row.is_renewal
683 AND usr_age_lower_bound IS NOT DISTINCT FROM temp_row.usr_age_lower_bound
684 AND usr_age_upper_bound IS NOT DISTINCT FROM temp_row.usr_age_upper_bound;
689 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
690 FROM config.hold_matrix_matchpoint
692 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
693 HAVING COUNT(id) > 1 LOOP
695 UPDATE config.hold_matrix_matchpoint SET active=false
696 WHERE id > temp_row.firstrow
697 AND user_home_ou IS NOT DISTINCT FROM temp_row.user_home_ou
698 AND request_ou IS NOT DISTINCT FROM temp_row.request_ou
699 AND pickup_ou IS NOT DISTINCT FROM temp_row.pickup_ou
700 AND item_owning_ou IS NOT DISTINCT FROM temp_row.item_owning_ou
701 AND item_circ_ou IS NOT DISTINCT FROM temp_row.item_circ_ou
702 AND usr_grp IS NOT DISTINCT FROM temp_row.usr_grp
703 AND requestor_grp IS NOT DISTINCT FROM temp_row.requestor_grp
704 AND circ_modifier IS NOT DISTINCT FROM temp_row.circ_modifier
705 AND marc_type IS NOT DISTINCT FROM temp_row.marc_type
706 AND marc_form IS NOT DISTINCT FROM temp_row.marc_form
707 AND marc_vr_format IS NOT DISTINCT FROM temp_row.marc_vr_format
708 AND juvenile_flag IS NOT DISTINCT FROM temp_row.juvenile_flag
709 AND ref_flag IS NOT DISTINCT FROM temp_row.ref_flag;
712 $func$ LANGUAGE plpgsql;
714 SELECT action.cleanup_matrix_matchpoints();
716 DROP FUNCTION IF EXISTS action.cleanup_matrix_matchpoints();
718 -- Create Normalized indexes
720 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;
722 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;
725 DROP FUNCTION asset.metarecord_copy_count ( INT, BIGINT, BOOL );
726 DROP FUNCTION asset.record_copy_count ( INT, BIGINT, BOOL );
728 DROP FUNCTION asset.opac_ou_record_copy_count (INT, BIGINT);
729 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$
734 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;
736 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
741 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
745 actor.org_unit_descendants(ans.id) d
746 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
747 JOIN asset.copy cp ON (cp.id = av.copy_id)
751 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
758 $f$ LANGUAGE PLPGSQL;
760 DROP FUNCTION asset.opac_lasso_record_copy_count (INT, BIGINT);
761 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$
766 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;
768 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
773 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
777 actor.org_unit_descendants(ans.id) d
778 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
779 JOIN asset.copy cp ON (cp.id = av.copy_id)
783 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
790 $f$ LANGUAGE PLPGSQL;
792 DROP FUNCTION asset.staff_ou_record_copy_count (INT, BIGINT);
794 DROP FUNCTION asset.staff_lasso_record_copy_count (INT, BIGINT);
796 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$
798 IF staff IS TRUE THEN
800 RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
802 RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
806 RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
808 RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
814 $f$ LANGUAGE PLPGSQL;
816 DROP FUNCTION asset.opac_ou_metarecord_copy_count (INT, BIGINT);
817 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$
822 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;
824 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
829 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
833 actor.org_unit_descendants(ans.id) d
834 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
835 JOIN asset.copy cp ON (cp.id = av.copy_id)
836 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
840 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
847 $f$ LANGUAGE PLPGSQL;
849 DROP FUNCTION asset.opac_lasso_metarecord_copy_count (INT, BIGINT);
850 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$
855 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;
857 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
862 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
866 actor.org_unit_descendants(ans.id) d
867 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
868 JOIN asset.copy cp ON (cp.id = av.copy_id)
869 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
873 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
880 $f$ LANGUAGE PLPGSQL;
882 DROP FUNCTION asset.staff_lasso_metarecord_copy_count (INT, BIGINT);
884 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$
886 IF staff IS TRUE THEN
888 RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
890 RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
894 RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
896 RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
902 $f$ LANGUAGE PLPGSQL;
905 CREATE OR REPLACE VIEW reporter.simple_record AS
912 title.value AS title,
913 uniform_title.value AS uniform_title,
914 author.value AS author,
915 publisher.value AS publisher,
916 SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
917 series_title.value AS series_title,
918 series_statement.value AS series_statement,
919 summary.value AS summary,
920 ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
921 ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
922 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
923 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
924 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
925 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
926 ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
927 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
928 FROM biblio.record_entry r
929 JOIN metabib.metarecord_source_map s ON (s.source = r.id)
930 LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
931 LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
932 LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
933 LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
934 LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
935 LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
936 LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
937 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')
938 LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
939 LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
940 GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
942 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
948 FIRST(title.value) AS title,
949 FIRST(author.value) AS author,
950 ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
951 ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
952 ARRAY_ACCUM( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
953 ARRAY_ACCUM( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn
954 FROM biblio.record_entry r
955 LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
956 LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
957 LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
958 LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
959 LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
960 LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
964 ALTER TABLE money.credit_card_payment ADD COLUMN cc_order_number TEXT;
966 -- Changing return types requires explicit dropping of old versions
967 DROP FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL );
968 DROP FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL );
969 DROP FUNCTION action.item_user_circ_test( INT, BIGINT, INT );
970 DROP FUNCTION action.item_user_renew_test( INT, BIGINT, INT );
973 CREATE TYPE action.found_circ_matrix_matchpoint AS ( success BOOL, matchpoint config.circ_matrix_matchpoint, buildrows INT[] );
975 -- Helper function - For manual calling, it can be easier to pass in IDs instead of objects
976 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$
978 item_object asset.copy%ROWTYPE;
979 user_object actor.usr%ROWTYPE;
981 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
982 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
984 RETURN QUERY SELECT * FROM action.find_circ_matrix_matchpoint( context_ou, item_object, user_object, renewal );
986 $func$ LANGUAGE plpgsql;
988 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 );
990 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$
992 user_object actor.usr%ROWTYPE;
993 standing_penalty config.standing_penalty%ROWTYPE;
994 item_object asset.copy%ROWTYPE;
995 item_status_object config.copy_status%ROWTYPE;
996 item_location_object asset.copy_location%ROWTYPE;
997 result action.circ_matrix_test_result;
998 circ_test action.found_circ_matrix_matchpoint;
999 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
1000 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
1001 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
1002 hold_ratio action.hold_stats%ROWTYPE;
1005 context_org_list INT[];
1008 -- Assume success unless we hit a failure condition
1009 result.success := TRUE;
1011 -- Fail if the user is BARRED
1012 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
1014 -- Fail if we couldn't find the user
1015 IF user_object.id IS NULL THEN
1016 result.fail_part := 'no_user';
1017 result.success := FALSE;
1023 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
1025 -- Fail if we couldn't find the item
1026 IF item_object.id IS NULL THEN
1027 result.fail_part := 'no_item';
1028 result.success := FALSE;
1034 IF user_object.barred IS TRUE THEN
1035 result.fail_part := 'actor.usr.barred';
1036 result.success := FALSE;
1041 -- Fail if the item can't circulate
1042 IF item_object.circulate IS FALSE THEN
1043 result.fail_part := 'asset.copy.circulate';
1044 result.success := FALSE;
1049 -- Fail if the item isn't in a circulateable status on a non-renewal
1050 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
1051 result.fail_part := 'asset.copy.status';
1052 result.success := FALSE;
1055 ELSIF renewal AND item_object.status <> 1 THEN
1056 result.fail_part := 'asset.copy.status';
1057 result.success := FALSE;
1062 -- Fail if the item can't circulate because of the shelving location
1063 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
1064 IF item_location_object.circulate IS FALSE THEN
1065 result.fail_part := 'asset.copy_location.circulate';
1066 result.success := FALSE;
1071 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
1073 circ_matchpoint := circ_test.matchpoint;
1074 result.matchpoint := circ_matchpoint.id;
1075 result.circulate := circ_matchpoint.circulate;
1076 result.duration_rule := circ_matchpoint.duration_rule;
1077 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
1078 result.max_fine_rule := circ_matchpoint.max_fine_rule;
1079 result.hard_due_date := circ_matchpoint.hard_due_date;
1080 result.renewals := circ_matchpoint.renewals;
1081 result.buildrows := circ_test.buildrows;
1083 -- Fail if we couldn't find a matchpoint
1084 IF circ_test.success = false THEN
1085 result.fail_part := 'no_matchpoint';
1086 result.success := FALSE;
1089 RETURN; -- All tests after this point require a matchpoint. No sense in running on an incomplete or missing one.
1092 -- Apparently....use the circ matchpoint org unit to determine what org units are valid.
1093 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_matchpoint.org_unit );
1096 penalty_type = '%RENEW%';
1098 penalty_type = '%CIRC%';
1101 FOR standing_penalty IN
1102 SELECT DISTINCT csp.*
1103 FROM actor.usr_standing_penalty usp
1104 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
1105 WHERE usr = match_user
1106 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
1107 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
1108 AND csp.block_list LIKE penalty_type LOOP
1110 result.fail_part := standing_penalty.name;
1111 result.success := FALSE;
1116 -- Fail if the test is set to hard non-circulating
1117 IF circ_matchpoint.circulate IS FALSE THEN
1118 result.fail_part := 'config.circ_matrix_test.circulate';
1119 result.success := FALSE;
1124 -- Fail if the total copy-hold ratio is too low
1125 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
1126 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
1127 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
1128 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
1129 result.success := FALSE;
1135 -- Fail if the available copy-hold ratio is too low
1136 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
1137 IF hold_ratio.hold_count IS NULL THEN
1138 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
1140 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
1141 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
1142 result.success := FALSE;
1148 -- Fail if the user has too many items with specific circ_modifiers checked out
1149 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
1150 SELECT INTO items_out COUNT(*)
1151 FROM action.circulation circ
1152 JOIN asset.copy cp ON (cp.id = circ.target_copy)
1153 WHERE circ.usr = match_user
1154 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
1155 AND circ.checkin_time IS NULL
1156 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
1157 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);
1158 IF items_out >= out_by_circ_mod.items_out THEN
1159 result.fail_part := 'config.circ_matrix_circ_mod_test';
1160 result.success := FALSE;
1166 -- If we passed everything, return the successful matchpoint id
1173 $func$ LANGUAGE plpgsql;
1175 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
1176 SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
1177 $func$ LANGUAGE SQL;
1179 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
1180 SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
1181 $func$ LANGUAGE SQL;
1184 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$
1189 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;
1191 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
1196 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1200 actor.org_unit_descendants(ans.id) d
1201 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1202 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1206 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1213 $f$ LANGUAGE PLPGSQL;
1215 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$
1220 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;
1222 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
1227 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1231 actor.org_unit_descendants(ans.id) d
1232 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1233 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1237 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1244 $f$ LANGUAGE PLPGSQL;
1246 DROP FUNCTION asset.staff_ou_metarecord_copy_count (INT, BIGINT);
1247 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$
1252 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;
1254 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
1259 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1263 actor.org_unit_descendants(ans.id) d
1264 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1265 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1266 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
1270 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1277 $f$ LANGUAGE PLPGSQL;
1279 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$
1284 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;
1286 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
1291 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
1295 actor.org_unit_descendants(ans.id) d
1296 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
1297 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
1298 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
1302 RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
1309 $f$ LANGUAGE PLPGSQL;
1313 UPDATE config.org_unit_setting_type
1314 SET description = 'Amount of time before a hold expires at which point the patron should be alerted. Examples: "5 days", "1 hour"'
1315 WHERE label = 'Holds: Expire Alert Interval';
1317 UPDATE config.org_unit_setting_type
1318 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"'
1319 WHERE label = 'Holds: Default Estimated Wait';
1321 UPDATE config.org_unit_setting_type
1322 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"'
1323 WHERE label = 'Holds: Minimum Estimated Wait';
1325 UPDATE config.org_unit_setting_type
1326 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"'
1327 WHERE label = 'Hold Shelf Status Delay';
1330 UPDATE config.metabib_field
1331 SET xpath = $$//mods32:mods/mods32:subject$$
1332 WHERE field_class = 'subject' AND name = 'complete';
1334 UPDATE config.metabib_field
1335 SET xpath = $$//marc:datafield[@tag='099']$$
1336 WHERE field_class = 'identifier' AND name = 'bibcn';
1339 CREATE TABLE config.record_attr_definition (
1340 name TEXT PRIMARY KEY,
1341 label TEXT NOT NULL, -- I18N
1343 filter BOOL NOT NULL DEFAULT TRUE, -- becomes QP filter if true
1344 sorter BOOL NOT NULL DEFAULT FALSE, -- becomes QP sort() axis if true
1346 -- For pre-extracted fields. Takes the first occurance, uses naive subfield ordering
1347 tag TEXT, -- LIKE format
1348 sf_list TEXT, -- pile-o-values, like 'abcd' for a and b and c and d
1350 -- This is used for both tag/sf and xpath entries
1353 -- For xpath-extracted attrs
1355 format TEXT REFERENCES config.xml_transform (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1360 fixed_field TEXT, -- should exist in config.marc21_ff_pos_map.fixed_field
1362 -- For phys-char fields
1363 phys_char_sf INT REFERENCES config.marc21_physical_characteristic_subfield_map (id)
1366 CREATE TABLE config.record_attr_index_norm_map (
1367 id SERIAL PRIMARY KEY,
1368 attr TEXT NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1369 norm INT NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1371 pos INT NOT NULL DEFAULT 0
1374 CREATE TABLE config.coded_value_map (
1375 id SERIAL PRIMARY KEY,
1376 ctype TEXT NOT NULL REFERENCES config.record_attr_definition (name) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
1378 value TEXT NOT NULL,
1382 -- record attributes
1383 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('alph','Alph','Alph');
1384 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('audience','Audn','Audn');
1385 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('bib_level','BLvl','BLvl');
1386 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('biog','Biog','Biog');
1387 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('conf','Conf','Conf');
1388 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('control_type','Ctrl','Ctrl');
1389 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ctry','Ctry','Ctry');
1390 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date1','Date1','Date1');
1391 INSERT INTO config.record_attr_definition (name,label,fixed_field,sorter,filter) values ('pubdate','Pub Date','Date1',TRUE,FALSE);
1392 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('date2','Date2','Date2');
1393 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('cat_form','Desc','Desc');
1394 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('pub_status','DtSt','DtSt');
1395 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('enc_level','ELvl','ELvl');
1396 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('fest','Fest','Fest');
1397 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_form','Form','Form');
1398 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('gpub','GPub','GPub');
1399 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ills','Ills','Ills');
1400 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('indx','Indx','Indx');
1401 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_lang','Lang','Lang');
1402 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('lit_form','LitF','LitF');
1403 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('mrec','MRec','MRec');
1404 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('ff_sl','S/L','S/L');
1405 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('type_mat','TMat','TMat');
1406 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('item_type','Type','Type');
1407 INSERT INTO config.record_attr_definition (name,label,phys_char_sf) values ('vr_format','Videorecording format',72);
1408 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('titlesort','Title',TRUE,FALSE,'tnf');
1409 INSERT INTO config.record_attr_definition (name,label,sorter,filter,tag) values ('authorsort','Author',TRUE,FALSE,'1%');
1411 INSERT INTO config.upgrade_log (version) VALUES ('0624'); -- miker/tsbere
1412 -- Cont was typod as Conf. Update the old entries.
1413 UPDATE config.marc21_ff_pos_map SET fixed_field = 'Cont' WHERE fixed_field = 'Conf' AND length > 1;
1414 -- Conf thus didn't exist. Add it.
1415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 11, 1, ' ');
1416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 11, 1, ' ');
1417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 29, 1, ' ');
1418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 29, 1, ' ');
1420 INSERT INTO config.coded_value_map (ctype,code,value,description)
1421 SELECT 'item_lang' AS ctype, code, value, NULL FROM config.language_map
1423 SELECT 'bib_level' AS ctype, code, value, NULL FROM config.bib_level_map
1425 SELECT 'item_form' AS ctype, code, value, NULL FROM config.item_form_map
1427 SELECT 'item_type' AS ctype, code, value, NULL FROM config.item_type_map
1429 SELECT 'lit_form' AS ctype, code, value, description FROM config.lit_form_map
1431 SELECT 'audience' AS ctype, code, value, description FROM config.audience_map
1433 SELECT 'vr_format' AS ctype, code, value, NULL FROM config.videorecording_format_map;
1435 ALTER TABLE config.i18n_locale DROP CONSTRAINT i18n_locale_marc_code_fkey;
1437 DROP TABLE config.language_map;
1438 DROP TABLE config.bib_level_map;
1439 DROP TABLE config.item_form_map;
1440 DROP TABLE config.item_type_map;
1441 DROP TABLE config.lit_form_map;
1442 DROP TABLE config.audience_map;
1443 DROP TABLE config.videorecording_format_map;
1445 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;
1446 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;
1447 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;
1448 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;
1449 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;
1450 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;
1451 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;
1453 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;
1454 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;
1456 CREATE VIEW config.language_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_lang';
1457 CREATE VIEW config.bib_level_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'bib_level';
1458 CREATE VIEW config.item_form_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_form';
1459 CREATE VIEW config.item_type_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'item_type';
1460 CREATE VIEW config.lit_form_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'lit_form';
1461 CREATE VIEW config.audience_map AS SELECT code, value, description FROM config.coded_value_map WHERE ctype = 'audience';
1462 CREATE VIEW config.videorecording_format_map AS SELECT code, value FROM config.coded_value_map WHERE ctype = 'vr_format';
1464 CREATE TABLE metabib.record_attr (
1465 id BIGINT PRIMARY KEY REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
1466 attrs HSTORE NOT NULL DEFAULT ''::HSTORE
1468 CREATE INDEX metabib_svf_attrs_idx ON metabib.record_attr USING GIST (attrs);
1469 CREATE INDEX metabib_svf_date1_idx ON metabib.record_attr ( (attrs->'date1') );
1470 CREATE INDEX metabib_svf_dates_idx ON metabib.record_attr ( (attrs->'date1'), (attrs->'date2') );
1472 INSERT INTO metabib.record_attr (id,attrs)
1473 SELECT DISTINCT ON (mrd.record) mrd.record, hstore(mrd) - '{id,record}'::TEXT[] FROM metabib.rec_descriptor mrd;
1475 -- Back-compat view ... we're moving to an HSTORE world
1476 CREATE TYPE metabib.rec_desc_type AS (
1494 DROP TABLE metabib.rec_descriptor CASCADE;
1496 CREATE VIEW metabib.rec_descriptor AS
1499 (populate_record(NULL::metabib.rec_desc_type, attrs)).*
1500 FROM metabib.record_attr;
1502 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
1509 retval config.marc21_rec_type_map%ROWTYPE;
1511 ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
1513 IF ldr IS NULL OR ldr = '' THEN
1514 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1518 SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
1519 SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
1522 tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
1523 bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
1525 -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
1527 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
1530 IF retval.code IS NULL THEN
1531 SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1536 $func$ LANGUAGE PLPGSQL;
1538 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
1539 SELECT * FROM vandelay.marc21_record_type( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1540 $func$ LANGUAGE SQL;
1542 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
1549 rtype := (vandelay.marc21_record_type( marc )).code;
1550 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
1551 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1552 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1555 val := REPEAT( ff_pos.default_val, ff_pos.length );
1561 $func$ LANGUAGE PLPGSQL;
1563 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
1564 SELECT * FROM vandelay.marc21_extract_fixed_field( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2 );
1565 $func$ LANGUAGE SQL;
1567 CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
1568 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
1573 output biblio.record_ff_map%ROWTYPE;
1575 rtype := (vandelay.marc21_record_type( marc )).code;
1577 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
1578 output.ff_name := ff_pos.fixed_field;
1579 output.ff_value := NULL;
1581 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(tag) || '"]/text()', marc ) ) x(value) LOOP
1582 output.ff_value := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1583 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
1585 output.ff_value := NULL;
1592 $func$ LANGUAGE PLPGSQL;
1594 CREATE OR REPLACE FUNCTION biblio.marc21_extract_all_fixed_fields( rid BIGINT ) RETURNS SETOF biblio.record_ff_map AS $func$
1595 SELECT $1 AS record, ff_name, ff_value FROM vandelay.marc21_extract_all_fixed_fields( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1596 $func$ LANGUAGE SQL;
1598 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1602 ptype config.marc21_physical_characteristic_type_map%ROWTYPE;
1603 psf config.marc21_physical_characteristic_subfield_map%ROWTYPE;
1604 pval config.marc21_physical_characteristic_value_map%ROWTYPE;
1605 retval biblio.marc21_physical_characteristics%ROWTYPE;
1608 _007 := oils_xpath_string( '//*[@tag="007"]', marc );
1610 IF _007 IS NOT NULL AND _007 <> '' THEN
1611 SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
1613 IF ptype.ptype_key IS NOT NULL THEN
1614 FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
1615 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 );
1617 IF pval.id IS NOT NULL THEN
1620 retval.ptype := ptype.ptype_key;
1621 retval.subfield := psf.id;
1622 retval.value := pval.id;
1632 $func$ LANGUAGE PLPGSQL;
1634 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1635 SELECT id, $1 AS record, ptype, subfield, value FROM vandelay.marc21_physical_characteristics( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
1636 $func$ LANGUAGE SQL;
1638 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
1640 transformed_xml TEXT;
1643 xfrm config.xml_transform%ROWTYPE;
1645 new_attrs HSTORE := ''::HSTORE;
1646 attr_def config.record_attr_definition%ROWTYPE;
1649 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
1650 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
1651 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
1652 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
1653 RETURN NEW; -- and we're done
1656 IF TG_OP = 'UPDATE' THEN -- re-ingest?
1657 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
1659 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
1664 -- Record authority linking
1665 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
1667 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
1670 -- Flatten and insert the mfr data
1671 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
1673 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
1675 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
1676 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
1678 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
1680 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
1681 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
1682 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
1683 WHERE record = NEW.id
1684 AND tag LIKE attr_def.tag
1686 WHEN attr_def.sf_list IS NOT NULL
1687 THEN POSITION(subfield IN attr_def.sf_list) > 0
1694 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
1695 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
1697 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
1699 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
1701 -- See if we can skip the XSLT ... it's expensive
1702 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1703 -- Can't skip the transform
1704 IF xfrm.xslt <> '---' THEN
1705 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
1707 transformed_xml := NEW.marc;
1710 prev_xfrm := xfrm.name;
1713 IF xfrm.name IS NULL THEN
1714 -- just grab the marcxml (empty) transform
1715 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
1716 prev_xfrm := xfrm.name;
1719 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
1721 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
1722 SELECT value::TEXT INTO attr_value
1723 FROM biblio.marc21_physical_characteristics(NEW.id)
1724 WHERE subfield = attr_def.phys_char_sf
1725 LIMIT 1; -- Just in case ...
1729 -- apply index normalizers to attr_value
1731 SELECT n.func AS func,
1732 n.param_count AS param_count,
1734 FROM config.index_normalizer n
1735 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
1736 WHERE attr = attr_def.name
1738 EXECUTE 'SELECT ' || normalizer.func || '(' ||
1739 quote_literal( attr_value ) ||
1741 WHEN normalizer.param_count > 0
1742 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1745 ')' INTO attr_value;
1749 -- Add the new value to the hstore
1750 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
1754 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
1755 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
1757 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
1763 -- Gather and insert the field entry data
1764 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
1766 -- Located URI magic
1767 IF TG_OP = 'INSERT' THEN
1768 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
1770 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
1773 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
1775 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
1779 -- (re)map metarecord-bib linking
1780 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
1781 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
1783 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
1785 ELSE -- we're doing an update, and we're not deleted, remap
1786 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
1788 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
1794 $func$ LANGUAGE PLPGSQL;
1796 DROP FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT );
1798 CREATE OR REPLACE FUNCTION public.approximate_date( TEXT, TEXT ) RETURNS TEXT AS $func$
1799 SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );
1800 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1802 CREATE OR REPLACE FUNCTION public.approximate_low_date( TEXT ) RETURNS TEXT AS $func$
1803 SELECT approximate_date( $1, '0');
1804 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1806 CREATE OR REPLACE FUNCTION public.approximate_high_date( TEXT ) RETURNS TEXT AS $func$
1807 SELECT approximate_date( $1, '9');
1808 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1810 CREATE OR REPLACE FUNCTION public.integer_or_null( TEXT ) RETURNS TEXT AS $func$
1811 SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END
1812 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1814 CREATE OR REPLACE FUNCTION public.content_or_null( TEXT ) RETURNS TEXT AS $func$
1815 SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END
1816 $func$ LANGUAGE SQL STRICT IMMUTABLE;
1818 CREATE OR REPLACE FUNCTION public.force_to_isbn13( TEXT ) RETURNS TEXT AS $func$
1823 # Find the first ISBN, force it to ISBN13 and return it
1827 foreach my $word (split(/\s/, $input)) {
1828 my $isbn = Business::ISBN->new($word);
1830 # First check the checksum; if it is not valid, fix it and add the original
1831 # bad-checksum ISBN to the output
1832 if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
1833 $isbn->fix_checksum();
1836 # If we now have a valid ISBN, force it to ISBN13 and return it
1837 return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
1840 $func$ LANGUAGE PLPERLU;
1842 COMMENT ON FUNCTION public.force_to_isbn13(TEXT) IS $$
1844 * Copyright (C) 2011 Equinox Software
1845 * Mike Rylander <mrylander@gmail.com>
1847 * Inspired by translate_isbn1013
1849 * The force_to_isbn13 function takes an input ISBN and returns the ISBN13
1850 * version without hypens and with a repaired checksum if the checksum was bad
1855 UPDATE config.metabib_field
1856 SET xpath = $$//marc:datafield[@tag='024' and @ind1='1']/marc:subfield[@code='a' or @code='z']$$
1857 WHERE field_class = 'identifier' AND name = 'upc';
1859 UPDATE config.metabib_field
1860 SET xpath = $$//marc:datafield[@tag='024' and @ind1='2']/marc:subfield[@code='a' or @code='z']$$
1861 WHERE field_class = 'identifier' AND name = 'ismn';
1863 UPDATE config.metabib_field
1864 SET xpath = $$//marc:datafield[@tag='024' and @ind1='3']/marc:subfield[@code='a' or @code='z']$$
1865 WHERE field_class = 'identifier' AND name = 'ean';
1867 UPDATE config.metabib_field
1868 SET xpath = $$//marc:datafield[@tag='024' and @ind1='0']/marc:subfield[@code='a' or @code='z']$$
1869 WHERE field_class = 'identifier' AND name = 'isrc';
1871 UPDATE config.metabib_field
1872 SET xpath = $$//marc:datafield[@tag='024' and @ind1='4']/marc:subfield[@code='a' or @code='z']$$
1873 WHERE field_class = 'identifier' AND name = 'sici';
1876 INSERT into config.org_unit_setting_type
1877 ( name, label, description, datatype ) VALUES
1879 ( 'ui.patron.edit.au.active.show',
1880 oils_i18n_gettext('ui.patron.edit.au.active.show', 'GUI: Show active field on patron registration', 'coust', 'label'),
1881 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'),
1883 ( 'ui.patron.edit.au.active.suggest',
1884 oils_i18n_gettext('ui.patron.edit.au.active.suggest', 'GUI: Suggest active field on patron registration', 'coust', 'label'),
1885 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'),
1887 ( 'ui.patron.edit.au.alert_message.show',
1888 oils_i18n_gettext('ui.patron.edit.au.alert_message.show', 'GUI: Show alert_message field on patron registration', 'coust', 'label'),
1889 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'),
1891 ( 'ui.patron.edit.au.alert_message.suggest',
1892 oils_i18n_gettext('ui.patron.edit.au.alert_message.suggest', 'GUI: Suggest alert_message field on patron registration', 'coust', 'label'),
1893 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'),
1895 ( 'ui.patron.edit.au.alias.show',
1896 oils_i18n_gettext('ui.patron.edit.au.alias.show', 'GUI: Show alias field on patron registration', 'coust', 'label'),
1897 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'),
1899 ( 'ui.patron.edit.au.alias.suggest',
1900 oils_i18n_gettext('ui.patron.edit.au.alias.suggest', 'GUI: Suggest alias field on patron registration', 'coust', 'label'),
1901 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'),
1903 ( 'ui.patron.edit.au.barred.show',
1904 oils_i18n_gettext('ui.patron.edit.au.barred.show', 'GUI: Show barred field on patron registration', 'coust', 'label'),
1905 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'),
1907 ( 'ui.patron.edit.au.barred.suggest',
1908 oils_i18n_gettext('ui.patron.edit.au.barred.suggest', 'GUI: Suggest barred field on patron registration', 'coust', 'label'),
1909 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'),
1911 ( 'ui.patron.edit.au.claims_never_checked_out_count.show',
1912 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'),
1913 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'),
1915 ( 'ui.patron.edit.au.claims_never_checked_out_count.suggest',
1916 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'),
1917 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'),
1919 ( 'ui.patron.edit.au.claims_returned_count.show',
1920 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.show', 'GUI: Show claims_returned_count field on patron registration', 'coust', 'label'),
1921 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'),
1923 ( 'ui.patron.edit.au.claims_returned_count.suggest',
1924 oils_i18n_gettext('ui.patron.edit.au.claims_returned_count.suggest', 'GUI: Suggest claims_returned_count field on patron registration', 'coust', 'label'),
1925 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'),
1927 ( 'ui.patron.edit.au.day_phone.example',
1928 oils_i18n_gettext('ui.patron.edit.au.day_phone.example', 'GUI: Example for day_phone field on patron registration', 'coust', 'label'),
1929 oils_i18n_gettext('ui.patron.edit.au.day_phone.example', 'The Example for validation on the day_phone field in patron registration.', 'coust', 'description'),
1931 ( 'ui.patron.edit.au.day_phone.regex',
1932 oils_i18n_gettext('ui.patron.edit.au.day_phone.regex', 'GUI: Regex for day_phone field on patron registration', 'coust', 'label'),
1933 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'),
1935 ( 'ui.patron.edit.au.day_phone.require',
1936 oils_i18n_gettext('ui.patron.edit.au.day_phone.require', 'GUI: Require day_phone field on patron registration', 'coust', 'label'),
1937 oils_i18n_gettext('ui.patron.edit.au.day_phone.require', 'The day_phone field will be required on the patron registration screen.', 'coust', 'description'),
1939 ( 'ui.patron.edit.au.day_phone.show',
1940 oils_i18n_gettext('ui.patron.edit.au.day_phone.show', 'GUI: Show day_phone field on patron registration', 'coust', 'label'),
1941 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'),
1943 ( 'ui.patron.edit.au.day_phone.suggest',
1944 oils_i18n_gettext('ui.patron.edit.au.day_phone.suggest', 'GUI: Suggest day_phone field on patron registration', 'coust', 'label'),
1945 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'),
1947 ( 'ui.patron.edit.au.dob.calendar',
1948 oils_i18n_gettext('ui.patron.edit.au.dob.calendar', 'GUI: Show calendar widget for dob field on patron registration', 'coust', 'label'),
1949 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'),
1951 ( 'ui.patron.edit.au.dob.require',
1952 oils_i18n_gettext('ui.patron.edit.au.dob.require', 'GUI: Require dob field on patron registration', 'coust', 'label'),
1953 oils_i18n_gettext('ui.patron.edit.au.dob.require', 'The dob field will be required on the patron registration screen.', 'coust', 'description'),
1955 ( 'ui.patron.edit.au.dob.show',
1956 oils_i18n_gettext('ui.patron.edit.au.dob.show', 'GUI: Show dob field on patron registration', 'coust', 'label'),
1957 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'),
1959 ( 'ui.patron.edit.au.dob.suggest',
1960 oils_i18n_gettext('ui.patron.edit.au.dob.suggest', 'GUI: Suggest dob field on patron registration', 'coust', 'label'),
1961 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'),
1963 ( 'ui.patron.edit.au.email.example',
1964 oils_i18n_gettext('ui.patron.edit.au.email.example', 'GUI: Example for email field on patron registration', 'coust', 'label'),
1965 oils_i18n_gettext('ui.patron.edit.au.email.example', 'The Example for validation on the email field in patron registration.', 'coust', 'description'),
1967 ( 'ui.patron.edit.au.email.regex',
1968 oils_i18n_gettext('ui.patron.edit.au.email.regex', 'GUI: Regex for email field on patron registration', 'coust', 'label'),
1969 oils_i18n_gettext('ui.patron.edit.au.email.regex', 'The Regular Expression for validation on the email field in patron registration.', 'coust', 'description'),
1971 ( 'ui.patron.edit.au.email.require',
1972 oils_i18n_gettext('ui.patron.edit.au.email.require', 'GUI: Require email field on patron registration', 'coust', 'label'),
1973 oils_i18n_gettext('ui.patron.edit.au.email.require', 'The email field will be required on the patron registration screen.', 'coust', 'description'),
1975 ( 'ui.patron.edit.au.email.show',
1976 oils_i18n_gettext('ui.patron.edit.au.email.show', 'GUI: Show email field on patron registration', 'coust', 'label'),
1977 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'),
1979 ( 'ui.patron.edit.au.email.suggest',
1980 oils_i18n_gettext('ui.patron.edit.au.email.suggest', 'GUI: Suggest email field on patron registration', 'coust', 'label'),
1981 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'),
1983 ( 'ui.patron.edit.au.evening_phone.example',
1984 oils_i18n_gettext('ui.patron.edit.au.evening_phone.example', 'GUI: Example for evening_phone field on patron registration', 'coust', 'label'),
1985 oils_i18n_gettext('ui.patron.edit.au.evening_phone.example', 'The Example for validation on the evening_phone field in patron registration.', 'coust', 'description'),
1987 ( 'ui.patron.edit.au.evening_phone.regex',
1988 oils_i18n_gettext('ui.patron.edit.au.evening_phone.regex', 'GUI: Regex for evening_phone field on patron registration', 'coust', 'label'),
1989 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'),
1991 ( 'ui.patron.edit.au.evening_phone.require',
1992 oils_i18n_gettext('ui.patron.edit.au.evening_phone.require', 'GUI: Require evening_phone field on patron registration', 'coust', 'label'),
1993 oils_i18n_gettext('ui.patron.edit.au.evening_phone.require', 'The evening_phone field will be required on the patron registration screen.', 'coust', 'description'),
1995 ( 'ui.patron.edit.au.evening_phone.show',
1996 oils_i18n_gettext('ui.patron.edit.au.evening_phone.show', 'GUI: Show evening_phone field on patron registration', 'coust', 'label'),
1997 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'),
1999 ( 'ui.patron.edit.au.evening_phone.suggest',
2000 oils_i18n_gettext('ui.patron.edit.au.evening_phone.suggest', 'GUI: Suggest evening_phone field on patron registration', 'coust', 'label'),
2001 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'),
2003 ( 'ui.patron.edit.au.ident_value.show',
2004 oils_i18n_gettext('ui.patron.edit.au.ident_value.show', 'GUI: Show ident_value field on patron registration', 'coust', 'label'),
2005 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'),
2007 ( 'ui.patron.edit.au.ident_value.suggest',
2008 oils_i18n_gettext('ui.patron.edit.au.ident_value.suggest', 'GUI: Suggest ident_value field on patron registration', 'coust', 'label'),
2009 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'),
2011 ( 'ui.patron.edit.au.ident_value2.show',
2012 oils_i18n_gettext('ui.patron.edit.au.ident_value2.show', 'GUI: Show ident_value2 field on patron registration', 'coust', 'label'),
2013 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'),
2015 ( 'ui.patron.edit.au.ident_value2.suggest',
2016 oils_i18n_gettext('ui.patron.edit.au.ident_value2.suggest', 'GUI: Suggest ident_value2 field on patron registration', 'coust', 'label'),
2017 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'),
2019 ( 'ui.patron.edit.au.juvenile.show',
2020 oils_i18n_gettext('ui.patron.edit.au.juvenile.show', 'GUI: Show juvenile field on patron registration', 'coust', 'label'),
2021 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'),
2023 ( 'ui.patron.edit.au.juvenile.suggest',
2024 oils_i18n_gettext('ui.patron.edit.au.juvenile.suggest', 'GUI: Suggest juvenile field on patron registration', 'coust', 'label'),
2025 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'),
2027 ( 'ui.patron.edit.au.master_account.show',
2028 oils_i18n_gettext('ui.patron.edit.au.master_account.show', 'GUI: Show master_account field on patron registration', 'coust', 'label'),
2029 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'),
2031 ( 'ui.patron.edit.au.master_account.suggest',
2032 oils_i18n_gettext('ui.patron.edit.au.master_account.suggest', 'GUI: Suggest master_account field on patron registration', 'coust', 'label'),
2033 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'),
2035 ( 'ui.patron.edit.au.other_phone.example',
2036 oils_i18n_gettext('ui.patron.edit.au.other_phone.example', 'GUI: Example for other_phone field on patron registration', 'coust', 'label'),
2037 oils_i18n_gettext('ui.patron.edit.au.other_phone.example', 'The Example for validation on the other_phone field in patron registration.', 'coust', 'description'),
2039 ( 'ui.patron.edit.au.other_phone.regex',
2040 oils_i18n_gettext('ui.patron.edit.au.other_phone.regex', 'GUI: Regex for other_phone field on patron registration', 'coust', 'label'),
2041 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'),
2043 ( 'ui.patron.edit.au.other_phone.require',
2044 oils_i18n_gettext('ui.patron.edit.au.other_phone.require', 'GUI: Require other_phone field on patron registration', 'coust', 'label'),
2045 oils_i18n_gettext('ui.patron.edit.au.other_phone.require', 'The other_phone field will be required on the patron registration screen.', 'coust', 'description'),
2047 ( 'ui.patron.edit.au.other_phone.show',
2048 oils_i18n_gettext('ui.patron.edit.au.other_phone.show', 'GUI: Show other_phone field on patron registration', 'coust', 'label'),
2049 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'),
2051 ( 'ui.patron.edit.au.other_phone.suggest',
2052 oils_i18n_gettext('ui.patron.edit.au.other_phone.suggest', 'GUI: Suggest other_phone field on patron registration', 'coust', 'label'),
2053 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'),
2055 ( 'ui.patron.edit.au.second_given_name.show',
2056 oils_i18n_gettext('ui.patron.edit.au.second_given_name.show', 'GUI: Show second_given_name field on patron registration', 'coust', 'label'),
2057 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'),
2059 ( 'ui.patron.edit.au.second_given_name.suggest',
2060 oils_i18n_gettext('ui.patron.edit.au.second_given_name.suggest', 'GUI: Suggest second_given_name field on patron registration', 'coust', 'label'),
2061 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'),
2063 ( 'ui.patron.edit.au.suffix.show',
2064 oils_i18n_gettext('ui.patron.edit.au.suffix.show', 'GUI: Show suffix field on patron registration', 'coust', 'label'),
2065 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'),
2067 ( 'ui.patron.edit.au.suffix.suggest',
2068 oils_i18n_gettext('ui.patron.edit.au.suffix.suggest', 'GUI: Suggest suffix field on patron registration', 'coust', 'label'),
2069 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'),
2071 ( 'ui.patron.edit.aua.county.require',
2072 oils_i18n_gettext('ui.patron.edit.aua.county.require', 'GUI: Require county field on patron registration', 'coust', 'label'),
2073 oils_i18n_gettext('ui.patron.edit.aua.county.require', 'The county field will be required on the patron registration screen.', 'coust', 'description'),
2075 ( 'ui.patron.edit.aua.post_code.example',
2076 oils_i18n_gettext('ui.patron.edit.aua.post_code.example', 'GUI: Example for post_code field on patron registration', 'coust', 'label'),
2077 oils_i18n_gettext('ui.patron.edit.aua.post_code.example', 'The Example for validation on the post_code field in patron registration.', 'coust', 'description'),
2079 ( 'ui.patron.edit.aua.post_code.regex',
2080 oils_i18n_gettext('ui.patron.edit.aua.post_code.regex', 'GUI: Regex for post_code field on patron registration', 'coust', 'label'),
2081 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'),
2083 ( 'ui.patron.edit.default_suggested',
2084 oils_i18n_gettext('ui.patron.edit.default_suggested', 'GUI: Default showing suggested patron registration fields', 'coust', 'label'),
2085 oils_i18n_gettext('ui.patron.edit.default_suggested', 'Instead of All fields, show just suggested fields in patron registration by default.', 'coust', 'description'),
2087 ( 'ui.patron.edit.phone.example',
2088 oils_i18n_gettext('ui.patron.edit.phone.example', 'GUI: Example for phone fields on patron registration', 'coust', 'label'),
2089 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'),
2091 ( 'ui.patron.edit.phone.regex',
2092 oils_i18n_gettext('ui.patron.edit.phone.regex', 'GUI: Regex for phone fields on patron registration', 'coust', 'label'),
2093 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'),
2096 -- update actor.usr_address indexes
2097 DROP INDEX IF EXISTS actor.actor_usr_addr_street1_idx;
2098 DROP INDEX IF EXISTS actor.actor_usr_addr_street2_idx;
2099 DROP INDEX IF EXISTS actor.actor_usr_addr_city_idx;
2100 DROP INDEX IF EXISTS actor.actor_usr_addr_state_idx;
2101 DROP INDEX IF EXISTS actor.actor_usr_addr_post_code_idx;
2103 CREATE INDEX actor_usr_addr_street1_idx ON actor.usr_address (evergreen.lowercase(street1));
2104 CREATE INDEX actor_usr_addr_street2_idx ON actor.usr_address (evergreen.lowercase(street2));
2105 CREATE INDEX actor_usr_addr_city_idx ON actor.usr_address (evergreen.lowercase(city));
2106 CREATE INDEX actor_usr_addr_state_idx ON actor.usr_address (evergreen.lowercase(state));
2107 CREATE INDEX actor_usr_addr_post_code_idx ON actor.usr_address (evergreen.lowercase(post_code));
2109 -- update actor.usr indexes
2110 DROP INDEX IF EXISTS actor.actor_usr_first_given_name_idx;
2111 DROP INDEX IF EXISTS actor.actor_usr_second_given_name_idx;
2112 DROP INDEX IF EXISTS actor.actor_usr_family_name_idx;
2113 DROP INDEX IF EXISTS actor.actor_usr_email_idx;
2114 DROP INDEX IF EXISTS actor.actor_usr_day_phone_idx;
2115 DROP INDEX IF EXISTS actor.actor_usr_evening_phone_idx;
2116 DROP INDEX IF EXISTS actor.actor_usr_other_phone_idx;
2117 DROP INDEX IF EXISTS actor.actor_usr_ident_value_idx;
2118 DROP INDEX IF EXISTS actor.actor_usr_ident_value2_idx;
2120 CREATE INDEX actor_usr_first_given_name_idx ON actor.usr (evergreen.lowercase(first_given_name));
2121 CREATE INDEX actor_usr_second_given_name_idx ON actor.usr (evergreen.lowercase(second_given_name));
2122 CREATE INDEX actor_usr_family_name_idx ON actor.usr (evergreen.lowercase(family_name));
2123 CREATE INDEX actor_usr_email_idx ON actor.usr (evergreen.lowercase(email));
2124 CREATE INDEX actor_usr_day_phone_idx ON actor.usr (evergreen.lowercase(day_phone));
2125 CREATE INDEX actor_usr_evening_phone_idx ON actor.usr (evergreen.lowercase(evening_phone));
2126 CREATE INDEX actor_usr_other_phone_idx ON actor.usr (evergreen.lowercase(other_phone));
2127 CREATE INDEX actor_usr_ident_value_idx ON actor.usr (evergreen.lowercase(ident_value));
2128 CREATE INDEX actor_usr_ident_value2_idx ON actor.usr (evergreen.lowercase(ident_value2));
2130 -- update actor.card indexes
2131 DROP INDEX IF EXISTS actor.actor_card_barcode_evergreen_lowercase_idx;
2132 CREATE INDEX actor_card_barcode_evergreen_lowercase_idx ON actor.card (evergreen.lowercase(barcode));
2134 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
2143 DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
2145 SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
2147 IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
2148 id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
2150 IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
2151 SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
2152 SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
2153 IF exact_id IS NOT NULL THEN
2154 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
2159 IF exact_id IS NULL THEN
2160 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
2162 -- All numbers? check for an id match
2163 IF (attr.attr_value ~ $r$^\d+$$r$) THEN
2164 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
2165 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
2169 -- Looks like an ISBN? check for an isbn match
2170 IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
2171 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
2172 PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
2174 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
2178 -- subcheck for isbn-as-tcn
2179 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
2180 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2184 -- check for an OCLC tcn_value match
2185 IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
2186 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
2187 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2191 -- check for a direct tcn_value match
2192 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
2193 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
2196 -- check for a direct item barcode match
2199 FROM biblio.record_entry b
2200 JOIN asset.call_number cn ON (cn.record = b.id)
2201 JOIN asset.copy cp ON (cp.call_number = cn.id)
2202 WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
2204 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
2212 $func$ LANGUAGE PLPGSQL;
2216 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
2217 # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
2218 # thus could probably be considered a derived work, although nothing was
2219 # directly copied - but to err on the safe side of providing attribution:
2220 # Copyright (C) 2007 LibLime
2221 # Copyright (C) 2011 Equinox Software, Inc (Steve Callendar)
2222 # Licensed under the GPL v2 or later
2227 # Converts the callnumber to uppercase
2228 # Strips spaces from start and end of the call number
2229 # Converts anything other than letters, digits, and periods into spaces
2230 # Collapses multiple spaces into a single underscore
2231 my $callnum = uc(shift);
2232 $callnum =~ s/^\s//g;
2233 $callnum =~ s/\s$//g;
2234 # NOTE: this previously used underscores, but this caused sorting issues
2235 # for the "before" half of page 0 on CN browse, sorting CNs containing a
2236 # decimal before "whole number" CNs
2237 $callnum =~ s/[^A-Z0-9_.]/ /g;
2238 $callnum =~ s/ {2,}/ /g;
2241 $func$ LANGUAGE PLPERLU;
2246 INSERT INTO config.record_attr_definition (name,label,fixed_field) values ('language','Language (2.0 compat version)','Lang');
2247 UPDATE metabib.record_attr SET attrs = attrs || hstore('language',(attrs->'item_lang'));
2251 UPDATE asset.call_number_class
2252 SET field = '080ab,082ab,092abef'
2257 UPDATE asset.call_number_class
2258 SET field = '050ab,055ab,090abef'
2263 -- Using a tool such as pgadmin to run this script may fail
2264 -- If it does, try psql command line.
2266 -- Change this to FALSE to disable updating existing circs
2267 -- Otherwise will use the fine interval for the grace period
2273 ALTER TABLE config.rule_recurring_fine
2274 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '1 day';
2276 ALTER TABLE action.circulation
2277 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '0 seconds';
2279 ALTER TABLE action.aged_circulation
2280 ADD COLUMN grace_period INTERVAL NOT NULL DEFAULT '0 seconds';
2282 -- Remove defaults needed to stop null complaints
2284 ALTER TABLE action.circulation
2285 ALTER COLUMN grace_period DROP DEFAULT;
2287 ALTER TABLE action.aged_circulation
2288 ALTER COLUMN grace_period DROP DEFAULT;
2292 DROP VIEW action.all_circulation;
2293 DROP VIEW action.open_circulation;
2294 DROP VIEW action.billable_circulations;
2298 CREATE OR REPLACE VIEW action.all_circulation AS
2299 SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
2300 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
2301 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
2302 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
2303 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
2304 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
2305 FROM action.aged_circulation
2307 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,
2308 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,
2309 cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
2310 circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
2311 circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
2312 circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
2314 FROM action.circulation circ
2315 JOIN asset.copy cp ON (circ.target_copy = cp.id)
2316 JOIN asset.call_number cn ON (cp.call_number = cn.id)
2317 JOIN actor.usr p ON (circ.usr = p.id)
2318 LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
2319 LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
2321 CREATE OR REPLACE VIEW action.open_circulation AS
2323 FROM action.circulation
2324 WHERE checkin_time IS NULL
2328 CREATE OR REPLACE VIEW action.billable_circulations AS
2330 FROM action.circulation
2331 WHERE xact_finish IS NULL;
2333 -- Drop Functions that rely on types
2335 DROP FUNCTION action.item_user_circ_test(INT, BIGINT, INT, BOOL);
2336 DROP FUNCTION action.item_user_circ_test(INT, BIGINT, INT);
2337 DROP FUNCTION action.item_user_renew_test(INT, BIGINT, INT);
2339 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$
2341 user_object actor.usr%ROWTYPE;
2342 standing_penalty config.standing_penalty%ROWTYPE;
2343 item_object asset.copy%ROWTYPE;
2344 item_status_object config.copy_status%ROWTYPE;
2345 item_location_object asset.copy_location%ROWTYPE;
2346 result action.circ_matrix_test_result;
2347 circ_test action.found_circ_matrix_matchpoint;
2348 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
2349 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
2350 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
2351 hold_ratio action.hold_stats%ROWTYPE;
2354 context_org_list INT[];
2357 -- Assume success unless we hit a failure condition
2358 result.success := TRUE;
2360 -- Fail if the user is BARRED
2361 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
2363 -- Fail if we couldn't find the user
2364 IF user_object.id IS NULL THEN
2365 result.fail_part := 'no_user';
2366 result.success := FALSE;
2372 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
2374 -- Fail if we couldn't find the item
2375 IF item_object.id IS NULL THEN
2376 result.fail_part := 'no_item';
2377 result.success := FALSE;
2383 IF user_object.barred IS TRUE THEN
2384 result.fail_part := 'actor.usr.barred';
2385 result.success := FALSE;
2390 -- Fail if the item can't circulate
2391 IF item_object.circulate IS FALSE THEN
2392 result.fail_part := 'asset.copy.circulate';
2393 result.success := FALSE;
2398 -- Fail if the item isn't in a circulateable status on a non-renewal
2399 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
2400 result.fail_part := 'asset.copy.status';
2401 result.success := FALSE;
2404 ELSIF renewal AND item_object.status <> 1 THEN
2405 result.fail_part := 'asset.copy.status';
2406 result.success := FALSE;
2411 -- Fail if the item can't circulate because of the shelving location
2412 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
2413 IF item_location_object.circulate IS FALSE THEN
2414 result.fail_part := 'asset.copy_location.circulate';
2415 result.success := FALSE;
2420 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
2422 circ_matchpoint := circ_test.matchpoint;
2423 result.matchpoint := circ_matchpoint.id;
2424 result.circulate := circ_matchpoint.circulate;
2425 result.duration_rule := circ_matchpoint.duration_rule;
2426 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
2427 result.max_fine_rule := circ_matchpoint.max_fine_rule;
2428 result.hard_due_date := circ_matchpoint.hard_due_date;
2429 result.renewals := circ_matchpoint.renewals;
2430 result.grace_period := circ_matchpoint.grace_period;
2431 result.buildrows := circ_test.buildrows;
2433 -- Fail if we couldn't find a matchpoint
2434 IF circ_test.success = false THEN
2435 result.fail_part := 'no_matchpoint';
2436 result.success := FALSE;
2439 RETURN; -- All tests after this point require a matchpoint. No sense in running on an incomplete or missing one.
2442 -- Apparently....use the circ matchpoint org unit to determine what org units are valid.
2443 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_matchpoint.org_unit );
2446 penalty_type = '%RENEW%';
2448 penalty_type = '%CIRC%';
2451 FOR standing_penalty IN
2452 SELECT DISTINCT csp.*
2453 FROM actor.usr_standing_penalty usp
2454 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
2455 WHERE usr = match_user
2456 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
2457 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
2458 AND csp.block_list LIKE penalty_type LOOP
2460 result.fail_part := standing_penalty.name;
2461 result.success := FALSE;
2466 -- Fail if the test is set to hard non-circulating
2467 IF circ_matchpoint.circulate IS FALSE THEN
2468 result.fail_part := 'config.circ_matrix_test.circulate';
2469 result.success := FALSE;
2474 -- Fail if the total copy-hold ratio is too low
2475 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
2476 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
2477 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
2478 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
2479 result.success := FALSE;
2485 -- Fail if the available copy-hold ratio is too low
2486 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
2487 IF hold_ratio.hold_count IS NULL THEN
2488 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
2490 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
2491 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
2492 result.success := FALSE;
2498 -- Fail if the user has too many items with specific circ_modifiers checked out
2499 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
2500 SELECT INTO items_out COUNT(*)
2501 FROM action.circulation circ
2502 JOIN asset.copy cp ON (cp.id = circ.target_copy)
2503 WHERE circ.usr = match_user
2504 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
2505 AND circ.checkin_time IS NULL
2506 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
2507 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);
2508 IF items_out >= out_by_circ_mod.items_out THEN
2509 result.fail_part := 'config.circ_matrix_circ_mod_test';
2510 result.success := FALSE;
2516 -- If we passed everything, return the successful matchpoint id
2523 $func$ LANGUAGE plpgsql;
2525 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
2526 SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
2527 $func$ LANGUAGE SQL;
2529 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
2530 SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
2531 $func$ LANGUAGE SQL;
2533 -- Update recurring fine rules
2534 UPDATE config.rule_recurring_fine SET grace_period=recurrence_interval;
2536 -- Update Circulation Data
2537 -- Only update if we were told to and the circ hasn't been checked in
2538 UPDATE action.circulation SET grace_period=fine_interval WHERE :CircGrace AND (checkin_time IS NULL);
2541 CREATE TABLE biblio.monograph_part (
2542 id SERIAL PRIMARY KEY,
2543 record BIGINT NOT NULL REFERENCES biblio.record_entry (id),
2544 label TEXT NOT NULL,
2545 label_sortkey TEXT NOT NULL,
2546 CONSTRAINT record_label_unique UNIQUE (record,label)
2549 CREATE OR REPLACE FUNCTION biblio.normalize_biblio_monograph_part_sortkey () RETURNS TRIGGER AS $$
2551 NEW.label_sortkey := REGEXP_REPLACE(
2552 evergreen.lpad_number_substrings(
2553 naco_normalize(NEW.label),
2563 $$ LANGUAGE PLPGSQL;
2565 CREATE TRIGGER norm_sort_label BEFORE INSERT OR UPDATE ON biblio.monograph_part FOR EACH ROW EXECUTE PROCEDURE biblio.normalize_biblio_monograph_part_sortkey();
2567 CREATE TABLE asset.copy_part_map (
2568 id SERIAL PRIMARY KEY,
2569 target_copy BIGINT NOT NULL, -- points o asset.copy
2570 part INT NOT NULL REFERENCES biblio.monograph_part (id) ON DELETE CASCADE
2572 CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part);
2574 CREATE TABLE asset.call_number_prefix (
2575 id SERIAL PRIMARY KEY,
2576 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
2577 label TEXT NOT NULL, -- i18n
2581 CREATE OR REPLACE FUNCTION asset.normalize_affix_sortkey () RETURNS TRIGGER AS $$
2583 NEW.label_sortkey := REGEXP_REPLACE(
2584 evergreen.lpad_number_substrings(
2585 naco_normalize(NEW.label),
2595 $$ LANGUAGE PLPGSQL;
2597 CREATE TRIGGER prefix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_prefix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
2598 CREATE UNIQUE INDEX asset_call_number_prefix_once_per_lib ON asset.call_number_prefix (label, owning_lib);
2599 CREATE INDEX asset_call_number_prefix_sortkey_idx ON asset.call_number_prefix (label_sortkey);
2601 CREATE TABLE asset.call_number_suffix (
2602 id SERIAL PRIMARY KEY,
2603 owning_lib INT NOT NULL REFERENCES actor.org_unit (id),
2604 label TEXT NOT NULL, -- i18n
2607 CREATE TRIGGER suffix_normalize_tgr BEFORE INSERT OR UPDATE ON asset.call_number_suffix FOR EACH ROW EXECUTE PROCEDURE asset.normalize_affix_sortkey();
2608 CREATE UNIQUE INDEX asset_call_number_suffix_once_per_lib ON asset.call_number_suffix (label, owning_lib);
2609 CREATE INDEX asset_call_number_suffix_sortkey_idx ON asset.call_number_suffix (label_sortkey);
2611 INSERT INTO asset.call_number_suffix (id, owning_lib, label) VALUES (-1, 1, '');
2612 INSERT INTO asset.call_number_prefix (id, owning_lib, label) VALUES (-1, 1, '');
2614 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
2616 ALTER TABLE asset.call_number
2617 ADD COLUMN prefix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_prefix(id) DEFERRABLE INITIALLY DEFERRED,
2618 ADD COLUMN suffix INT NOT NULL DEFAULT -1 REFERENCES asset.call_number_suffix(id) DEFERRABLE INITIALLY DEFERRED;
2620 ALTER TABLE auditor.asset_call_number_history
2621 ADD COLUMN prefix INT NOT NULL DEFAULT -1,
2622 ADD COLUMN suffix INT NOT NULL DEFAULT -1;
2624 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;
2626 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2627 'ui.cat.volume_copy_editor.horizontal',
2629 'ui.cat.volume_copy_editor.horizontal',
2630 'GUI: Horizontal layout for Volume/Copy Creator/Editor.',
2633 'ui.cat.volume_copy_editor.horizontal',
2634 '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.',
2635 'coust', 'description'),
2642 ALTER FUNCTION actor.org_unit_descendants( INT, INT ) ROWS 1;
2643 ALTER FUNCTION actor.org_unit_descendants( INT ) ROWS 1;
2644 ALTER FUNCTION actor.org_unit_descendants_distance( INT ) ROWS 1;
2645 ALTER FUNCTION actor.org_unit_ancestors( INT ) ROWS 1;
2646 ALTER FUNCTION actor.org_unit_ancestors_distance( INT ) ROWS 1;
2647 ALTER FUNCTION actor.org_unit_full_path ( INT ) ROWS 2;
2648 ALTER FUNCTION actor.org_unit_full_path ( INT, INT ) ROWS 2;
2649 ALTER FUNCTION actor.org_unit_combined_ancestors ( INT, INT ) ROWS 1;
2650 ALTER FUNCTION actor.org_unit_common_ancestors ( INT, INT ) ROWS 1;
2651 ALTER FUNCTION actor.org_unit_ancestor_setting( TEXT, INT ) ROWS 1;
2652 ALTER FUNCTION permission.grp_ancestors ( INT ) ROWS 1;
2653 ALTER FUNCTION permission.grp_ancestors_distance( INT ) ROWS 1;
2654 ALTER FUNCTION permission.grp_descendants_distance( INT ) ROWS 1;
2655 ALTER FUNCTION permission.usr_perms ( INT ) ROWS 10;
2656 ALTER FUNCTION permission.usr_has_perm_at_nd ( INT, TEXT) ROWS 1;
2657 ALTER FUNCTION permission.usr_has_perm_at_all_nd ( INT, TEXT ) ROWS 1;
2658 ALTER FUNCTION permission.usr_has_perm_at ( INT, TEXT ) ROWS 1;
2659 ALTER FUNCTION permission.usr_has_perm_at_all ( INT, TEXT ) ROWS 1;
2662 DROP TRIGGER IF EXISTS facet_force_nfc_tgr ON metabib.facet_entry;
2663 CREATE TRIGGER facet_force_nfc_tgr
2664 BEFORE UPDATE OR INSERT ON metabib.facet_entry
2665 FOR EACH ROW EXECUTE PROCEDURE evergreen.facet_force_nfc();
2667 DROP FUNCTION IF EXISTS public.force_unicode_normal_form (TEXT,TEXT);
2668 DROP FUNCTION IF EXISTS public.facet_force_nfc ();
2670 DROP TRIGGER b_maintain_901 ON biblio.record_entry;
2671 DROP TRIGGER b_maintain_901 ON authority.record_entry;
2672 DROP TRIGGER b_maintain_901 ON serial.record_entry;
2674 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2675 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2676 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
2678 DROP FUNCTION IF EXISTS public.maintain_901 ();
2680 ------ Backporting note: 2.1+ only beyond here --------
2682 CREATE SCHEMA unapi;
2684 CREATE TABLE unapi.bre_output_layout (
2685 name TEXT PRIMARY KEY,
2686 transform TEXT REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
2687 mime_type TEXT NOT NULL,
2688 feed_top TEXT NOT NULL,
2689 holdings_element TEXT,
2691 description_element TEXT,
2692 creator_element TEXT,
2693 update_ts_element TEXT
2696 INSERT INTO unapi.bre_output_layout
2697 (name, transform, mime_type, holdings_element, feed_top, title_element, description_element, creator_element, update_ts_element)
2699 ('holdings_xml', NULL, 'application/xml', NULL, 'hxml', NULL, NULL, NULL, NULL),
2700 ('marcxml', 'marcxml', 'application/marc+xml', 'record', 'collection', NULL, NULL, NULL, NULL),
2701 ('mods32', 'mods32', 'application/mods+xml', 'mods', 'modsCollection', NULL, NULL, NULL, NULL)
2704 -- Dummy functions, so we can create the real ones out of order
2705 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;
2706 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;
2707 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;
2708 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;
2709 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;
2710 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;
2711 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;
2712 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;
2713 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;
2714 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;
2715 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;
2716 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;
2717 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;
2718 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;
2719 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;
2720 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;
2721 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;
2722 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;
2723 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;
2724 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;
2725 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;
2727 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;
2728 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;
2730 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$
2736 'id' || COALESCE(obj_id::TEXT,'') ||
2737 'format' || COALESCE(format::TEXT,'') ||
2738 'ename' || COALESCE(ename::TEXT,'') ||
2739 'includes' || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
2740 'org' || COALESCE(org::TEXT,'') ||
2741 'depth' || COALESCE(depth::TEXT,'') ||
2742 'slimit' || COALESCE(slimit::TEXT,'') ||
2743 'soffset' || COALESCE(soffset::TEXT,'') ||
2744 'include_xmlns' || COALESCE(include_xmlns::TEXT,'');
2745 -- RAISE NOTICE 'memoize key: %', key;
2748 -- RAISE NOTICE 'memoize hash: %', key;
2750 -- XXX cache logic ... memcached? table?
2752 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;
2755 $F$ LANGUAGE PLPGSQL;
2757 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$
2759 layout unapi.bre_output_layout%ROWTYPE;
2760 transform config.xml_transform%ROWTYPE;
2763 xmlns_uri TEXT := 'http://open-ils.org/spec/feed-xml/v1';
2765 element_list TEXT[];
2768 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
2769 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
2771 IF layout.name IS NULL THEN
2775 SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
2776 xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
2778 -- Gather the bib xml
2779 SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
2781 IF layout.title_element IS NOT NULL THEN
2782 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;
2785 IF layout.description_element IS NOT NULL THEN
2786 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;
2789 IF layout.creator_element IS NOT NULL THEN
2790 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;
2793 IF layout.update_ts_element IS NOT NULL THEN
2794 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;
2797 IF unapi_url IS NOT NULL THEN
2798 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;
2801 IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
2803 element_list := regexp_split_to_array(layout.feed_top,E'\\.');
2804 FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
2805 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;
2808 RETURN tmp_xml::XML;
2810 $F$ LANGUAGE PLPGSQL;
2812 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$
2814 me biblio.record_entry%ROWTYPE;
2815 layout unapi.bre_output_layout%ROWTYPE;
2816 xfrm config.xml_transform%ROWTYPE;
2824 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
2826 IF ouid IS NULL THEN
2830 IF format = 'holdings_xml' THEN -- the special case
2831 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
2835 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
2837 IF layout.name IS NULL THEN
2841 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
2843 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
2845 -- grab hodlings if we need them
2846 IF ('holdings_xml' = ANY (includes)) THEN
2847 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
2853 -- generate our item node
2856 IF format = 'marcxml' THEN
2858 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
2859 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
2862 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
2865 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
2867 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
2868 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
2871 IF ('bre.unapi' = ANY (includes)) THEN
2872 output := REGEXP_REPLACE(
2874 '</' || top_el || '>(.*?)',
2878 'http://www.w3.org/1999/xhtml' AS xmlns,
2879 'unapi-id' AS class,
2880 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
2882 )::TEXT || '</' || top_el || E'>\\1'
2890 $F$ LANGUAGE PLPGSQL;
2892 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$
2896 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2897 CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
2901 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
2904 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
2906 FROM asset.opac_ou_record_copy_count($2, $1)
2910 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
2912 FROM asset.staff_ou_record_copy_count($2, $1)
2917 WHEN ('bmp' = ANY ($5)) THEN
2918 XMLELEMENT( name monograph_parts,
2919 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))
2923 CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
2926 (SELECT XMLAGG(acn) FROM (
2927 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)
2928 FROM asset.call_number acn
2929 WHERE acn.record = $1
2933 JOIN actor.org_unit_descendants(
2938 FROM actor.org_unit_type aout
2939 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
2942 ) aoud ON (acp.circ_lib = aoud.id)
2945 ORDER BY label_sortkey
2951 CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
2954 (SELECT XMLAGG(ssub) FROM (
2955 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
2956 FROM serial.subscription
2957 WHERE record_entry = $1
2964 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$
2968 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2969 'tag:open-ils.org:U2@ssub/' || id AS id,
2970 start_date AS start, end_date AS end, expected_date_offset
2972 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
2973 XMLELEMENT( name distributions,
2975 WHEN ('sdist' = ANY ($4)) THEN
2976 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))
2981 FROM serial.subscription ssub
2983 GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
2986 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$
2990 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
2991 'tag:open-ils.org:U2@sdist/' || id AS id,
2992 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
2993 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
2994 unit_label_prefix, label, unit_label_suffix, summary_method
2996 unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
2997 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,
2998 XMLELEMENT( name streams,
3000 WHEN ('sstr' = ANY ($4)) THEN
3001 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))
3005 XMLELEMENT( name summaries,
3007 WHEN ('ssum' = ANY ($4)) THEN
3008 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))
3012 WHEN ('ssum' = ANY ($4)) THEN
3013 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))
3017 WHEN ('ssum' = ANY ($4)) THEN
3018 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))
3023 FROM serial.distribution sdist
3025 GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
3028 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$
3032 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3033 'tag:open-ils.org:U2@sstr/' || id AS id,
3036 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,
3037 XMLELEMENT( name items,
3039 WHEN ('sitem' = ANY ($4)) THEN
3040 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))
3045 FROM serial.stream sstr
3047 GROUP BY id, routing_label, distribution;
3050 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$
3054 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3055 'tag:open-ils.org:U2@siss/' || id AS id,
3056 create_date, edit_date, label, date_published,
3057 holding_code, holding_type, holding_link_id
3059 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,
3060 XMLELEMENT( name items,
3062 WHEN ('sitem' = ANY ($4)) THEN
3063 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))
3068 FROM serial.issuance sstr
3070 GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
3073 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$
3077 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3078 'tag:open-ils.org:U2@sitem/' || id AS id,
3079 'tag:open-ils.org:U2@siss/' || issuance AS issuance,
3080 date_expected, date_received
3082 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,
3083 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,
3084 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,
3085 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
3086 -- XMLELEMENT( name notes,
3088 -- WHEN ('acpn' = ANY ($4)) THEN
3089 -- 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))
3094 FROM serial.item sitem
3099 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$
3101 name serial_summary,
3103 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3104 'tag:open-ils.org:U2@sbsum/' || id AS id,
3105 'sssum' AS type, generated_coverage, textual_holdings, show_generated
3107 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
3109 FROM serial.supplement_summary ssum
3111 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3114 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$
3116 name serial_summary,
3118 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3119 'tag:open-ils.org:U2@sbsum/' || id AS id,
3120 'sbsum' AS type, generated_coverage, textual_holdings, show_generated
3122 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
3124 FROM serial.basic_summary ssum
3126 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3129 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$
3131 name serial_summary,
3133 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3134 'tag:open-ils.org:U2@sbsum/' || id AS id,
3135 'sisum' AS type, generated_coverage, textual_holdings, show_generated
3137 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
3139 FROM serial.index_summary ssum
3141 GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
3145 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$
3149 IF ename = 'circlib' THEN
3153 'http://open-ils.org/spec/actors/v1' AS xmlns,
3158 FROM actor.org_unit aou
3161 EXECUTE $$SELECT XMLELEMENT(
3162 name $$ || ename || $$,
3164 'http://open-ils.org/spec/actors/v1' AS xmlns,
3165 'tag:open-ils.org:U2@aou/' || id AS id,
3166 shortname, name, opac_visible
3169 FROM actor.org_unit aou
3170 WHERE id = $1 $$ INTO output USING obj_id;
3176 $F$ LANGUAGE PLPGSQL;
3178 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$
3182 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3187 FROM asset.copy_location
3191 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$
3195 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3200 FROM config.copy_status
3204 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$
3208 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3209 create_date AS date,
3214 FROM asset.copy_note
3218 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$
3222 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3228 FROM asset.stat_cat_entry asce
3229 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
3233 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$
3235 name monograph_part,
3237 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3238 'tag:open-ils.org:U2@bmp/' || id AS id,
3242 'tag:open-ils.org:U2@bre/' || record AS record
3245 WHEN ('acp' = ANY ($4)) THEN
3246 XMLELEMENT( name copies,
3247 (SELECT XMLAGG(acp) FROM (
3248 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
3250 JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
3252 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
3259 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
3261 FROM biblio.monograph_part
3263 GROUP BY id, label, label_sortkey, record;
3266 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$
3270 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3271 'tag:open-ils.org:U2@acp/' || id AS id,
3272 create_date, edit_date, copy_number, circulate, deposit,
3273 ref, holdable, deleted, deposit_amount, price, barcode,
3274 circ_modifier, circ_as_type, opac_visible
3276 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3277 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3278 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3279 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3280 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,
3281 XMLELEMENT( name copy_notes,
3283 WHEN ('acpn' = ANY ($4)) THEN
3284 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))
3288 XMLELEMENT( name statcats,
3290 WHEN ('ascecm' = ANY ($4)) THEN
3291 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))
3296 WHEN ('bmp' = ANY ($4)) THEN
3297 XMLELEMENT( name monograph_parts,
3298 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))
3305 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;
3308 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$
3312 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3313 'tag:open-ils.org:U2@acp/' || id AS id,
3314 create_date, edit_date, copy_number, circulate, deposit,
3315 ref, holdable, deleted, deposit_amount, price, barcode,
3316 circ_modifier, circ_as_type, opac_visible, status_changed_time,
3317 floating, mint_condition, detailed_contents, sort_key, summary_contents, cost
3319 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),
3320 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),
3321 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),
3322 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),
3323 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,
3324 XMLELEMENT( name copy_notes,
3326 WHEN ('acpn' = ANY ($4)) THEN
3327 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))
3331 XMLELEMENT( name statcats,
3333 WHEN ('ascecm' = ANY ($4)) THEN
3334 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))
3341 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
3342 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;
3345 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$
3349 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3350 'tag:open-ils.org:U2@acn/' || acn.id AS id,
3352 o.opac_visible AS opac_visible,
3353 deleted, label, label_sortkey, label_class, record
3355 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
3356 XMLELEMENT( name copies,
3358 WHEN ('acp' = ANY ($4)) THEN
3359 (SELECT XMLAGG(acp) FROM (
3360 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE)
3362 JOIN actor.org_unit_descendants(
3363 (SELECT id FROM actor.org_unit WHERE shortname = $5),
3364 (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))))
3365 ) aoud ON (cp.circ_lib = aoud.id)
3366 WHERE cp.call_number = acn.id
3367 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
3376 (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)
3378 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,
3379 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,
3380 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
3382 FROM asset.call_number acn
3383 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
3385 GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
3388 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$
3390 name call_number_prefix,
3392 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3397 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acnp'), $5, $6, $7, $8)
3399 FROM asset.call_number_prefix
3403 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$
3405 name call_number_suffix,
3407 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3412 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acns'), $5, $6, $7, $8)
3414 FROM asset.call_number_suffix
3418 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$
3422 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3423 'tag:open-ils.org:U2@auri/' || uri.id AS id,
3428 XMLELEMENT( name copies,
3430 WHEN ('acn' = ANY ($4)) THEN
3431 (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)
3438 GROUP BY uri.id, use_restriction, href, label;
3441 DROP FUNCTION IF EXISTS public.array_remove_item_by_value(ANYARRAY,ANYELEMENT);
3443 DROP FUNCTION IF EXISTS public.lpad_number_substrings(TEXT,TEXT,INT);
3447 CREATE OR REPLACE FUNCTION evergreen.fake_fkey_tgr () RETURNS TRIGGER AS $F$
3451 EXECUTE 'SELECT ($1).' || quote_ident(TG_ARGV[0]) INTO copy_id USING NEW;
3452 PERFORM * FROM asset.copy WHERE id = copy_id;
3454 RAISE EXCEPTION 'Key (%.%=%) does not exist in asset.copy', TG_TABLE_SCHEMA, TG_TABLE_NAME, copy_id;
3458 $F$ LANGUAGE PLPGSQL;
3460 DROP TRIGGER IF EXISTS action_circulation_target_copy_trig ON action.circulation;
3461 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');
3464 CREATE TABLE biblio.peer_type (
3465 id SERIAL PRIMARY KEY,
3466 name TEXT NOT NULL UNIQUE -- i18n
3469 CREATE TABLE biblio.peer_bib_copy_map (
3470 id SERIAL PRIMARY KEY,
3471 peer_type INT NOT NULL REFERENCES biblio.peer_type (id),
3472 peer_record BIGINT NOT NULL REFERENCES biblio.record_entry (id),
3473 target_copy BIGINT NOT NULL -- can't use fkey because of acp subtables
3475 CREATE INDEX peer_bib_copy_map_record_idx ON biblio.peer_bib_copy_map (peer_record);
3476 CREATE INDEX peer_bib_copy_map_copy_idx ON biblio.peer_bib_copy_map (target_copy);
3478 DROP TABLE asset.opac_visible_copies;
3479 CREATE TABLE asset.opac_visible_copies (
3480 id BIGSERIAL primary key,
3486 INSERT INTO biblio.peer_type (id,name) VALUES
3487 (1,oils_i18n_gettext(1,'Bound Volume','bpt','name')),
3488 (2,oils_i18n_gettext(2,'Bilingual','bpt','name')),
3489 (3,oils_i18n_gettext(3,'Back-to-back','bpt','name')),
3490 (4,oils_i18n_gettext(4,'Set','bpt','name')),
3491 (5,oils_i18n_gettext(5,'e-Reader Preload','bpt','name'));
3493 SELECT SETVAL('biblio.peer_type_id_seq'::TEXT, 100);
3495 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$
3499 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3500 CASE WHEN ('bre' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
3504 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
3507 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
3509 FROM asset.opac_ou_record_copy_count($2, $1)
3513 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
3515 FROM asset.staff_ou_record_copy_count($2, $1)
3520 WHEN ('bmp' = ANY ($5)) THEN
3521 XMLELEMENT( name monograph_parts,
3522 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))
3526 CASE WHEN ('acn' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
3529 (SELECT XMLAGG(acn) FROM (
3530 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)
3531 FROM asset.call_number acn
3532 WHERE acn.record = $1
3536 JOIN actor.org_unit_descendants(
3541 FROM actor.org_unit_type aout
3542 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
3545 ) aoud ON (acp.circ_lib = aoud.id)
3548 ORDER BY label_sortkey
3554 CASE WHEN ('ssub' = ANY ('{acn,auri}'::TEXT[] || $5)) THEN
3557 (SELECT XMLAGG(ssub) FROM (
3558 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
3559 FROM serial.subscription
3560 WHERE record_entry = $1
3564 CASE WHEN ('acp' = ANY ($5)) THEN
3566 name foreign_copies,
3567 (SELECT XMLAGG(acp) FROM (
3568 SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
3569 FROM biblio.peer_bib_copy_map p
3570 JOIN asset.copy c ON (p.target_copy = c.id)
3571 WHERE NOT c.deleted AND peer_record = $1
3578 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$
3582 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
3583 'tag:open-ils.org:U2@acp/' || id AS id,
3584 create_date, edit_date, copy_number, circulate, deposit,
3585 ref, holdable, deleted, deposit_amount, price, barcode,
3586 circ_modifier, circ_as_type, opac_visible
3588 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3589 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
3590 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3591 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
3592 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,
3593 XMLELEMENT( name copy_notes,
3595 WHEN ('acpn' = ANY ($4)) THEN
3596 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))
3600 XMLELEMENT( name statcats,
3602 WHEN ('ascecm' = ANY ($4)) THEN
3603 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))
3607 XMLELEMENT( name foreign_records,
3609 WHEN ('bre' = ANY ($4)) THEN
3610 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))
3616 WHEN ('bmp' = ANY ($4)) THEN
3617 XMLELEMENT( name monograph_parts,
3618 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))
3625 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;
3628 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
3630 TRUNCATE TABLE asset.opac_visible_copies;
3632 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
3633 SELECT cp.id, cp.circ_lib, cn.record
3635 JOIN asset.call_number cn ON (cn.id = cp.call_number)
3636 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3637 JOIN asset.copy_location cl ON (cp.location = cl.id)
3638 JOIN config.copy_status cs ON (cp.status = cs.id)
3639 JOIN biblio.record_entry b ON (cn.record = b.id)
3640 WHERE NOT cp.deleted
3648 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record
3650 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
3651 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3652 JOIN asset.copy_location cl ON (cp.location = cl.id)
3653 JOIN config.copy_status cs ON (cp.status = cs.id)
3654 WHERE NOT cp.deleted
3661 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
3662 Rebuild the copy OPAC visibility cache. Useful during migrations.
3665 SELECT asset.refresh_opac_visible_copies_mat_view();
3666 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
3667 CREATE INDEX opac_visible_copies_copy_id_idx on asset.opac_visible_copies (copy_id);
3668 CREATE UNIQUE INDEX opac_visible_copies_once_per_record_idx on asset.opac_visible_copies (copy_id, record);
3670 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
3674 do_add BOOLEAN := false;
3675 do_remove BOOLEAN := false;
3678 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
3679 SELECT id, circ_lib, record FROM (
3680 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number
3682 JOIN asset.call_number cn ON (cn.id = cp.call_number)
3683 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3684 JOIN asset.copy_location cl ON (cp.location = cl.id)
3685 JOIN config.copy_status cs ON (cp.status = cs.id)
3686 JOIN biblio.record_entry b ON (cn.record = b.id)
3687 WHERE NOT cp.deleted
3695 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number
3697 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
3698 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
3699 JOIN asset.copy_location cl ON (cp.location = cl.id)
3700 JOIN config.copy_status cs ON (cp.status = cs.id)
3701 WHERE NOT cp.deleted
3710 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
3712 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
3713 IF TG_OP = 'INSERT' THEN
3714 add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';';
3718 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
3719 EXECUTE remove_query;
3724 IF TG_OP = 'INSERT' THEN
3726 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
3727 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3735 -- handle items first, since with circulation activity
3736 -- their statuses change frequently
3737 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
3739 IF OLD.location <> NEW.location OR
3740 OLD.call_number <> NEW.call_number OR
3741 OLD.status <> NEW.status OR
3742 OLD.circ_lib <> NEW.circ_lib THEN
3743 -- any of these could change visibility, but
3744 -- we'll save some queries and not try to calculate
3745 -- the change directly
3750 IF OLD.deleted <> NEW.deleted THEN
3758 IF OLD.opac_visible <> NEW.opac_visible THEN
3759 IF OLD.opac_visible THEN
3761 ELSIF NOT do_remove THEN -- handle edge case where deleted item
3762 -- is also marked opac_visible
3770 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
3773 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3781 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
3783 IF OLD.deleted AND NEW.deleted THEN -- do nothing
3787 ELSIF NEW.deleted THEN -- remove rows
3789 IF TG_TABLE_NAME = 'call_number' THEN
3790 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
3791 ELSIF TG_TABLE_NAME = 'record_entry' THEN
3792 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
3797 ELSIF OLD.deleted THEN -- add rows
3799 IF TG_TABLE_NAME IN ('copy','unit') THEN
3800 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
3801 ELSIF TG_TABLE_NAME = 'call_number' THEN
3802 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
3803 ELSIF TG_TABLE_NAME = 'record_entry' THEN
3804 add_query := add_query || 'WHERE x.record = ' || NEW.id || ';';
3814 IF TG_TABLE_NAME = 'call_number' THEN
3816 IF OLD.record <> NEW.record THEN
3817 -- call number is linked to different bib
3818 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
3819 EXECUTE remove_query;
3820 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
3828 IF TG_TABLE_NAME IN ('record_entry') THEN
3829 RETURN NEW; -- don't have 'opac_visible'
3832 -- actor.org_unit, asset.copy_location, asset.copy_status
3833 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
3837 ELSIF NEW.opac_visible THEN -- add rows
3839 IF TG_TABLE_NAME = 'org_unit' THEN
3840 add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
3841 ELSIF TG_TABLE_NAME = 'copy_location' THEN
3842 add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
3843 ELSIF TG_TABLE_NAME = 'copy_status' THEN
3844 add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
3851 IF TG_TABLE_NAME = 'org_unit' THEN
3852 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
3853 ELSIF TG_TABLE_NAME = 'copy_location' THEN
3854 remove_query := remove_query || 'location = ' || NEW.id || ');';
3855 ELSIF TG_TABLE_NAME = 'copy_status' THEN
3856 remove_query := remove_query || 'status = ' || NEW.id || ');';
3859 EXECUTE remove_query;
3865 $func$ LANGUAGE PLPGSQL;
3866 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
3867 Trigger function to update the copy OPAC visiblity cache.
3870 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();
3872 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
3874 transformed_xml TEXT;
3877 xfrm config.xml_transform%ROWTYPE;
3879 new_attrs HSTORE := ''::HSTORE;
3880 attr_def config.record_attr_definition%ROWTYPE;
3883 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
3884 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
3885 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
3886 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
3887 DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
3888 RETURN NEW; -- and we're done
3891 IF TG_OP = 'UPDATE' THEN -- re-ingest?
3892 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
3894 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
3899 -- Record authority linking
3900 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
3902 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
3905 -- Flatten and insert the mfr data
3906 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
3908 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
3910 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
3911 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
3913 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
3915 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
3916 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
3917 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
3918 WHERE record = NEW.id
3919 AND tag LIKE attr_def.tag
3921 WHEN attr_def.sf_list IS NOT NULL
3922 THEN POSITION(subfield IN attr_def.sf_list) > 0
3929 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
3930 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
3932 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
3934 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
3936 -- See if we can skip the XSLT ... it's expensive
3937 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
3938 -- Can't skip the transform
3939 IF xfrm.xslt <> '---' THEN
3940 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
3942 transformed_xml := NEW.marc;
3945 prev_xfrm := xfrm.name;
3948 IF xfrm.name IS NULL THEN
3949 -- just grab the marcxml (empty) transform
3950 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
3951 prev_xfrm := xfrm.name;
3954 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
3956 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
3957 SELECT value::TEXT INTO attr_value
3958 FROM biblio.marc21_physical_characteristics(NEW.id)
3959 WHERE subfield = attr_def.phys_char_sf
3960 LIMIT 1; -- Just in case ...
3964 -- apply index normalizers to attr_value
3966 SELECT n.func AS func,
3967 n.param_count AS param_count,
3969 FROM config.index_normalizer n
3970 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
3971 WHERE attr = attr_def.name
3973 EXECUTE 'SELECT ' || normalizer.func || '(' ||
3974 quote_literal( attr_value ) ||
3976 WHEN normalizer.param_count > 0
3977 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
3980 ')' INTO attr_value;
3984 -- Add the new value to the hstore
3985 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
3989 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
3990 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
3992 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
3998 -- Gather and insert the field entry data
3999 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
4001 -- Located URI magic
4002 IF TG_OP = 'INSERT' THEN
4003 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4005 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4008 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4010 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4014 -- (re)map metarecord-bib linking
4015 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
4016 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
4018 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4020 ELSE -- we're doing an update, and we're not deleted, remap
4021 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
4023 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4029 $func$ LANGUAGE PLPGSQL;
4032 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$
4036 CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
4037 'tag:open-ils.org:U2@mra/' || mra.id AS id,
4038 'tag:open-ils.org:U2@bre/' || mra.id AS record
4040 (SELECT XMLAGG(foo.y)
4041 FROM (SELECT XMLELEMENT(
4045 cvm.value AS "coded-value",
4051 FROM EACH(mra.attrs) AS x
4052 JOIN config.record_attr_definition rad ON (x.key = rad.name)
4053 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
4057 FROM metabib.record_attr mra
4061 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$
4063 me biblio.record_entry%ROWTYPE;
4064 layout unapi.bre_output_layout%ROWTYPE;
4065 xfrm config.xml_transform%ROWTYPE;
4074 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
4076 IF ouid IS NULL THEN
4080 IF format = 'holdings_xml' THEN -- the special case
4081 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
4085 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
4087 IF layout.name IS NULL THEN
4091 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
4093 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
4095 -- grab SVF if we need them
4096 IF ('mra' = ANY (includes)) THEN
4097 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
4102 -- grab hodlings if we need them
4103 IF ('holdings_xml' = ANY (includes)) THEN
4104 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
4110 -- generate our item node
4113 IF format = 'marcxml' THEN
4115 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
4116 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
4119 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
4122 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
4124 IF axml IS NOT NULL THEN
4125 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
4128 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
4129 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
4132 IF ('bre.unapi' = ANY (includes)) THEN
4133 output := REGEXP_REPLACE(
4135 '</' || top_el || '>(.*?)',
4139 'http://www.w3.org/1999/xhtml' AS xmlns,
4140 'unapi-id' AS class,
4141 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
4143 )::TEXT || '</' || top_el || E'>\\1'
4151 $F$ LANGUAGE PLPGSQL;
4155 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$
4157 me biblio.record_entry%ROWTYPE;
4158 layout unapi.bre_output_layout%ROWTYPE;
4159 xfrm config.xml_transform%ROWTYPE;
4168 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
4170 IF ouid IS NULL THEN
4174 IF format = 'holdings_xml' THEN -- the special case
4175 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
4179 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
4181 IF layout.name IS NULL THEN
4185 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
4187 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
4189 -- grab SVF if we need them
4190 IF ('mra' = ANY (includes)) THEN
4191 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
4196 -- grab hodlings if we need them
4197 IF ('holdings_xml' = ANY (includes)) THEN
4198 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
4204 -- generate our item node
4207 IF format = 'marcxml' THEN
4209 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
4210 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
4213 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
4216 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
4218 IF axml IS NOT NULL THEN
4219 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
4222 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
4223 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
4226 IF ('bre.unapi' = ANY (includes)) THEN
4227 output := REGEXP_REPLACE(
4229 '</' || top_el || '>(.*?)',
4233 'http://www.w3.org/1999/xhtml' AS xmlns,
4234 'unapi-id' AS class,
4235 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
4237 )::TEXT || '</' || top_el || E'>\\1'
4243 output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
4246 $F$ LANGUAGE PLPGSQL;
4251 CREATE OR REPLACE FUNCTION public.extract_acq_marc_field ( BIGINT, TEXT, TEXT) RETURNS TEXT AS $$
4252 SELECT extract_marc_field('acq.lineitem', $1, $2, $3);
4256 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
4263 rtype := (vandelay.marc21_record_type( marc )).code;
4264 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
4265 IF ff_pos.tag = 'ldr' THEN
4266 val := oils_xpath_string('//*[local-name()="leader"]', marc);
4267 IF val IS NOT NULL THEN
4268 val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
4272 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
4273 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
4277 val := REPEAT( ff_pos.default_val, ff_pos.length );
4283 $func$ LANGUAGE PLPGSQL;
4287 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
4292 output biblio.record_ff_map%ROWTYPE;
4294 rtype := (vandelay.marc21_record_type( marc )).code;
4296 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
4297 output.ff_name := ff_pos.fixed_field;
4298 output.ff_value := NULL;
4300 IF ff_pos.tag = 'ldr' THEN
4301 output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
4302 IF output.ff_value IS NOT NULL THEN
4303 output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
4305 output.ff_value := NULL;
4308 FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
4309 output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
4310 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
4312 output.ff_value := NULL;
4320 $func$ LANGUAGE PLPGSQL;
4322 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
4329 uri_owner_list TEXT[];
4337 -- Clear any URI mappings and call numbers for this bib.
4338 -- This leads to acn / auricnm inflation, but also enables
4339 -- old acn/auricnm's to go away and for bibs to be deleted.
4340 FOR uri_cn_id IN SELECT id FROM asset.call_number WHERE record = bib_id AND label = '##URI##' AND NOT deleted LOOP
4341 DELETE FROM asset.uri_call_number_map WHERE call_number = uri_cn_id;
4342 DELETE FROM asset.call_number WHERE id = uri_cn_id;
4345 uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
4346 IF ARRAY_UPPER(uris,1) > 0 THEN
4347 FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
4348 -- First we pull info out of the 856
4351 uri_href := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
4352 uri_label := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()',uri_xml))[1];
4353 uri_use := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
4355 IF uri_label IS NULL THEN
4356 uri_label := uri_href;
4358 CONTINUE WHEN uri_href IS NULL;
4360 -- Get the distinct list of libraries wanting to use
4362 DISTINCT REGEXP_REPLACE(
4364 $re$^.*?\((\w+)\).*$$re$,
4367 ) INTO uri_owner_list
4370 '//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',
4375 IF ARRAY_UPPER(uri_owner_list,1) > 0 THEN
4377 -- look for a matching uri
4378 IF uri_use IS NULL THEN
4379 SELECT id INTO uri_id
4381 WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active
4382 ORDER BY id LIMIT 1;
4383 IF NOT FOUND THEN -- create one
4384 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
4385 SELECT id INTO uri_id
4387 WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active;
4390 SELECT id INTO uri_id
4392 WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active
4393 ORDER BY id LIMIT 1;
4394 IF NOT FOUND THEN -- create one
4395 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
4396 SELECT id INTO uri_id
4398 WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
4402 FOR j IN 1 .. ARRAY_UPPER(uri_owner_list, 1) LOOP
4403 uri_owner := uri_owner_list[j];
4405 SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
4406 CONTINUE WHEN NOT FOUND;
4408 -- we need a call number to link through
4409 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;
4411 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
4412 VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
4413 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;
4416 -- now, link them if they're not already
4417 SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
4419 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
4431 $func$ LANGUAGE PLPGSQL;
4434 UPDATE config.org_unit_setting_type SET datatype = 'string' WHERE name = 'ui.general.button_bar';
4436 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');
4438 UPDATE actor.org_unit_setting SET value='"circ"' WHERE name = 'ui.general.button_bar' AND value='true';
4440 UPDATE actor.org_unit_setting SET value='"none"' WHERE name = 'ui.general.button_bar' AND value='false';
4444 INSERT into config.org_unit_setting_type
4445 ( name, label, description, datatype, fm_class ) VALUES
4446 ( 'cat.default_copy_status_fast',
4447 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Cataloging: Default copy status (fast add)', 'coust', 'label'),
4448 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Default status when a copy is created using the "Fast Add" interface.', 'coust', 'description'),
4452 INSERT into config.org_unit_setting_type
4453 ( name, label, description, datatype, fm_class ) VALUES
4454 ( 'cat.default_copy_status_normal',
4455 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Cataloging: Default copy status (normal)', 'coust', 'label'),
4456 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Default status when a copy is created using the normal volume/copy creator interface.', 'coust', 'description'),
4461 INSERT into config.org_unit_setting_type
4462 ( name, label, description, datatype ) VALUES
4463 ( 'ui.unified_volume_copy_editor',
4464 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'GUI: Unified Volume/Item Creator/Editor', 'coust', 'label'),
4465 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'If true combines the Volume/Copy Creator and Item Attribute Editor in some instances.', 'coust', 'description'),
4470 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
4472 transformed_xml TEXT;
4475 xfrm config.xml_transform%ROWTYPE;
4477 new_attrs HSTORE := ''::HSTORE;
4478 attr_def config.record_attr_definition%ROWTYPE;
4481 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
4482 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
4483 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
4484 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
4485 DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
4486 RETURN NEW; -- and we're done
4489 IF TG_OP = 'UPDATE' THEN -- re-ingest?
4490 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
4492 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
4497 -- Record authority linking
4498 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
4500 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
4503 -- Flatten and insert the mfr data
4504 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
4506 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
4508 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
4509 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
4511 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
4513 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
4514 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
4515 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
4516 WHERE record = NEW.id
4517 AND tag LIKE attr_def.tag
4519 WHEN attr_def.sf_list IS NOT NULL
4520 THEN POSITION(subfield IN attr_def.sf_list) > 0
4527 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
4528 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
4530 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
4532 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
4534 -- See if we can skip the XSLT ... it's expensive
4535 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
4536 -- Can't skip the transform
4537 IF xfrm.xslt <> '---' THEN
4538 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
4540 transformed_xml := NEW.marc;
4543 prev_xfrm := xfrm.name;
4546 IF xfrm.name IS NULL THEN
4547 -- just grab the marcxml (empty) transform
4548 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
4549 prev_xfrm := xfrm.name;
4552 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
4554 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
4555 SELECT m.value INTO attr_value
4556 FROM biblio.marc21_physical_characteristics(NEW.id) v
4557 JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
4558 WHERE v.subfield = attr_def.phys_char_sf
4559 LIMIT 1; -- Just in case ...
4563 -- apply index normalizers to attr_value
4565 SELECT n.func AS func,
4566 n.param_count AS param_count,
4568 FROM config.index_normalizer n
4569 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
4570 WHERE attr = attr_def.name
4572 EXECUTE 'SELECT ' || normalizer.func || '(' ||
4573 quote_literal( attr_value ) ||
4575 WHEN normalizer.param_count > 0
4576 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
4579 ')' INTO attr_value;
4583 -- Add the new value to the hstore
4584 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
4588 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
4589 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
4591 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
4597 -- Gather and insert the field entry data
4598 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
4600 -- Located URI magic
4601 IF TG_OP = 'INSERT' THEN
4602 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4604 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4607 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4609 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4613 -- (re)map metarecord-bib linking
4614 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
4615 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
4617 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4619 ELSE -- we're doing an update, and we're not deleted, remap
4620 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
4622 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4628 $func$ LANGUAGE PLPGSQL;
4630 ALTER TABLE config.circ_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6,2) NOT NULL DEFAULT 0.0;
4632 UPDATE config.circ_matrix_weights
4633 SET marc_bib_level = marc_vr_format;
4635 ALTER TABLE config.hold_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6, 2) NOT NULL DEFAULT 0.0;
4637 UPDATE config.hold_matrix_weights
4638 SET marc_bib_level = marc_vr_format;
4640 ALTER TABLE config.circ_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4642 ALTER TABLE config.hold_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4644 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$
4646 cn_object asset.call_number%ROWTYPE;
4647 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4648 cur_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4649 matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4650 weights config.circ_matrix_weights%ROWTYPE;
4652 denominator NUMERIC(6,2);
4654 result action.found_circ_matrix_matchpoint;
4657 result.success = false;
4659 -- Fetch useful data
4660 SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4661 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
4663 -- Pre-generate this so we only calc it once
4664 IF user_object.dob IS NOT NULL THEN
4665 SELECT INTO user_age age(user_object.dob);
4668 -- Grab the closest set circ weight setting.
4669 SELECT INTO weights cw.*
4670 FROM config.weight_assoc wa
4671 JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
4672 JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
4677 -- No weights? Bad admin! Defaults to handle that anyway.
4678 IF weights.id IS NULL THEN
4679 weights.grp := 11.0;
4680 weights.org_unit := 10.0;
4681 weights.circ_modifier := 5.0;
4682 weights.marc_type := 4.0;
4683 weights.marc_form := 3.0;
4684 weights.marc_bib_level := 2.0;
4685 weights.marc_vr_format := 2.0;
4686 weights.copy_circ_lib := 8.0;
4687 weights.copy_owning_lib := 8.0;
4688 weights.user_home_ou := 8.0;
4689 weights.ref_flag := 1.0;
4690 weights.juvenile_flag := 6.0;
4691 weights.is_renewal := 7.0;
4692 weights.usr_age_lower_bound := 0.0;
4693 weights.usr_age_upper_bound := 0.0;
4696 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4697 -- If you break your org tree with funky parenting this may be wrong
4698 -- 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
4699 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4700 WITH all_distance(distance) AS (
4701 SELECT depth AS distance FROM actor.org_unit_type
4703 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4705 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4707 -- Loop over all the potential matchpoints
4708 FOR cur_matchpoint IN
4710 FROM config.circ_matrix_matchpoint m
4711 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
4712 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
4713 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
4714 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
4715 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4717 -- Permission Groups
4718 -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
4720 -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
4721 AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
4722 AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
4723 AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
4725 AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
4726 -- Static User Checks
4727 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4728 AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
4729 AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
4730 -- Static Item Checks
4731 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4732 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4733 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4734 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4735 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4736 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4738 -- Permission Groups
4739 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
4741 CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
4742 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
4743 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
4744 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4745 -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
4746 CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
4747 -- Static User Checks
4748 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4749 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
4750 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
4751 -- Static Item Checks
4752 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4753 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4754 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4755 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4756 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4757 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4758 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4761 -- Record the full matching row list
4762 row_list := row_list || cur_matchpoint.id;
4764 -- No matchpoint yet?
4765 IF matchpoint.id IS NULL THEN
4766 -- Take the entire matchpoint as a starting point
4767 matchpoint := cur_matchpoint;
4768 CONTINUE; -- No need to look at this row any more.
4771 -- Incomplete matchpoint?
4772 IF matchpoint.circulate IS NULL THEN
4773 matchpoint.circulate := cur_matchpoint.circulate;
4775 IF matchpoint.duration_rule IS NULL THEN
4776 matchpoint.duration_rule := cur_matchpoint.duration_rule;
4778 IF matchpoint.recurring_fine_rule IS NULL THEN
4779 matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
4781 IF matchpoint.max_fine_rule IS NULL THEN
4782 matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
4784 IF matchpoint.hard_due_date IS NULL THEN
4785 matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
4787 IF matchpoint.total_copy_hold_ratio IS NULL THEN
4788 matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
4790 IF matchpoint.available_copy_hold_ratio IS NULL THEN
4791 matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
4793 IF matchpoint.renewals IS NULL THEN
4794 matchpoint.renewals := cur_matchpoint.renewals;
4796 IF matchpoint.grace_period IS NULL THEN
4797 matchpoint.grace_period := cur_matchpoint.grace_period;
4801 -- Check required fields
4802 IF matchpoint.circulate IS NOT NULL AND
4803 matchpoint.duration_rule IS NOT NULL AND
4804 matchpoint.recurring_fine_rule IS NOT NULL AND
4805 matchpoint.max_fine_rule IS NOT NULL THEN
4806 -- All there? We have a completed match.
4807 result.success := true;
4810 -- Include the assembled matchpoint, even if it isn't complete
4811 result.matchpoint := matchpoint;
4813 -- Include (for debugging) the full list of matching rows
4814 result.buildrows := row_list;
4816 -- Hand the result back to caller
4819 $func$ LANGUAGE plpgsql;
4821 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
4825 requestor_object actor.usr%ROWTYPE;
4826 user_object actor.usr%ROWTYPE;
4827 item_object asset.copy%ROWTYPE;
4828 item_cn_object asset.call_number%ROWTYPE;
4829 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4830 matchpoint config.hold_matrix_matchpoint%ROWTYPE;
4831 weights config.hold_matrix_weights%ROWTYPE;
4832 denominator NUMERIC(6,2);
4834 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
4835 SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
4836 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
4837 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4838 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
4840 -- The item's owner should probably be the one determining if the item is holdable
4841 -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
4842 -- This flag will allow for setting it to the owning library (where the call number "lives")
4843 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
4845 -- Grab the closest set circ weight setting.
4847 -- Default to circ library
4848 SELECT INTO weights hw.*
4849 FROM config.weight_assoc wa
4850 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4851 JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
4856 -- Flag is set, use owning library
4857 SELECT INTO weights hw.*
4858 FROM config.weight_assoc wa
4859 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4860 JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
4866 -- No weights? Bad admin! Defaults to handle that anyway.
4867 IF weights.id IS NULL THEN
4868 weights.user_home_ou := 5.0;
4869 weights.request_ou := 5.0;
4870 weights.pickup_ou := 5.0;
4871 weights.item_owning_ou := 5.0;
4872 weights.item_circ_ou := 5.0;
4873 weights.usr_grp := 7.0;
4874 weights.requestor_grp := 8.0;
4875 weights.circ_modifier := 4.0;
4876 weights.marc_type := 3.0;
4877 weights.marc_form := 2.0;
4878 weights.marc_bib_level := 1.0;
4879 weights.marc_vr_format := 1.0;
4880 weights.juvenile_flag := 4.0;
4881 weights.ref_flag := 0.0;
4884 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4885 -- If you break your org tree with funky parenting this may be wrong
4886 -- 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
4887 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4888 WITH all_distance(distance) AS (
4889 SELECT depth AS distance FROM actor.org_unit_type
4891 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4893 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4895 -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
4896 -- This may be better implemented as part of the upgrade script?
4897 -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
4898 -- Then remove this flag, of course.
4899 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
4902 -- Note: This, to me, is REALLY hacky. I put it in anyway.
4903 -- If you can't tell, this is a single call swap on two variables.
4904 SELECT INTO user_object.profile, requestor_object.profile
4905 requestor_object.profile, user_object.profile;
4908 -- Select the winning matchpoint into the matchpoint variable for returning
4909 SELECT INTO matchpoint m.*
4910 FROM config.hold_matrix_matchpoint m
4911 /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
4912 LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
4913 LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
4914 LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
4915 LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
4916 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
4917 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4919 -- Permission Groups
4920 -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
4921 AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
4923 AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
4924 AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
4925 AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
4926 AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
4927 AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
4928 -- Static User Checks
4929 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4930 -- Static Item Checks
4931 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4932 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4933 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4934 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4935 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4936 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4938 -- Permission Groups
4939 CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
4940 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
4942 CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
4943 CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
4944 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
4945 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
4946 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4947 -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
4948 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4949 -- Static Item Checks
4950 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4951 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4952 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4953 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4954 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4955 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4956 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4959 -- Return just the ID for now
4960 RETURN matchpoint.id;
4962 $func$ LANGUAGE 'plpgsql';
4965 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
4968 use MARC::File::XML (BinaryEncoding => 'UTF-8');
4971 use Unicode::Normalize;
4973 MARC::Charset->assume_unicode(1);
4975 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
4976 my $schema = $_TD->{table_schema};
4977 my $rec_id = $_TD->{new}{id};
4979 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
4980 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
4981 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
4985 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
4986 my $ou_cni = 'EVRGRN';
4989 if ($schema eq 'serial') {
4990 $owner = $_TD->{new}{owning_lib};
4992 # are.owner and bre.owner can be null, so fall back to the consortial setting
4993 $owner = $_TD->{new}{owner} || 1;
4996 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
4997 if ($ous_rv->{processed}) {
4998 $ou_cni = $ous_rv->{rows}[0]->{value};
4999 $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
5001 # Fall back to the shortname of the OU if there was no OU setting
5002 $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
5003 if ($ous_rv->{processed}) {
5004 $ou_cni = $ous_rv->{rows}[0]->{shortname};
5008 my ($create, $munge) = (0, 0);
5010 my @scns = $record->field('035');
5012 foreach my $id_field ('001', '003') {
5014 my @controls = $record->field($id_field);
5016 if ($id_field eq '001') {
5017 $spec_value = $rec_id;
5019 $spec_value = $ou_cni;
5022 # Create the 001/003 if none exist
5023 if (scalar(@controls) == 1) {
5024 # Only one field; check to see if we need to munge it
5025 unless (grep $_->data() eq $spec_value, @controls) {
5029 # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
5030 foreach my $control (@controls) {
5031 unless ($control->data() eq $spec_value) {
5032 $record->delete_field($control);
5035 $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
5040 # Now, if we need to munge the 001, we will first push the existing 001/003
5041 # into the 035; but if the record did not have one (and one only) 001 and 003
5042 # to begin with, skip this process
5043 if ($munge and not $create) {
5044 my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
5046 # Do not create duplicate 035 fields
5047 unless (grep $_->subfield('a') eq $scn, @scns) {
5048 $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
5052 # Set the 001/003 and update the MARC
5053 if ($create or $munge) {
5054 $record->field('001')->data($rec_id);
5055 $record->field('003')->data($ou_cni);
5057 my $xml = $record->as_xml_record();
5059 $xml =~ s/^<\?xml.+\?\s*>//go;
5060 $xml =~ s/>\s+</></go;
5061 $xml =~ s/\p{Cc}//go;
5063 # Embed a version of OpenILS::Application::AppUtils->entityize()
5064 # to avoid having to set PERL5LIB for PostgreSQL as well
5066 # If we are going to convert non-ASCII characters to XML entities,
5067 # we had better be dealing with a UTF8 string to begin with
5068 $xml = decode_utf8($xml);
5072 # Convert raw ampersands to entities
5073 $xml =~ s/&(?!\S+;)/&/gso;
5075 # Convert Unicode characters to entities
5076 $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
5078 $xml =~ s/[\x00-\x1f]//go;
5079 $_TD->{new}{marc} = $xml;
5085 $func$ LANGUAGE PLPERLU;
5087 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
5090 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5093 MARC::Charset->assume_unicode(1);
5096 my $r = MARC::Record->new_from_xml( $xml );
5098 return undef unless ($r);
5100 my $id = shift() || $r->subfield( '901' => 'c' );
5101 $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
5102 return undef unless ($id); # We need an ID!
5104 my $tmpl = MARC::Record->new();
5105 $tmpl->encoding( 'UTF-8' );
5108 for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
5110 my $tag = $field->tag;
5111 my $i1 = $field->indicator(1);
5112 my $i2 = $field->indicator(2);
5113 my $sf = join '', map { $_->[0] } $field->subfields;
5114 my @data = map { @$_ } $field->subfields;
5118 # Map the authority field to bib fields it can control.
5119 if ($tag >= 100 and $tag <= 111) { # names
5120 @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
5121 } elsif ($tag eq '130') { # uniform title
5122 @replace_them = qw/130 240 440 730 830/;
5123 } elsif ($tag >= 150 and $tag <= 155) { # subjects
5124 @replace_them = ($tag + 500);
5125 } elsif ($tag >= 180 and $tag <= 185) { # floating subdivisions
5126 @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/;
5131 # Dummy up the bib-side data
5132 $tmpl->append_fields(
5134 MARC::Field->new( $_, $i1, $i2, @data )
5138 # Construct some 'replace' rules
5139 push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
5142 # Insert the replace rules into the template
5143 $tmpl->append_fields(
5144 MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
5147 $xml = $tmpl->as_xml_record;
5148 $xml =~ s/^<\?.+?\?>$//mo;
5150 $xml =~ s/>\s+</></sgo;
5154 $func$ LANGUAGE PLPERLU;
5156 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
5159 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5163 MARC::Charset->assume_unicode(1);
5165 my $target_xml = shift;
5166 my $source_xml = shift;
5167 my $field_spec = shift;
5168 my $force_add = shift || 0;
5170 my $target_r = MARC::Record->new_from_xml( $target_xml );
5171 my $source_r = MARC::Record->new_from_xml( $source_xml );
5173 return $target_xml unless ($target_r && $source_r);
5175 my @field_list = split(',', $field_spec);
5178 for my $f (@field_list) {
5179 $f =~ s/^\s*//; $f =~ s/\s*$//;
5180 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5186 $match =~ s/^\s*//; $match =~ s/\s*$//;
5187 $fields{$field} = { sf => [ split('', $sf) ] };
5189 my ($msf,$mre) = split('~', $match);
5190 if (length($msf) > 0 and length($mre) > 0) {
5191 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5192 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5193 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5199 for my $f ( keys %fields) {
5200 if ( @{$fields{$f}{sf}} ) {
5201 for my $from_field ($source_r->field( $f )) {
5202 my @tos = $target_r->field( $f );
5204 next if (exists($fields{$f}{match}) and !$force_add);
5205 my @new_fields = map { $_->clone } $source_r->field( $f );
5206 $target_r->insert_fields_ordered( @new_fields );
5208 for my $to_field (@tos) {
5209 if (exists($fields{$f}{match})) {
5210 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5212 my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
5213 $to_field->add_subfields( @new_sf );
5218 my @new_fields = map { $_->clone } $source_r->field( $f );
5219 $target_r->insert_fields_ordered( @new_fields );
5223 $target_xml = $target_r->as_xml_record;
5224 $target_xml =~ s/^<\?.+?\?>$//mo;
5225 $target_xml =~ s/\n//sgo;
5226 $target_xml =~ s/>\s+</></sgo;
5230 $_$ LANGUAGE PLPERLU;
5232 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
5238 use MARC::File::XML (BinaryEncoding => 'UTF8');
5240 use UUID::Tiny ':std';
5242 MARC::Charset->assume_unicode(1);
5244 my $xml = shift() or return undef;
5248 # Prevent errors in XML parsing from blowing out ungracefully
5250 $r = MARC::Record->new_from_xml( $xml );
5253 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5257 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5260 # From http://www.loc.gov/standards/sourcelist/subject.html
5261 my $thes_code_map = {
5267 n => 'notapplicable',
5273 # Default to "No attempt to code" if the leader is horribly broken
5274 my $fixed_field = $r->field('008');
5275 my $thes_char = '|';
5277 $thes_char = substr($fixed_field->data(), 11, 1) || '|';
5280 my $thes_code = 'UNDEFINED';
5282 if ($thes_char eq 'z') {
5283 # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
5284 $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
5285 } elsif ($thes_code_map->{$thes_char}) {
5286 $thes_code = $thes_code_map->{$thes_char};
5290 my $head = $r->field('1..');
5292 # Concatenate all of these subfields together, prefixed by their code
5293 # to prevent collisions along the lines of "Fiction, North Carolina"
5294 foreach my $sf ($head->subfields()) {
5295 $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
5300 my $stmt = spi_prepare('SELECT public.naco_normalize($1) AS norm_text', 'TEXT');
5301 my $result = spi_exec_prepared($stmt, $auth_txt);
5302 my $norm_txt = $result->{rows}[0]->{norm_text};
5303 spi_freeplan($stmt);
5305 return $head->tag() . "_" . $thes_code . " " . $norm_txt;
5308 return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
5309 $func$ LANGUAGE 'plperlu' IMMUTABLE;
5311 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
5314 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5318 MARC::Charset->assume_unicode(1);
5321 my $r = MARC::Record->new_from_xml( $xml );
5323 return $xml unless ($r);
5325 my $field_spec = shift;
5326 my @field_list = split(',', $field_spec);
5329 for my $f (@field_list) {
5330 $f =~ s/^\s*//; $f =~ s/\s*$//;
5331 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5337 $match =~ s/^\s*//; $match =~ s/\s*$//;
5338 $fields{$field} = { sf => [ split('', $sf) ] };
5340 my ($msf,$mre) = split('~', $match);
5341 if (length($msf) > 0 and length($mre) > 0) {
5342 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5343 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5344 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5350 for my $f ( keys %fields) {
5351 for my $to_field ($r->field( $f )) {
5352 if (exists($fields{$f}{match})) {
5353 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5356 if ( @{$fields{$f}{sf}} ) {
5357 $to_field->delete_subfield(code => $fields{$f}{sf});
5359 $r->delete_field( $to_field );
5364 $xml = $r->as_xml_record;
5365 $xml =~ s/^<\?.+?\?>$//mo;
5367 $xml =~ s/>\s+</></sgo;
5371 $_$ LANGUAGE PLPERLU;
5373 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5376 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5379 MARC::Charset->assume_unicode(1);
5382 my $r = MARC::Record->new_from_xml( $xml );
5384 return_next( { tag => 'LDR', value => $r->leader } );
5386 for my $f ( $r->fields ) {
5387 if ($f->is_control_field) {
5388 return_next({ tag => $f->tag, value => $f->data });
5390 for my $s ($f->subfields) {
5393 ind1 => $f->indicator(1),
5394 ind2 => $f->indicator(2),
5395 subfield => $s->[0],
5399 if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5400 my $trim = $f->indicator(2) || 0;
5403 ind1 => $f->indicator(1),
5404 ind2 => $f->indicator(2),
5406 value => substr( $s->[1], $trim )
5415 $func$ LANGUAGE PLPERLU;
5417 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
5420 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5423 MARC::Charset->assume_unicode(1);
5426 my $r = MARC::Record->new_from_xml( $xml );
5428 return_next( { tag => 'LDR', value => $r->leader } );
5430 for my $f ( $r->fields ) {
5431 if ($f->is_control_field) {
5432 return_next({ tag => $f->tag, value => $f->data });
5434 for my $s ($f->subfields) {
5437 ind1 => $f->indicator(1),
5438 ind2 => $f->indicator(2),
5439 subfield => $s->[0],
5449 $func$ LANGUAGE PLPERLU;
5452 CREATE INDEX actor_usr_day_phone_idx_numeric ON actor.usr USING BTREE
5453 (evergreen.lowercase(REGEXP_REPLACE(day_phone, '[^0-9]', '', 'g')));
5455 CREATE INDEX actor_usr_evening_phone_idx_numeric ON actor.usr USING BTREE
5456 (evergreen.lowercase(REGEXP_REPLACE(evening_phone, '[^0-9]', '', 'g')));
5458 CREATE INDEX actor_usr_other_phone_idx_numeric ON actor.usr USING BTREE
5459 (evergreen.lowercase(REGEXP_REPLACE(other_phone, '[^0-9]', '', 'g')));
5462 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5467 -- If there are any renewals for this circulation, don't archive or delete
5468 -- it yet. We'll do so later, when we archive and delete the renewals.
5470 SELECT 'Y' INTO found
5471 FROM action.circulation
5472 WHERE parent_circ = OLD.id
5476 RETURN NULL; -- don't delete
5479 -- Archive a copy of the old row to action.aged_circulation
5481 INSERT INTO action.aged_circulation
5482 (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5483 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5484 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5485 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5486 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5487 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5489 id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5490 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5491 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5492 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5493 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5494 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5495 FROM action.all_circulation WHERE id = OLD.id;
5499 $$ LANGUAGE 'plpgsql';
5502 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$
5505 user_object actor.usr%ROWTYPE;
5506 age_protect_object config.rule_age_hold_protect%ROWTYPE;
5507 standing_penalty config.standing_penalty%ROWTYPE;
5508 transit_range_ou_type actor.org_unit_type%ROWTYPE;
5509 transit_source actor.org_unit%ROWTYPE;
5510 item_object asset.copy%ROWTYPE;
5511 item_cn_object asset.call_number%ROWTYPE;
5512 ou_skip actor.org_unit_setting%ROWTYPE;
5513 result action.matrix_test_result;
5514 hold_test config.hold_matrix_matchpoint%ROWTYPE;
5516 hold_transit_prox INT;
5517 frozen_hold_count INT;
5518 context_org_list INT[];
5521 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5522 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5524 result.success := TRUE;
5526 -- Fail if we couldn't find a user
5527 IF user_object.id IS NULL THEN
5528 result.fail_part := 'no_user';
5529 result.success := FALSE;
5535 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5537 -- Fail if we couldn't find a copy
5538 IF item_object.id IS NULL THEN
5539 result.fail_part := 'no_item';
5540 result.success := FALSE;
5546 SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5547 result.matchpoint := matchpoint_id;
5549 SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5551 -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5552 IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5553 result.fail_part := 'circ.holds.target_skip_me';
5554 result.success := FALSE;
5560 -- Fail if user is barred
5561 IF user_object.barred IS TRUE THEN
5562 result.fail_part := 'actor.usr.barred';
5563 result.success := FALSE;
5569 -- Fail if we couldn't find any matchpoint (requires a default)
5570 IF matchpoint_id IS NULL THEN
5571 result.fail_part := 'no_matchpoint';
5572 result.success := FALSE;
5578 SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5580 IF hold_test.holdable IS FALSE THEN
5581 result.fail_part := 'config.hold_matrix_test.holdable';
5582 result.success := FALSE;
5587 IF hold_test.transit_range IS NOT NULL THEN
5588 SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5589 IF hold_test.distance_is_from_owner THEN
5590 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;
5592 SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5595 PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5598 result.fail_part := 'transit_range';
5599 result.success := FALSE;
5605 FOR standing_penalty IN
5606 SELECT DISTINCT csp.*
5607 FROM actor.usr_standing_penalty usp
5608 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5609 WHERE usr = match_user
5610 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5611 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5612 AND csp.block_list LIKE '%HOLD%' LOOP
5614 result.fail_part := standing_penalty.name;
5615 result.success := FALSE;
5620 IF hold_test.stop_blocked_user IS TRUE THEN
5621 FOR standing_penalty IN
5622 SELECT DISTINCT csp.*
5623 FROM actor.usr_standing_penalty usp
5624 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5625 WHERE usr = match_user
5626 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5627 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5628 AND csp.block_list LIKE '%CIRC%' LOOP
5630 result.fail_part := standing_penalty.name;
5631 result.success := FALSE;
5637 IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
5638 SELECT INTO hold_count COUNT(*)
5639 FROM action.hold_request
5640 WHERE usr = match_user
5641 AND fulfillment_time IS NULL
5642 AND cancel_time IS NULL
5643 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5645 IF hold_count >= hold_test.max_holds THEN
5646 result.fail_part := 'config.hold_matrix_test.max_holds';
5647 result.success := FALSE;
5653 IF item_object.age_protect IS NOT NULL THEN
5654 SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5656 IF item_object.create_date + age_protect_object.age > NOW() THEN
5657 IF hold_test.distance_is_from_owner THEN
5658 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
5659 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5661 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5664 IF hold_transit_prox > age_protect_object.prox THEN
5665 result.fail_part := 'config.rule_age_hold_protect.prox';
5666 result.success := FALSE;
5679 $func$ LANGUAGE plpgsql;
5681 -- do potentially large updates last to save time if upgrader needs
5682 -- to manually tweak the upgrade script to resolve errors
5685 UPDATE metabib.facet_entry SET value = evergreen.force_unicode_normal_form(value,'NFC');
5687 -- Update reporter.materialized_simple_record with normalized ISBN values
5688 -- This might not get all of them, but most ISBNs will have more than one hyphen
5689 DELETE FROM reporter.materialized_simple_record WHERE id IN (
5690 SELECT record FROM metabib.full_rec WHERE tag = '020' AND subfield IN ('a', 'z') AND value LIKE '%-%-%'
5693 INSERT INTO reporter.materialized_simple_record
5694 SELECT DISTINCT rossr.* FROM reporter.old_super_simple_record rossr INNER JOIN metabib.full_rec mfr ON mfr.record = rossr.id
5695 WHERE mfr.tag = '020' AND mfr.subfield IN ('a', 'z') AND mfr.value LIKE '%-%-%'
5698 INSERT INTO config.upgrade_log (version) VALUES ('0542'); -- phasefx
5700 INSERT INTO permission.perm_list
5703 (485, 'CREATE_VOLUME_SUFFIX', oils_i18n_gettext(485, 'Create suffix label definition.', 'ppl', 'description'))
5704 ,(486, 'UPDATE_VOLUME_SUFFIX', oils_i18n_gettext(486, 'Update suffix label definition.', 'ppl', 'description'))
5705 ,(487, 'DELETE_VOLUME_SUFFIX', oils_i18n_gettext(487, 'Delete suffix label definition.', 'ppl', 'description'))
5706 ,(488, 'CREATE_VOLUME_PREFIX', oils_i18n_gettext(488, 'Create prefix label definition.', 'ppl', 'description'))
5707 ,(489, 'UPDATE_VOLUME_PREFIX', oils_i18n_gettext(489, 'Update prefix label definition.', 'ppl', 'description'))
5708 ,(490, 'DELETE_VOLUME_PREFIX', oils_i18n_gettext(490, 'Delete prefix label definition.', 'ppl', 'description'))
5709 ,(491, 'CREATE_MONOGRAPH_PART', oils_i18n_gettext(491, 'Create monograph part definition.', 'ppl', 'description'))
5710 ,(492, 'UPDATE_MONOGRAPH_PART', oils_i18n_gettext(492, 'Update monograph part definition.', 'ppl', 'description'))
5711 ,(493, 'DELETE_MONOGRAPH_PART', oils_i18n_gettext(493, 'Delete monograph part definition.', 'ppl', 'description'))
5712 ,(494, 'ADMIN_CODED_VALUE', oils_i18n_gettext(494, 'Create/Update/Delete SVF Record Attribute Coded Value Map', 'ppl', 'description'))
5713 ,(495, 'ADMIN_SERIAL_ITEM', oils_i18n_gettext(495, 'Create/Retrieve/Update/Delete Serial Item', 'ppl', 'description'))
5714 ,(496, 'ADMIN_SVF', oils_i18n_gettext(496, 'Create/Update/Delete SVF Record Attribute Defintion', 'ppl', 'description'))
5715 ,(497, 'CREATE_BIB_PTYPE', oils_i18n_gettext(497, 'Create Bibliographic Record Peer Type', 'ppl', 'description'))
5716 ,(498, 'CREATE_PURCHASE_REQUEST', oils_i18n_gettext(498, 'Create User Purchase Request', 'ppl', 'description'))
5717 ,(499, 'DELETE_BIB_PTYPE', oils_i18n_gettext(499, 'Delete Bibliographic Record Peer Type', 'ppl', 'description'))
5718 ,(500, 'MAP_MONOGRAPH_PART', oils_i18n_gettext(500, 'Create/Update/Delete Copy Monograph Part Map', 'ppl', 'description'))
5719 ,(501, 'MARK_ITEM_MISSING_PIECES', oils_i18n_gettext(501, 'Allows the Mark Item Missing Pieces action.', 'ppl', 'description'))
5720 ,(502, 'UPDATE_BIB_PTYPE', oils_i18n_gettext(502, 'Update Bibliographic Record Peer Type', 'ppl', 'description'))
5721 ,(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'))
5722 ,(504, 'UPDATE_PICKLIST', oils_i18n_gettext(504, 'Allows update/re-use of an acquisitions pick/selection list.', 'ppl', 'description'))
5723 ,(505, 'UPDATE_WORKSTATION', oils_i18n_gettext(505, 'Allows update of a workstation during workstation registration override.', 'ppl', 'description'))
5724 ,(506, 'VIEW_USER_SETTING_TYPE', oils_i18n_gettext(506, 'Allows viewing of configurable user setting types.', 'ppl', 'description'))
5725 ) AS np(id,code,description)
5726 LEFT JOIN permission.perm_list pl USING (code)
5727 WHERE pl.id IS NULL;
5731 -- add new perms AND catch up on some missed upgrade data, if needed
5733 -- Prevent conflicts with existing custom permission groups; as of 2.0.10,
5734 -- highest permission.grp_tree ID was 10 for Local System Administrator
5735 UPDATE permission.grp_tree SET id = id + 100 WHERE id > 10;
5736 UPDATE permission.grp_tree SET parent = parent + 100 WHERE parent > 10;
5737 UPDATE actor.usr SET profile = profile + 100 WHERE profile > 10;
5738 UPDATE permission.grp_perm_map SET grp = grp + 100 WHERE grp > 10;
5739 UPDATE permission.usr_grp_map SET grp = grp + 100 WHERE grp > 10;
5740 UPDATE permission.grp_penalty_threshold SET grp = grp + 100 WHERE grp > 10;
5741 UPDATE config.circ_matrix_matchpoint SET grp = grp + 100 WHERE grp > 10;
5742 UPDATE config.hold_matrix_matchpoint SET requestor_grp = requestor_grp + 100 WHERE requestor_grp > 10;
5743 UPDATE config.hold_matrix_matchpoint SET usr_grp = usr_grp + 100 WHERE usr_grp > 10;
5745 -- we could get away from these fixed-id inserts here, but then this
5746 -- upgrade would be ahead of the mainline, I think
5748 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5749 SELECT 8, oils_i18n_gettext(8, 'Cataloging Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.cat_admin'
5752 FROM permission.grp_tree
5757 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5758 SELECT 9, oils_i18n_gettext(9, 'Circulation Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.circ_admin'
5761 FROM permission.grp_tree
5766 UPDATE permission.grp_tree SET description = oils_i18n_gettext(10, 'Can do anything at the Branch level', 'pgt', 'description') WHERE id = 10;
5768 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5769 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'
5772 FROM permission.grp_tree
5777 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5778 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'
5781 FROM permission.grp_tree
5786 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5787 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'
5790 FROM permission.grp_tree
5795 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5796 SELECT 14, oils_i18n_gettext(14, 'Data Review', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.data_review'
5799 FROM permission.grp_tree
5804 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5805 SELECT 15, oils_i18n_gettext(15, 'Volunteers', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.volunteers'
5808 FROM permission.grp_tree
5813 SELECT SETVAL('permission.grp_tree_id_seq'::TEXT, (SELECT MAX(id) FROM permission.grp_tree));
5815 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5817 pgt.id, perm.id, aout.depth, TRUE
5819 permission.grp_tree pgt,
5820 permission.perm_list perm,
5821 actor.org_unit_type aout
5823 pgt.name = 'Cataloging Administrator' AND
5824 aout.name = 'Consortium' AND
5826 'ADMIN_IMPORT_ITEM_ATTR_DEF',
5827 'ADMIN_MERGE_PROFILE',
5828 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
5829 'CREATE_BIB_IMPORT_FIELD_DEF',
5831 'CREATE_BIB_SOURCE',
5832 'CREATE_IMPORT_ITEM_ATTR_DEF',
5833 'CREATE_IMPORT_TRASH_FIELD',
5834 'CREATE_MERGE_PROFILE',
5835 'CREATE_MONOGRAPH_PART',
5836 'CREATE_VOLUME_PREFIX',
5837 'CREATE_VOLUME_SUFFIX',
5838 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5840 'DELETE_BIB_SOURCE',
5841 'DELETE_IMPORT_ITEM_ATTR_DEF',
5842 'DELETE_IMPORT_TRASH_FIELD',
5843 'DELETE_MERGE_PROFILE',
5844 'DELETE_MONOGRAPH_PART',
5845 'DELETE_VOLUME_PREFIX',
5846 'DELETE_VOLUME_SUFFIX',
5847 'MAP_MONOGRAPH_PART',
5848 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5849 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
5851 'UPDATE_IMPORT_ITEM_ATTR_DEF',
5852 'UPDATE_IMPORT_TRASH_FIELD',
5853 'UPDATE_MERGE_PROFILE',
5854 'UPDATE_MONOGRAPH_PART',
5855 'UPDATE_VOLUME_PREFIX',
5856 'UPDATE_VOLUME_SUFFIX'
5859 FROM permission.grp_perm_map AS map
5862 AND map.perm = perm.id
5865 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5867 pgt.id, perm.id, aout.depth, TRUE
5869 permission.grp_tree pgt,
5870 permission.perm_list perm,
5871 actor.org_unit_type aout
5873 pgt.name = 'Circulation Administrator' AND
5874 aout.name = 'Branch' AND
5879 FROM permission.grp_perm_map AS map
5882 AND map.perm = perm.id
5885 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5887 pgt.id, perm.id, aout.depth, TRUE
5889 permission.grp_tree pgt,
5890 permission.perm_list perm,
5891 actor.org_unit_type aout
5893 pgt.name = 'Circulation Administrator' AND
5894 aout.name = 'Consortium' AND
5896 'ADMIN_MAX_FINE_RULE',
5897 'CREATE_CIRC_DURATION',
5898 'DELETE_CIRC_DURATION',
5899 'MARK_ITEM_MISSING_PIECES',
5900 'UPDATE_CIRC_DURATION',
5901 'UPDATE_HOLD_REQUEST_TIME',
5902 'UPDATE_NET_ACCESS_LEVEL',
5903 'VIEW_CIRC_MATRIX_MATCHPOINT',
5904 'VIEW_HOLD_MATRIX_MATCHPOINT'
5907 FROM permission.grp_perm_map AS map
5910 AND map.perm = perm.id
5913 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5915 pgt.id, perm.id, aout.depth, TRUE
5917 permission.grp_tree pgt,
5918 permission.perm_list perm,
5919 actor.org_unit_type aout
5921 pgt.name = 'Circulation Administrator' AND
5922 aout.name = 'System' AND
5924 'ADMIN_BOOKING_RESERVATION',
5925 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
5926 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
5927 'ADMIN_BOOKING_RESOURCE',
5928 'ADMIN_BOOKING_RESOURCE_ATTR',
5929 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
5930 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
5931 'ADMIN_BOOKING_RESOURCE_TYPE',
5932 'ADMIN_COPY_LOCATION_ORDER',
5933 'ADMIN_HOLD_CANCEL_CAUSE',
5934 'ASSIGN_GROUP_PERM',
5937 'COPY_TRANSIT_RECEIVE',
5939 'CREATE_BILLING_TYPE',
5940 'CREATE_NON_CAT_TYPE',
5941 'CREATE_PATRON_STAT_CAT',
5942 'CREATE_PATRON_STAT_CAT_ENTRY',
5943 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
5944 'CREATE_USER_GROUP_LINK',
5945 'DELETE_BILLING_TYPE',
5946 'DELETE_NON_CAT_TYPE',
5947 'DELETE_PATRON_STAT_CAT',
5948 'DELETE_PATRON_STAT_CAT_ENTRY',
5949 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
5951 'group_application.user.staff',
5953 'MARK_ITEM_AVAILABLE',
5954 'MARK_ITEM_BINDERY',
5955 'MARK_ITEM_CHECKED_OUT',
5957 'MARK_ITEM_IN_PROCESS',
5958 'MARK_ITEM_IN_TRANSIT',
5960 'MARK_ITEM_MISSING',
5961 'MARK_ITEM_ON_HOLDS_SHELF',
5962 'MARK_ITEM_ON_ORDER',
5963 'MARK_ITEM_RESHELVING',
5965 'money.collections_tracker.create',
5966 'money.collections_tracker.delete',
5970 'REMOVE_USER_GROUP_LINK',
5971 'SET_CIRC_CLAIMS_RETURNED',
5972 'SET_CIRC_CLAIMS_RETURNED.override',
5977 'UPDATE_NON_CAT_TYPE',
5978 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
5979 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
5980 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
5981 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
5983 'VIEW_REPORT_OUTPUT',
5984 'VIEW_STANDING_PENALTY',
5989 FROM permission.grp_perm_map AS map
5992 AND map.perm = perm.id
5995 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5997 pgt.id, perm.id, aout.depth, TRUE
5999 permission.grp_tree pgt,
6000 permission.perm_list perm,
6001 actor.org_unit_type aout
6003 pgt.name = 'Local Administrator' AND
6004 aout.name = 'Branch' AND
6009 FROM permission.grp_perm_map AS map
6012 AND map.perm = perm.id
6015 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6017 pgt.id, perm.id, aout.depth, FALSE
6019 permission.grp_tree pgt,
6020 permission.perm_list perm,
6021 actor.org_unit_type aout
6023 pgt.name = 'Serials' AND
6024 aout.name = 'System' AND
6026 'ADMIN_ASSET_COPY_TEMPLATE',
6027 'ADMIN_SERIAL_CAPTION_PATTERN',
6028 'ADMIN_SERIAL_DISTRIBUTION',
6029 'ADMIN_SERIAL_ITEM',
6030 'ADMIN_SERIAL_STREAM',
6031 'ADMIN_SERIAL_SUBSCRIPTION',
6036 FROM permission.grp_perm_map AS map
6039 AND map.perm = perm.id
6042 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6044 pgt.id, perm.id, aout.depth, TRUE
6046 permission.grp_tree pgt,
6047 permission.perm_list perm,
6048 actor.org_unit_type aout
6050 pgt.name = 'System Administrator' AND
6051 aout.name = 'System' AND
6056 FROM permission.grp_perm_map AS map
6059 AND map.perm = perm.id
6062 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6064 pgt.id, perm.id, aout.depth, FALSE
6066 permission.grp_tree pgt,
6067 permission.perm_list perm,
6068 actor.org_unit_type aout
6070 pgt.name = 'System Administrator' AND
6071 aout.name = 'Consortium' AND
6072 perm.code ~ '^VIEW_TRIGGER'
6075 FROM permission.grp_perm_map AS map
6078 AND map.perm = perm.id
6081 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6083 pgt.id, perm.id, aout.depth, TRUE
6085 permission.grp_tree pgt,
6086 permission.perm_list perm,
6087 actor.org_unit_type aout
6089 pgt.name = 'Global Administrator' AND
6090 aout.name = 'Consortium' AND
6095 FROM permission.grp_perm_map AS map
6098 AND map.perm = perm.id
6101 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6103 pgt.id, perm.id, aout.depth, FALSE
6105 permission.grp_tree pgt,
6106 permission.perm_list perm,
6107 actor.org_unit_type aout
6109 pgt.name = 'Data Review' AND
6110 aout.name = 'Consortium' AND
6112 'CREATE_COPY_TRANSIT',
6113 'VIEW_BILLING_TYPE',
6114 'VIEW_CIRCULATIONS',
6117 'VIEW_ORG_SETTINGS',
6121 'VIEW_USER_FINES_SUMMARY',
6122 'VIEW_USER_TRANSACTIONS',
6123 'VIEW_VOLUME_NOTES',
6127 FROM permission.grp_perm_map AS map
6130 AND map.perm = perm.id
6133 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6135 pgt.id, perm.id, aout.depth, FALSE
6137 permission.grp_tree pgt,
6138 permission.perm_list perm,
6139 actor.org_unit_type aout
6141 pgt.name = 'Data Review' AND
6142 aout.name = 'System' AND
6146 'CREATE_IN_HOUSE_USE',
6147 'CREATE_TRANSACTION',
6154 FROM permission.grp_perm_map AS map
6157 AND map.perm = perm.id
6160 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6162 pgt.id, perm.id, aout.depth, FALSE
6164 permission.grp_tree pgt,
6165 permission.perm_list perm,
6166 actor.org_unit_type aout
6168 pgt.name = 'Volunteers' AND
6169 aout.name = 'Branch' AND
6173 'CREATE_IN_HOUSE_USE',
6175 'VIEW_BILLING_TYPE',
6177 'VIEW_COPY_CHECKOUT',
6182 'VIEW_USER_FINES_SUMMARY',
6183 'VIEW_USER_TRANSACTIONS'
6186 FROM permission.grp_perm_map AS map
6189 AND map.perm = perm.id
6192 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6194 pgt.id, perm.id, aout.depth, FALSE
6196 permission.grp_tree pgt,
6197 permission.perm_list perm,
6198 actor.org_unit_type aout
6200 pgt.name = 'Volunteers' AND
6201 aout.name = 'Consortium' AND
6203 'CREATE_COPY_TRANSIT',
6204 'CREATE_TRANSACTION',
6211 FROM permission.grp_perm_map AS map
6214 AND map.perm = perm.id
6218 -- stock Users group
6219 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6221 pgt.id, perm.id, aout.depth, FALSE
6223 permission.grp_tree pgt,
6224 permission.perm_list perm,
6225 actor.org_unit_type aout
6227 pgt.name = 'Users' AND
6228 aout.name = 'Consortium' AND
6230 'CREATE_PURCHASE_REQUEST'
6233 FROM permission.grp_perm_map AS map
6236 AND map.perm = perm.id
6239 -- stock Staff group
6240 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6242 pgt.id, perm.id, aout.depth, FALSE
6244 permission.grp_tree pgt,
6245 permission.perm_list perm,
6246 actor.org_unit_type aout
6248 pgt.name = 'Staff' AND
6249 aout.name = 'Consortium' AND
6251 'VIEW_USER_SETTING_TYPE'
6254 FROM permission.grp_perm_map AS map
6257 AND map.perm = perm.id
6260 -- stock Circulators group
6261 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6263 pgt.id, perm.id, aout.depth, FALSE
6265 permission.grp_tree pgt,
6266 permission.perm_list perm,
6267 actor.org_unit_type aout
6269 pgt.name = 'Circulators' AND
6270 aout.name = 'Branch' AND
6272 'MARK_ITEM_MISSING_PIECES'
6275 FROM permission.grp_perm_map AS map
6278 AND map.perm = perm.id
6281 -- stock Catalogers group
6282 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6284 pgt.id, perm.id, aout.depth, FALSE
6286 permission.grp_tree pgt,
6287 permission.perm_list perm,
6288 actor.org_unit_type aout
6290 pgt.name = 'Catalogers' AND
6291 aout.name = 'System' AND
6293 'MAP_MONOGRAPH_PART'
6296 FROM permission.grp_perm_map AS map
6299 AND map.perm = perm.id
6302 -- stock Acquisitions group
6303 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6305 pgt.id, perm.id, aout.depth, FALSE
6307 permission.grp_tree pgt,
6308 permission.perm_list perm,
6309 actor.org_unit_type aout
6311 pgt.name = 'Acquisitions' AND
6312 aout.name = 'Consortium' AND
6317 FROM permission.grp_perm_map AS map
6320 AND map.perm = perm.id
6323 -- stock Acq Admin group
6324 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6326 pgt.id, perm.id, aout.depth, TRUE
6328 permission.grp_tree pgt,
6329 permission.perm_list perm,
6330 actor.org_unit_type aout
6332 pgt.name = 'Acquisitions Administrator' AND
6333 aout.name = 'Consortium' AND
6338 FROM permission.grp_perm_map AS map
6341 AND map.perm = perm.id
6344 INSERT INTO config.upgrade_log (version) VALUES ('0547'); -- dbwells
6346 -- account for spelling errors (Admin != Administrator)
6347 \qecho This might not insert much if you passed through 0542 on your way here,
6348 \qecho but one group was missed there as well
6350 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6352 pgt.id, perm.id, aout.depth, TRUE
6354 permission.grp_tree pgt,
6355 permission.perm_list perm,
6356 actor.org_unit_type aout
6358 pgt.name = 'Cataloging Administrator' AND
6359 aout.name = 'Consortium' AND
6361 'ADMIN_IMPORT_ITEM_ATTR_DEF',
6362 'ADMIN_MERGE_PROFILE',
6363 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
6364 'CREATE_BIB_IMPORT_FIELD_DEF',
6366 'CREATE_BIB_SOURCE',
6367 'CREATE_IMPORT_ITEM_ATTR_DEF',
6368 'CREATE_IMPORT_TRASH_FIELD',
6369 'CREATE_MERGE_PROFILE',
6370 'CREATE_MONOGRAPH_PART',
6371 'CREATE_VOLUME_PREFIX',
6372 'CREATE_VOLUME_SUFFIX',
6373 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6375 'DELETE_BIB_SOURCE',
6376 'DELETE_IMPORT_ITEM_ATTR_DEF',
6377 'DELETE_IMPORT_TRASH_FIELD',
6378 'DELETE_MERGE_PROFILE',
6379 'DELETE_MONOGRAPH_PART',
6380 'DELETE_VOLUME_PREFIX',
6381 'DELETE_VOLUME_SUFFIX',
6382 'MAP_MONOGRAPH_PART',
6383 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6384 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
6386 'UPDATE_IMPORT_ITEM_ATTR_DEF',
6387 'UPDATE_IMPORT_TRASH_FIELD',
6388 'UPDATE_MERGE_PROFILE',
6389 'UPDATE_MONOGRAPH_PART',
6390 'UPDATE_VOLUME_PREFIX',
6391 'UPDATE_VOLUME_SUFFIX'
6394 FROM permission.grp_perm_map AS map
6397 AND map.perm = perm.id
6400 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6402 pgt.id, perm.id, aout.depth, TRUE
6404 permission.grp_tree pgt,
6405 permission.perm_list perm,
6406 actor.org_unit_type aout
6408 pgt.name = 'Cataloging Administrator' AND
6409 aout.name = 'System' AND
6411 'CREATE_COPY_STAT_CAT',
6412 'CREATE_COPY_STAT_CAT_ENTRY',
6413 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
6415 'SHARE_REPORT_FOLDER',
6416 'UPDATE_COPY_LOCATION',
6417 'UPDATE_COPY_STAT_CAT',
6418 'UPDATE_COPY_STAT_CAT_ENTRY',
6419 'VIEW_REPORT_OUTPUT'
6422 FROM permission.grp_perm_map AS map
6425 AND map.perm = perm.id
6428 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6430 pgt.id, perm.id, aout.depth, TRUE
6432 permission.grp_tree pgt,
6433 permission.perm_list perm,
6434 actor.org_unit_type aout
6436 pgt.name = 'Circulation Administrator' AND
6437 aout.name = 'Branch' AND
6442 FROM permission.grp_perm_map AS map
6445 AND map.perm = perm.id
6448 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6450 pgt.id, perm.id, aout.depth, TRUE
6452 permission.grp_tree pgt,
6453 permission.perm_list perm,
6454 actor.org_unit_type aout
6456 pgt.name = 'Circulation Administrator' AND
6457 aout.name = 'Consortium' AND
6459 'ADMIN_MAX_FINE_RULE',
6460 'CREATE_CIRC_DURATION',
6461 'DELETE_CIRC_DURATION',
6462 'MARK_ITEM_MISSING_PIECES',
6463 'UPDATE_CIRC_DURATION',
6464 'UPDATE_HOLD_REQUEST_TIME',
6465 'UPDATE_NET_ACCESS_LEVEL',
6466 'VIEW_CIRC_MATRIX_MATCHPOINT',
6467 'VIEW_HOLD_MATRIX_MATCHPOINT'
6470 FROM permission.grp_perm_map AS map
6473 AND map.perm = perm.id
6476 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6478 pgt.id, perm.id, aout.depth, TRUE
6480 permission.grp_tree pgt,
6481 permission.perm_list perm,
6482 actor.org_unit_type aout
6484 pgt.name = 'Circulation Administrator' AND
6485 aout.name = 'System' AND
6487 'ADMIN_BOOKING_RESERVATION',
6488 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
6489 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
6490 'ADMIN_BOOKING_RESOURCE',
6491 'ADMIN_BOOKING_RESOURCE_ATTR',
6492 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
6493 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
6494 'ADMIN_BOOKING_RESOURCE_TYPE',
6495 'ADMIN_COPY_LOCATION_ORDER',
6496 'ADMIN_HOLD_CANCEL_CAUSE',
6497 'ASSIGN_GROUP_PERM',
6500 'COPY_TRANSIT_RECEIVE',
6502 'CREATE_BILLING_TYPE',
6503 'CREATE_NON_CAT_TYPE',
6504 'CREATE_PATRON_STAT_CAT',
6505 'CREATE_PATRON_STAT_CAT_ENTRY',
6506 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
6507 'CREATE_USER_GROUP_LINK',
6508 'DELETE_BILLING_TYPE',
6509 'DELETE_NON_CAT_TYPE',
6510 'DELETE_PATRON_STAT_CAT',
6511 'DELETE_PATRON_STAT_CAT_ENTRY',
6512 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
6514 'group_application.user.staff',
6516 'MARK_ITEM_AVAILABLE',
6517 'MARK_ITEM_BINDERY',
6518 'MARK_ITEM_CHECKED_OUT',
6520 'MARK_ITEM_IN_PROCESS',
6521 'MARK_ITEM_IN_TRANSIT',
6523 'MARK_ITEM_MISSING',
6524 'MARK_ITEM_ON_HOLDS_SHELF',
6525 'MARK_ITEM_ON_ORDER',
6526 'MARK_ITEM_RESHELVING',
6528 'money.collections_tracker.create',
6529 'money.collections_tracker.delete',
6533 'REMOVE_USER_GROUP_LINK',
6534 'SET_CIRC_CLAIMS_RETURNED',
6535 'SET_CIRC_CLAIMS_RETURNED.override',
6540 'UPDATE_NON_CAT_TYPE',
6541 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
6542 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
6543 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
6544 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
6546 'VIEW_REPORT_OUTPUT',
6547 'VIEW_STANDING_PENALTY',
6552 FROM permission.grp_perm_map AS map
6555 AND map.perm = perm.id
6558 INSERT INTO config.upgrade_log (version) VALUES ('0557'); -- miker
6560 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$
6564 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6568 label_prefix AS prefix,
6569 label_suffix AS suffix
6573 FROM asset.copy_location
6577 INSERT INTO config.upgrade_log (version) VALUES ('0558'); -- miker
6579 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$
6583 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6590 FROM config.copy_status
6594 INSERT INTO config.upgrade_log (version) VALUES ('0560'); -- miker
6596 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
6600 do_add BOOLEAN := false;
6601 do_remove BOOLEAN := false;
6604 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
6605 SELECT id, circ_lib, record FROM (
6606 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
6608 JOIN asset.call_number cn ON (cn.id = cp.call_number)
6609 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6610 JOIN asset.copy_location cl ON (cp.location = cl.id)
6611 JOIN config.copy_status cs ON (cp.status = cs.id)
6612 JOIN biblio.record_entry b ON (cn.record = b.id)
6613 WHERE NOT cp.deleted
6621 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
6623 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
6624 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6625 JOIN asset.copy_location cl ON (cp.location = cl.id)
6626 JOIN config.copy_status cs ON (cp.status = cs.id)
6627 WHERE NOT cp.deleted
6636 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
6638 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
6639 IF TG_OP = 'INSERT' THEN
6640 add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';';
6644 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
6645 EXECUTE remove_query;
6650 IF TG_OP = 'INSERT' THEN
6652 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6653 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6661 -- handle items first, since with circulation activity
6662 -- their statuses change frequently
6663 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6665 IF OLD.location <> NEW.location OR
6666 OLD.call_number <> NEW.call_number OR
6667 OLD.status <> NEW.status OR
6668 OLD.circ_lib <> NEW.circ_lib THEN
6669 -- any of these could change visibility, but
6670 -- we'll save some queries and not try to calculate
6671 -- the change directly
6676 IF OLD.deleted <> NEW.deleted THEN
6684 IF OLD.opac_visible <> NEW.opac_visible THEN
6685 IF OLD.opac_visible THEN
6687 ELSIF NOT do_remove THEN -- handle edge case where deleted item
6688 -- is also marked opac_visible
6696 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
6699 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6707 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
6709 IF OLD.deleted AND NEW.deleted THEN -- do nothing
6713 ELSIF NEW.deleted THEN -- remove rows
6715 IF TG_TABLE_NAME = 'call_number' THEN
6716 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
6717 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6718 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
6723 ELSIF OLD.deleted THEN -- add rows
6725 IF TG_TABLE_NAME IN ('copy','unit') THEN
6726 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6727 ELSIF TG_TABLE_NAME = 'call_number' THEN
6728 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6729 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6730 add_query := add_query || 'WHERE x.record = ' || NEW.id || ';';
6740 IF TG_TABLE_NAME = 'call_number' THEN
6742 IF OLD.record <> NEW.record THEN
6743 -- call number is linked to different bib
6744 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
6745 EXECUTE remove_query;
6746 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6754 IF TG_TABLE_NAME IN ('record_entry') THEN
6755 RETURN NEW; -- don't have 'opac_visible'
6758 -- actor.org_unit, asset.copy_location, asset.copy_status
6759 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
6763 ELSIF NEW.opac_visible THEN -- add rows
6765 IF TG_TABLE_NAME = 'org_unit' THEN
6766 add_query := add_query || 'WHERE x.circ_lib = ' || NEW.id || ';';
6767 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6768 add_query := add_query || 'WHERE x.location = ' || NEW.id || ';';
6769 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6770 add_query := add_query || 'WHERE x.status = ' || NEW.id || ';';
6777 IF TG_TABLE_NAME = 'org_unit' THEN
6778 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
6779 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6780 remove_query := remove_query || 'location = ' || NEW.id || ');';
6781 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6782 remove_query := remove_query || 'status = ' || NEW.id || ');';
6785 EXECUTE remove_query;
6791 $func$ LANGUAGE PLPGSQL;
6793 INSERT INTO config.upgrade_log (version) VALUES ('0563');
6795 INSERT INTO permission.perm_list ( id, code, description )
6796 VALUES ( 510, 'UPDATE_PATRON_COLLECTIONS_EXEMPT', oils_i18n_gettext(510,
6797 'Allows a user to indicate that a patron is exempt from collections processing', 'ppl', 'description'));
6799 --- stock Circulation Administrator group
6801 INSERT INTO permission.grp_perm_map ( grp, perm, depth, grantable )
6807 FROM permission.perm_list
6808 WHERE code in ('UPDATE_PATRON_COLLECTIONS_EXEMPT');
6810 INSERT INTO config.upgrade_log (version) VALUES ('0566');
6812 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$
6814 me biblio.record_entry%ROWTYPE;
6815 layout unapi.bre_output_layout%ROWTYPE;
6816 xfrm config.xml_transform%ROWTYPE;
6825 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6827 IF ouid IS NULL THEN
6831 IF format = 'holdings_xml' THEN -- the special case
6832 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6836 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6838 IF layout.name IS NULL THEN
6842 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6844 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6846 -- grab SVF if we need them
6847 IF ('mra' = ANY (includes)) THEN
6848 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6853 -- grab hodlings if we need them
6854 IF ('holdings_xml' = ANY (includes)) THEN
6855 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6861 -- generate our item node
6864 IF format = 'marcxml' THEN
6866 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6867 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6870 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6873 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6875 IF axml IS NOT NULL THEN
6876 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6879 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6880 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6883 IF ('bre.unapi' = ANY (includes)) THEN
6884 output := REGEXP_REPLACE(
6886 '</' || top_el || '>(.*?)',
6890 'http://www.w3.org/1999/xhtml' AS xmlns,
6891 'unapi-id' AS class,
6892 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6894 )::TEXT || '</' || top_el || E'>\\1'
6900 output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6903 $F$ LANGUAGE PLPGSQL;
6905 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$
6909 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6910 CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6914 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
6917 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6919 FROM asset.opac_ou_record_copy_count($2, $1)
6923 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6925 FROM asset.staff_ou_record_copy_count($2, $1)
6930 WHEN ('bmp' = ANY ($5)) THEN
6932 name monograph_parts,
6933 (SELECT XMLAGG(bmp) FROM (
6934 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)
6935 FROM biblio.monograph_part
6943 (SELECT XMLAGG(acn) FROM (
6944 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)
6945 FROM asset.call_number acn
6946 WHERE acn.record = $1
6950 JOIN actor.org_unit_descendants(
6955 FROM actor.org_unit_type aout
6956 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6959 ) aoud ON (acp.circ_lib = aoud.id)
6962 ORDER BY label_sortkey
6967 CASE WHEN ('ssub' = ANY ($5)) THEN
6970 (SELECT XMLAGG(ssub) FROM (
6971 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6972 FROM serial.subscription
6973 WHERE record_entry = $1
6977 CASE WHEN ('acp' = ANY ($5)) THEN
6979 name foreign_copies,
6980 (SELECT XMLAGG(acp) FROM (
6981 SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6982 FROM biblio.peer_bib_copy_map p
6983 JOIN asset.copy c ON (p.target_copy = c.id)
6984 WHERE NOT c.deleted AND peer_record = $1
6991 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$
6995 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6996 'tag:open-ils.org:U2@ssub/' || id AS id,
6997 start_date AS start, end_date AS end, expected_date_offset
6999 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
7000 XMLELEMENT( name distributions,
7002 WHEN ('sdist' = ANY ($4)) THEN
7003 (SELECT XMLAGG(sdist) FROM (
7004 SELECT unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
7005 FROM serial.distribution
7006 WHERE subscription = ssub.id
7012 FROM serial.subscription ssub
7014 GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
7017 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$
7021 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7022 'tag:open-ils.org:U2@sdist/' || id AS id,
7023 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
7024 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
7025 unit_label_prefix, label, unit_label_suffix, summary_method
7027 unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
7028 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,
7029 XMLELEMENT( name streams,
7031 WHEN ('sstr' = ANY ($4)) THEN
7032 (SELECT XMLAGG(sstr) FROM (
7033 SELECT unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7035 WHERE distribution = sdist.id
7040 XMLELEMENT( name summaries,
7042 WHEN ('ssum' = ANY ($4)) THEN
7043 (SELECT XMLAGG(sbsum) FROM (
7044 SELECT unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7045 FROM serial.basic_summary
7046 WHERE distribution = sdist.id
7051 WHEN ('ssum' = ANY ($4)) THEN
7052 (SELECT XMLAGG(sisum) FROM (
7053 SELECT unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7054 FROM serial.index_summary
7055 WHERE distribution = sdist.id
7060 WHEN ('ssum' = ANY ($4)) THEN
7061 (SELECT XMLAGG(sssum) FROM (
7062 SELECT unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7063 FROM serial.supplement_summary
7064 WHERE distribution = sdist.id
7070 FROM serial.distribution sdist
7072 GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
7075 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$
7079 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7080 'tag:open-ils.org:U2@sstr/' || id AS id,
7083 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,
7084 XMLELEMENT( name items,
7086 WHEN ('sitem' = ANY ($4)) THEN
7087 (SELECT XMLAGG(sitem) FROM (
7088 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
7090 WHERE stream = sstr.id
7096 FROM serial.stream sstr
7098 GROUP BY id, routing_label, distribution;
7101 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$
7105 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7106 'tag:open-ils.org:U2@siss/' || id AS id,
7107 create_date, edit_date, label, date_published,
7108 holding_code, holding_type, holding_link_id
7110 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,
7111 XMLELEMENT( name items,
7113 WHEN ('sitem' = ANY ($4)) THEN
7114 (SELECT XMLAGG(sitem) FROM (
7115 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
7117 WHERE issuance = sstr.id
7123 FROM serial.issuance sstr
7125 GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
7128 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$
7132 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7133 'tag:open-ils.org:U2@sitem/' || id AS id,
7134 'tag:open-ils.org:U2@siss/' || issuance AS issuance,
7135 date_expected, date_received
7137 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,
7138 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,
7139 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,
7140 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
7141 -- XMLELEMENT( name notes,
7143 -- WHEN ('acpn' = ANY ($4)) THEN
7144 -- (SELECT XMLAGG(acpn) FROM (
7145 -- SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
7146 -- FROM asset.copy_note
7147 -- WHERE owning_copy = cp.id AND pub
7153 FROM serial.item sitem
7158 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$
7160 name monograph_part,
7162 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7163 'tag:open-ils.org:U2@bmp/' || id AS id,
7167 'tag:open-ils.org:U2@bre/' || record AS record
7170 WHEN ('acp' = ANY ($4)) THEN
7171 XMLELEMENT( name copies,
7172 (SELECT XMLAGG(acp) FROM (
7173 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
7175 JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
7177 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
7184 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
7186 FROM biblio.monograph_part
7188 GROUP BY id, label, label_sortkey, record;
7191 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$
7195 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7196 'tag:open-ils.org:U2@acp/' || id AS id,
7197 create_date, edit_date, copy_number, circulate, deposit,
7198 ref, holdable, deleted, deposit_amount, price, barcode,
7199 circ_modifier, circ_as_type, opac_visible
7201 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7202 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7203 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7204 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7205 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,
7206 XMLELEMENT( name copy_notes,
7208 WHEN ('acpn' = ANY ($4)) THEN
7209 (SELECT XMLAGG(acpn) FROM (
7210 SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7211 FROM asset.copy_note
7212 WHERE owning_copy = cp.id AND pub
7217 XMLELEMENT( name statcats,
7219 WHEN ('ascecm' = ANY ($4)) THEN
7220 (SELECT XMLAGG(ascecm) FROM (
7221 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7222 FROM asset.stat_cat_entry_copy_map
7223 WHERE owning_copy = cp.id
7228 XMLELEMENT( name foreign_records,
7230 WHEN ('bre' = ANY ($4)) THEN
7231 (SELECT XMLAGG(bre) FROM (
7232 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7233 FROM biblio.peer_bib_copy_map
7234 WHERE target_copy = cp.id
7241 WHEN ('bmp' = ANY ($4)) THEN
7242 XMLELEMENT( name monograph_parts,
7243 (SELECT XMLAGG(bmp) FROM (
7244 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7245 FROM asset.copy_part_map
7246 WHERE target_copy = cp.id
7254 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;
7257 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$
7261 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7262 'tag:open-ils.org:U2@acp/' || id AS id,
7263 create_date, edit_date, copy_number, circulate, deposit,
7264 ref, holdable, deleted, deposit_amount, price, barcode,
7265 circ_modifier, circ_as_type, opac_visible, status_changed_time,
7266 floating, mint_condition, detailed_contents, sort_key, summary_contents, cost
7268 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),
7269 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),
7270 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),
7271 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),
7272 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,
7273 XMLELEMENT( name copy_notes,
7275 WHEN ('acpn' = ANY ($4)) THEN
7276 (SELECT XMLAGG(acpn) FROM (
7277 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)
7278 FROM asset.copy_note
7279 WHERE owning_copy = cp.id AND pub
7284 XMLELEMENT( name statcats,
7286 WHEN ('ascecm' = ANY ($4)) THEN
7287 (SELECT XMLAGG(ascecm) FROM (
7288 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7289 FROM asset.stat_cat_entry_copy_map
7290 WHERE owning_copy = cp.id
7295 XMLELEMENT( name foreign_records,
7297 WHEN ('bre' = ANY ($4)) THEN
7298 (SELECT XMLAGG(bre) FROM (
7299 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7300 FROM biblio.peer_bib_copy_map
7301 WHERE target_copy = cp.id
7308 WHEN ('bmp' = ANY ($4)) THEN
7309 XMLELEMENT( name monograph_parts,
7310 (SELECT XMLAGG(bmp) FROM (
7311 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7312 FROM asset.copy_part_map
7313 WHERE target_copy = cp.id
7321 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
7322 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;
7325 INSERT INTO config.upgrade_log (version) VALUES ('0568'); -- miker for tsbere
7327 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7331 add_base_query TEXT;
7332 add_peer_query TEXT;
7334 do_add BOOLEAN := false;
7335 do_remove BOOLEAN := false;
7337 add_base_query := $$
7338 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7340 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7341 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7342 JOIN asset.copy_location cl ON (cp.location = cl.id)
7343 JOIN config.copy_status cs ON (cp.status = cs.id)
7344 JOIN biblio.record_entry b ON (cn.record = b.id)
7345 WHERE NOT cp.deleted
7353 add_peer_query := $$
7354 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7356 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7357 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7358 JOIN asset.copy_location cl ON (cp.location = cl.id)
7359 JOIN config.copy_status cs ON (cp.status = cs.id)
7360 WHERE NOT cp.deleted
7367 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7368 SELECT id, circ_lib, record FROM (
7374 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7376 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7377 IF TG_OP = 'INSERT' THEN
7378 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7379 EXECUTE add_front || add_peer_query || add_back;
7382 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7383 EXECUTE remove_query;
7388 IF TG_OP = 'INSERT' THEN
7390 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7391 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7392 EXECUTE add_front || add_base_query || add_back;
7399 -- handle items first, since with circulation activity
7400 -- their statuses change frequently
7401 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7403 IF OLD.location <> NEW.location OR
7404 OLD.call_number <> NEW.call_number OR
7405 OLD.status <> NEW.status OR
7406 OLD.circ_lib <> NEW.circ_lib THEN
7407 -- any of these could change visibility, but
7408 -- we'll save some queries and not try to calculate
7409 -- the change directly
7414 IF OLD.deleted <> NEW.deleted THEN
7422 IF OLD.opac_visible <> NEW.opac_visible THEN
7423 IF OLD.opac_visible THEN
7425 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7426 -- is also marked opac_visible
7434 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7437 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7438 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7439 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7446 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7448 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7452 ELSIF NEW.deleted THEN -- remove rows
7454 IF TG_TABLE_NAME = 'call_number' THEN
7455 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7456 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7457 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7462 ELSIF OLD.deleted THEN -- add rows
7464 IF TG_TABLE_NAME = 'call_number' THEN
7465 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7466 EXECUTE add_front || add_base_query || add_back;
7467 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7468 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7469 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7470 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7479 IF TG_TABLE_NAME = 'call_number' THEN
7481 IF OLD.record <> NEW.record THEN
7482 -- call number is linked to different bib
7483 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7484 EXECUTE remove_query;
7485 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7486 EXECUTE add_front || add_base_query || add_back;
7493 IF TG_TABLE_NAME IN ('record_entry') THEN
7494 RETURN NEW; -- don't have 'opac_visible'
7497 -- actor.org_unit, asset.copy_location, asset.copy_status
7498 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7502 ELSIF NEW.opac_visible THEN -- add rows
7504 IF TG_TABLE_NAME = 'org_unit' THEN
7505 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7506 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7507 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7508 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id || ';';
7509 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id || ';';
7510 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7511 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id || ';';
7512 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id || ';';
7515 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7519 IF TG_TABLE_NAME = 'org_unit' THEN
7520 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7521 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7522 remove_query := remove_query || 'location = ' || NEW.id || ');';
7523 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7524 remove_query := remove_query || 'status = ' || NEW.id || ');';
7527 EXECUTE remove_query;
7533 $func$ LANGUAGE PLPGSQL;
7535 INSERT INTO config.upgrade_log (version) VALUES ('0569'); --miker
7537 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$
7541 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7542 'tag:open-ils.org:U2@auri/' || uri.id AS id,
7547 XMLELEMENT( name copies,
7549 WHEN ('acn' = ANY ($4)) THEN
7550 (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)
7557 GROUP BY uri.id, use_restriction, href, label;
7560 INSERT INTO config.upgrade_log (version) VALUES ('0570');
7562 -- Not everything in 1XX tags should become part of the authorsort field
7563 -- ($0 for example). The list of subfields chosen here is a superset of all
7564 -- the fields found in the LoC authority mappin definitions for 1XX fields.
7565 -- Anyway, if more fields should be here, add them.
7567 UPDATE config.record_attr_definition
7568 SET sf_list = 'abcdefgklmnopqrstvxyz'
7569 WHERE name='authorsort' AND sf_list IS NULL;
7571 INSERT INTO config.upgrade_log (version) VALUES ('0571');
7573 -- FIXME: add/check SQL statements to perform the upgrade
7574 CREATE OR REPLACE FUNCTION metabib.facet_normalize_trigger () RETURNS TRIGGER AS $$
7579 facet_text := NEW.value;
7582 SELECT n.func AS func,
7583 n.param_count AS param_count,
7585 FROM config.index_normalizer n
7586 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7587 WHERE m.field = NEW.field AND m.pos < 0
7590 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7591 quote_literal( facet_text ) ||
7593 WHEN normalizer.param_count > 0
7594 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7597 ')' INTO facet_text;
7601 NEW.value = facet_text;
7605 $$ LANGUAGE PLPGSQL;
7607 CREATE TRIGGER facet_normalize_tgr
7608 BEFORE UPDATE OR INSERT ON metabib.facet_entry
7609 FOR EACH ROW EXECUTE PROCEDURE metabib.facet_normalize_trigger();
7613 INSERT INTO config.upgrade_log (version) VALUES ('0578'); -- tsbere via miker
7615 CREATE OR REPLACE VIEW reporter.hold_request_record AS
7620 WHEN hold_type = 'T'
7622 WHEN hold_type = 'I'
7623 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
7624 WHEN hold_type = 'V'
7625 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
7626 WHEN hold_type IN ('C','R','F')
7627 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
7628 WHEN hold_type = 'M'
7629 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
7630 WHEN hold_type = 'P'
7631 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
7633 FROM action.hold_request ahr;
7635 INSERT INTO config.upgrade_log (version) VALUES ('0583');
7637 CREATE OR REPLACE VIEW action.all_circulation AS
7638 SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
7639 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
7640 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
7641 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
7642 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
7643 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
7644 FROM action.aged_circulation
7646 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,
7647 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,
7648 cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
7649 circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
7650 circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
7651 circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
7653 FROM action.circulation circ
7654 JOIN asset.copy cp ON (circ.target_copy = cp.id)
7655 JOIN asset.call_number cn ON (cp.call_number = cn.id)
7656 JOIN actor.usr p ON (circ.usr = p.id)
7657 LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
7658 LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
7662 INSERT INTO config.upgrade_log (version) VALUES ('0590'); -- miker/tsbere
7664 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7668 add_base_query TEXT;
7669 add_peer_query TEXT;
7671 do_add BOOLEAN := false;
7672 do_remove BOOLEAN := false;
7674 add_base_query := $$
7675 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7677 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7678 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7679 JOIN asset.copy_location cl ON (cp.location = cl.id)
7680 JOIN config.copy_status cs ON (cp.status = cs.id)
7681 JOIN biblio.record_entry b ON (cn.record = b.id)
7682 WHERE NOT cp.deleted
7690 add_peer_query := $$
7691 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7693 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7694 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7695 JOIN asset.copy_location cl ON (cp.location = cl.id)
7696 JOIN config.copy_status cs ON (cp.status = cs.id)
7697 WHERE NOT cp.deleted
7704 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7705 SELECT id, circ_lib, record FROM (
7711 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7713 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7714 IF TG_OP = 'INSERT' THEN
7715 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7716 EXECUTE add_front || add_peer_query || add_back;
7719 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7720 EXECUTE remove_query;
7725 IF TG_OP = 'INSERT' THEN
7727 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7728 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7729 EXECUTE add_front || add_base_query || add_back;
7736 -- handle items first, since with circulation activity
7737 -- their statuses change frequently
7738 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7740 IF OLD.location <> NEW.location OR
7741 OLD.call_number <> NEW.call_number OR
7742 OLD.status <> NEW.status OR
7743 OLD.circ_lib <> NEW.circ_lib THEN
7744 -- any of these could change visibility, but
7745 -- we'll save some queries and not try to calculate
7746 -- the change directly
7751 IF OLD.deleted <> NEW.deleted THEN
7759 IF OLD.opac_visible <> NEW.opac_visible THEN
7760 IF OLD.opac_visible THEN
7762 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7763 -- is also marked opac_visible
7771 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7774 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7775 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7776 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7783 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7785 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7789 ELSIF NEW.deleted THEN -- remove rows
7791 IF TG_TABLE_NAME = 'call_number' THEN
7792 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7793 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7794 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7799 ELSIF OLD.deleted THEN -- add rows
7801 IF TG_TABLE_NAME = 'call_number' THEN
7802 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7803 EXECUTE add_front || add_base_query || add_back;
7804 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7805 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7806 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7807 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7816 IF TG_TABLE_NAME = 'call_number' THEN
7818 IF OLD.record <> NEW.record THEN
7819 -- call number is linked to different bib
7820 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7821 EXECUTE remove_query;
7822 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7823 EXECUTE add_front || add_base_query || add_back;
7830 IF TG_TABLE_NAME IN ('record_entry') THEN
7831 RETURN NEW; -- don't have 'opac_visible'
7834 -- actor.org_unit, asset.copy_location, asset.copy_status
7835 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7839 ELSIF NEW.opac_visible THEN -- add rows
7841 IF TG_TABLE_NAME = 'org_unit' THEN
7842 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
7843 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
7844 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7845 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
7846 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
7847 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7848 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
7849 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
7852 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7856 IF TG_TABLE_NAME = 'org_unit' THEN
7857 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7858 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7859 remove_query := remove_query || 'location = ' || NEW.id || ');';
7860 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7861 remove_query := remove_query || 'status = ' || NEW.id || ');';
7864 EXECUTE remove_query;
7870 $func$ LANGUAGE PLPGSQL;
7872 INSERT INTO config.upgrade_log (version) VALUES ('0591'); -- berick/miker
7874 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
7876 c action.circulation%ROWTYPE;
7878 usr_view_age actor.usr_setting%ROWTYPE;
7879 usr_view_start actor.usr_setting%ROWTYPE;
7881 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
7882 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
7884 IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
7885 -- User opted in and supplied a retention age
7886 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7887 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7889 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7891 ELSIF usr_view_start.value IS NOT NULL THEN
7893 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7895 -- User did not opt in
7901 FROM action.circulation
7903 AND parent_circ IS NULL
7904 AND xact_start > NOW() - view_age
7905 ORDER BY xact_start DESC
7912 $func$ LANGUAGE PLPGSQL;
7914 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
7916 h action.hold_request%ROWTYPE;
7919 usr_view_count actor.usr_setting%ROWTYPE;
7920 usr_view_age actor.usr_setting%ROWTYPE;
7921 usr_view_start actor.usr_setting%ROWTYPE;
7923 SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
7924 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
7925 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
7929 FROM action.hold_request
7931 AND fulfillment_time IS NULL
7932 AND cancel_time IS NULL
7933 ORDER BY request_time DESC
7938 IF usr_view_start.value IS NULL THEN
7942 IF usr_view_age.value IS NOT NULL THEN
7943 -- User opted in and supplied a retention age
7944 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7945 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7947 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7951 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7954 IF usr_view_count.value IS NOT NULL THEN
7955 view_count := oils_json_to_text(usr_view_count.value)::INT;
7960 -- show some fulfilled/canceled holds
7963 FROM action.hold_request
7965 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
7966 AND request_time > NOW() - view_age
7967 ORDER BY request_time DESC
7975 $func$ LANGUAGE PLPGSQL;
7977 INSERT INTO config.upgrade_log (version) VALUES ('0599'); -- miker/gmc
7979 UPDATE config.metabib_field
7980 SET xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role/mods32:roleTerm[text()='creator'])]$$
7981 WHERE field_class = 'author'
7983 AND xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role)]$$
7984 AND format = 'mods32';
7986 \qecho To reindex bibs that use the author|other index definition,
7987 \qecho you can run something like this:
7989 \qecho SELECT metabib.reingest_metabib_field_entries(record)
7991 \qecho SELECT DISTINCT record
7992 \qecho FROM metabib.real_full_rec
7993 \qecho WHERE tag IN ('600', '700', '720', '800')
7994 \qecho AND subfield IN ('4', 'e')
7997 -- Resolves an error in calculating copy counts for org lassos
7999 INSERT INTO config.upgrade_log (version) VALUES ('0603');
8001 -- FIXME: add/check SQL statements to perform the upgrade
8002 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$
8007 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;
8009 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
8014 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
8018 actor.org_unit_descendants(ans.id) d
8019 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
8020 JOIN asset.copy cp ON (cp.id = av.copy_id)
8024 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8031 $f$ LANGUAGE PLPGSQL;
8034 -- Staff record copy counts also triggered an SQL error for org lassos
8037 INSERT INTO config.upgrade_log (version) VALUES ('0604');
8039 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$
8044 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;
8046 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
8051 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
8055 actor.org_unit_descendants(ans.id) d
8056 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
8057 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
8061 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8068 $f$ LANGUAGE PLPGSQL;
8070 INSERT INTO config.upgrade_log (version) VALUES ('0614'); --miker/phasefx
8072 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
8076 add_base_query TEXT;
8077 add_peer_query TEXT;
8079 do_add BOOLEAN := false;
8080 do_remove BOOLEAN := false;
8082 add_base_query := $$
8083 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
8085 JOIN asset.call_number cn ON (cn.id = cp.call_number)
8086 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8087 JOIN asset.copy_location cl ON (cp.location = cl.id)
8088 JOIN config.copy_status cs ON (cp.status = cs.id)
8089 JOIN biblio.record_entry b ON (cn.record = b.id)
8090 WHERE NOT cp.deleted
8098 add_peer_query := $$
8099 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
8101 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
8102 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8103 JOIN asset.copy_location cl ON (cp.location = cl.id)
8104 JOIN config.copy_status cs ON (cp.status = cs.id)
8105 WHERE NOT cp.deleted
8112 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
8113 SELECT id, circ_lib, record FROM (
8119 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
8121 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
8122 IF TG_OP = 'INSERT' THEN
8123 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.peer_record = ' || NEW.peer_record;
8124 EXECUTE add_front || add_peer_query || add_back;
8127 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
8128 EXECUTE remove_query;
8133 IF TG_OP = 'INSERT' THEN
8135 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8136 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8137 EXECUTE add_front || add_base_query || add_back;
8144 -- handle items first, since with circulation activity
8145 -- their statuses change frequently
8146 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8148 IF OLD.location <> NEW.location OR
8149 OLD.call_number <> NEW.call_number OR
8150 OLD.status <> NEW.status OR
8151 OLD.circ_lib <> NEW.circ_lib THEN
8152 -- any of these could change visibility, but
8153 -- we'll save some queries and not try to calculate
8154 -- the change directly
8159 IF OLD.deleted <> NEW.deleted THEN
8167 IF OLD.opac_visible <> NEW.opac_visible THEN
8168 IF OLD.opac_visible THEN
8170 ELSIF NOT do_remove THEN -- handle edge case where deleted item
8171 -- is also marked opac_visible
8179 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
8182 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8183 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
8184 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8191 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
8193 IF OLD.deleted AND NEW.deleted THEN -- do nothing
8197 ELSIF NEW.deleted THEN -- remove rows
8199 IF TG_TABLE_NAME = 'call_number' THEN
8200 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
8201 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8202 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
8207 ELSIF OLD.deleted THEN -- add rows
8209 IF TG_TABLE_NAME = 'call_number' THEN
8210 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8211 EXECUTE add_front || add_base_query || add_back;
8212 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8213 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
8214 add_peer_query := add_peer_query || ' AND pbcm.peer_record = ' || NEW.id;
8215 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8224 IF TG_TABLE_NAME = 'call_number' THEN
8226 IF OLD.record <> NEW.record THEN
8227 -- call number is linked to different bib
8228 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
8229 EXECUTE remove_query;
8230 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8231 EXECUTE add_front || add_base_query || add_back;
8238 IF TG_TABLE_NAME IN ('record_entry') THEN
8239 RETURN NEW; -- don't have 'opac_visible'
8242 -- actor.org_unit, asset.copy_location, asset.copy_status
8243 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
8247 ELSIF NEW.opac_visible THEN -- add rows
8249 IF TG_TABLE_NAME = 'org_unit' THEN
8250 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
8251 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
8252 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8253 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
8254 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
8255 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8256 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
8257 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
8260 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8264 IF TG_TABLE_NAME = 'org_unit' THEN
8265 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
8266 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8267 remove_query := remove_query || 'location = ' || NEW.id || ');';
8268 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8269 remove_query := remove_query || 'status = ' || NEW.id || ');';
8272 EXECUTE remove_query;
8278 $func$ LANGUAGE PLPGSQL;
8280 INSERT INTO config.upgrade_log (version) VALUES ('0579'); -- superceded by 0620
8281 INSERT INTO config.upgrade_log (version) VALUES ('0620'); -- tsbere via miker
8283 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$
8285 user_object actor.usr%ROWTYPE;
8286 standing_penalty config.standing_penalty%ROWTYPE;
8287 item_object asset.copy%ROWTYPE;
8288 item_status_object config.copy_status%ROWTYPE;
8289 item_location_object asset.copy_location%ROWTYPE;
8290 result action.circ_matrix_test_result;
8291 circ_test action.found_circ_matrix_matchpoint;
8292 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
8293 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
8294 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
8295 hold_ratio action.hold_stats%ROWTYPE;
8298 context_org_list INT[];
8301 -- Assume success unless we hit a failure condition
8302 result.success := TRUE;
8304 -- Need user info to look up matchpoints
8305 SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
8307 -- (Insta)Fail if we couldn't find the user
8308 IF user_object.id IS NULL THEN
8309 result.fail_part := 'no_user';
8310 result.success := FALSE;
8316 -- Need item info to look up matchpoints
8317 SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
8319 -- (Insta)Fail if we couldn't find the item
8320 IF item_object.id IS NULL THEN
8321 result.fail_part := 'no_item';
8322 result.success := FALSE;
8328 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
8330 circ_matchpoint := circ_test.matchpoint;
8331 result.matchpoint := circ_matchpoint.id;
8332 result.circulate := circ_matchpoint.circulate;
8333 result.duration_rule := circ_matchpoint.duration_rule;
8334 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
8335 result.max_fine_rule := circ_matchpoint.max_fine_rule;
8336 result.hard_due_date := circ_matchpoint.hard_due_date;
8337 result.renewals := circ_matchpoint.renewals;
8338 result.grace_period := circ_matchpoint.grace_period;
8339 result.buildrows := circ_test.buildrows;
8341 -- (Insta)Fail if we couldn't find a matchpoint
8342 IF circ_test.success = false THEN
8343 result.fail_part := 'no_matchpoint';
8344 result.success := FALSE;
8350 -- All failures before this point are non-recoverable
8351 -- Below this point are possibly overridable failures
8353 -- Fail if the user is barred
8354 IF user_object.barred IS TRUE THEN
8355 result.fail_part := 'actor.usr.barred';
8356 result.success := FALSE;
8361 -- Fail if the item can't circulate
8362 IF item_object.circulate IS FALSE THEN
8363 result.fail_part := 'asset.copy.circulate';
8364 result.success := FALSE;
8369 -- Fail if the item isn't in a circulateable status on a non-renewal
8370 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
8371 result.fail_part := 'asset.copy.status';
8372 result.success := FALSE;
8375 -- Alternately, fail if the item isn't checked out on a renewal
8376 ELSIF renewal AND item_object.status <> 1 THEN
8377 result.fail_part := 'asset.copy.status';
8378 result.success := FALSE;
8383 -- Fail if the item can't circulate because of the shelving location
8384 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8385 IF item_location_object.circulate IS FALSE THEN
8386 result.fail_part := 'asset.copy_location.circulate';
8387 result.success := FALSE;
8392 -- Use Circ OU for penalties and such
8393 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_ou );
8396 penalty_type = '%RENEW%';
8398 penalty_type = '%CIRC%';
8401 FOR standing_penalty IN
8402 SELECT DISTINCT csp.*
8403 FROM actor.usr_standing_penalty usp
8404 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8405 WHERE usr = match_user
8406 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8407 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8408 AND csp.block_list LIKE penalty_type LOOP
8410 result.fail_part := standing_penalty.name;
8411 result.success := FALSE;
8416 -- Fail if the test is set to hard non-circulating
8417 IF circ_matchpoint.circulate IS FALSE THEN
8418 result.fail_part := 'config.circ_matrix_test.circulate';
8419 result.success := FALSE;
8424 -- Fail if the total copy-hold ratio is too low
8425 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
8426 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8427 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
8428 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8429 result.success := FALSE;
8435 -- Fail if the available copy-hold ratio is too low
8436 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
8437 IF hold_ratio.hold_count IS NULL THEN
8438 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8440 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
8441 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8442 result.success := FALSE;
8448 -- Fail if the user has too many items with specific circ_modifiers checked out
8449 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
8450 SELECT INTO items_out COUNT(*)
8451 FROM action.circulation circ
8452 JOIN asset.copy cp ON (cp.id = circ.target_copy)
8453 WHERE circ.usr = match_user
8454 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8455 AND circ.checkin_time IS NULL
8456 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8457 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);
8458 IF items_out >= out_by_circ_mod.items_out THEN
8459 result.fail_part := 'config.circ_matrix_circ_mod_test';
8460 result.success := FALSE;
8466 -- If we passed everything, return the successful matchpoint
8473 $func$ LANGUAGE plpgsql;
8477 INSERT INTO config.upgrade_log (version) VALUES ('0628');
8479 -- acq.fund_combined_balance and acq.fund_spent_balance are unchanged,
8480 -- however we need to drop them to recreate the other views.
8481 -- we need to drop all our views because we change the number of columns
8482 -- for example, debit_total does not need an encumberance column when we
8483 -- have a sepearate total for that.
8485 DROP VIEW acq.fund_spent_balance;
8486 DROP VIEW acq.fund_combined_balance;
8487 DROP VIEW acq.fund_encumbrance_total;
8488 DROP VIEW acq.fund_spent_total;
8489 DROP VIEW acq.fund_debit_total;
8491 CREATE OR REPLACE VIEW acq.fund_debit_total AS
8492 SELECT fund.id AS fund,
8493 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8495 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8498 CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS
8501 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8503 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8504 WHERE fund_debit.encumbrance GROUP BY fund.id;
8506 CREATE OR REPLACE VIEW acq.fund_spent_total AS
8507 SELECT fund.id AS fund,
8508 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8510 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8511 WHERE NOT fund_debit.encumbrance
8514 CREATE OR REPLACE VIEW acq.fund_combined_balance AS
8516 c.amount - COALESCE(d.amount, 0.0) AS amount
8517 FROM acq.fund_allocation_total c
8518 LEFT JOIN acq.fund_debit_total d USING (fund);
8520 CREATE OR REPLACE VIEW acq.fund_spent_balance AS
8522 c.amount - COALESCE(d.amount,0.0) AS amount
8523 FROM acq.fund_allocation_total c
8524 LEFT JOIN acq.fund_spent_total d USING (fund);
8528 INSERT INTO config.upgrade_log (version) VALUES ('0631');
8530 CREATE OR REPLACE FUNCTION search.query_parser_fts (
8532 param_search_ou INT,
8535 param_statuses INT[],
8536 param_locations INT[],
8543 ) RETURNS SETOF search.search_result AS $func$
8546 current_res search.search_result%ROWTYPE;
8547 search_org_list INT[];
8548 luri_org_list INT[];
8557 core_cursor REFCURSOR;
8558 core_rel_query TEXT;
8560 total_count INT := 0;
8561 check_count INT := 0;
8562 deleted_count INT := 0;
8563 visible_count INT := 0;
8564 excluded_count INT := 0;
8568 check_limit := COALESCE( param_check, 1000 );
8569 core_limit := COALESCE( param_limit, 25000 );
8570 core_offset := COALESCE( param_offset, 0 );
8572 -- core_skip_chk := COALESCE( param_skip_chk, 1 );
8574 IF param_search_ou > 0 THEN
8575 IF param_depth IS NOT NULL THEN
8576 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
8578 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
8581 SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
8583 ELSIF param_search_ou < 0 THEN
8584 SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
8586 FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
8587 SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
8588 luri_org_list := luri_org_list || tmp_int_list;
8591 SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
8593 ELSIF param_search_ou = 0 THEN
8594 -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
8597 OPEN core_cursor FOR EXECUTE param_query;
8601 FETCH core_cursor INTO core_result;
8602 EXIT WHEN NOT FOUND;
8603 EXIT WHEN total_count >= core_limit;
8605 total_count := total_count + 1;
8607 CONTINUE WHEN total_count NOT BETWEEN core_offset + 1 AND check_limit + core_offset;
8609 check_count := check_count + 1;
8611 PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8613 -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
8614 deleted_count := deleted_count + 1;
8619 FROM biblio.record_entry b
8620 JOIN config.bib_source s ON (b.source = s.id)
8621 WHERE s.transcendant
8622 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8625 -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
8626 visible_count := visible_count + 1;
8628 current_res.id = core_result.id;
8629 current_res.rel = core_result.rel;
8633 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8637 current_res.record = core_result.records[1];
8639 current_res.record = NULL;
8642 RETURN NEXT current_res;
8648 FROM asset.call_number cn
8649 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
8650 JOIN asset.uri uri ON (map.uri = uri.id)
8651 WHERE NOT cn.deleted
8652 AND cn.label = '##URI##'
8654 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
8655 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8656 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
8660 -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
8661 visible_count := visible_count + 1;
8663 current_res.id = core_result.id;
8664 current_res.rel = core_result.rel;
8668 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8672 current_res.record = core_result.records[1];
8674 current_res.record = NULL;
8677 RETURN NEXT current_res;
8682 IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
8685 FROM asset.call_number cn
8686 JOIN asset.copy cp ON (cp.call_number = cn.id)
8687 WHERE NOT cn.deleted
8689 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8690 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8691 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8696 FROM biblio.peer_bib_copy_map pr
8697 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8698 WHERE NOT cp.deleted
8699 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8700 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8701 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8705 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
8706 excluded_count := excluded_count + 1;
8713 IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
8716 FROM asset.call_number cn
8717 JOIN asset.copy cp ON (cp.call_number = cn.id)
8718 WHERE NOT cn.deleted
8720 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8721 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8722 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8727 FROM biblio.peer_bib_copy_map pr
8728 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8729 WHERE NOT cp.deleted
8730 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8731 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8732 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8736 -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
8737 excluded_count := excluded_count + 1;
8744 IF staff IS NULL OR NOT staff THEN
8747 FROM asset.opac_visible_copies
8748 WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8749 AND record IN ( SELECT * FROM unnest( core_result.records ) )
8754 FROM biblio.peer_bib_copy_map pr
8755 JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
8756 WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8757 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8762 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8763 excluded_count := excluded_count + 1;
8771 FROM asset.call_number cn
8772 JOIN asset.copy cp ON (cp.call_number = cn.id)
8773 WHERE NOT cn.deleted
8775 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8776 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8782 FROM biblio.peer_bib_copy_map pr
8783 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8784 WHERE NOT cp.deleted
8785 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8786 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8792 FROM asset.call_number cn
8793 JOIN asset.copy cp ON (cp.call_number = cn.id)
8794 WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8799 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8800 excluded_count := excluded_count + 1;
8809 visible_count := visible_count + 1;
8811 current_res.id = core_result.id;
8812 current_res.rel = core_result.rel;
8816 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8820 current_res.record = core_result.records[1];
8822 current_res.record = NULL;
8825 RETURN NEXT current_res;
8827 IF visible_count % 1000 = 0 THEN
8828 -- RAISE NOTICE ' % visible so far ... ', visible_count;
8833 current_res.id = NULL;
8834 current_res.rel = NULL;
8835 current_res.record = NULL;
8836 current_res.total = total_count;
8837 current_res.checked = check_count;
8838 current_res.deleted = deleted_count;
8839 current_res.visible = visible_count;
8840 current_res.excluded = excluded_count;
8844 RETURN NEXT current_res;
8847 $func$ LANGUAGE PLPGSQL;
8850 INSERT INTO config.upgrade_log (version) VALUES ('0633');
8851 INSERT INTO config.upgrade_log (version) VALUES ('0634');
8856 INSERT into config.org_unit_setting_type
8857 ( name, grp, label, description, datatype ) VALUES
8859 'print.custom_js_file', 'circ',
8861 'print.custom_js_file',
8862 'Printing: Custom Javascript File',
8867 'print.custom_js_file',
8868 'Full URL path to a Javascript File to be loaded when printing. Should'
8869 || ' implement a print_custom function for DOM manipulation. Can change'
8870 || ' the value of the do_print variable to false to cancel printing.',
8879 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8880 ( 513, 'DEBUG_CLIENT', oils_i18n_gettext( 513,
8881 'Allows a user to use debug functions in the staff client', 'ppl', 'description' ));
8883 UPDATE asset.call_number SET id = id WHERE deleted IS FALSE OR deleted = FALSE;
8886 INSERT INTO config.org_unit_setting_type
8887 ( name, label, description, datatype ) VALUES
8888 ( 'circ.user_merge.delete_addresses',
8889 'Circ: Patron Merge Address Delete',
8890 'Delete address(es) of subordinate user(s) in a patron merge',
8894 INSERT INTO config.org_unit_setting_type
8895 ( name, label, description, datatype ) VALUES
8896 ( 'circ.user_merge.delete_cards',
8897 'Circ: Patron Merge Barcode Delete',
8898 'Delete barcode(s) of subordinate user(s) in a patron merge',
8902 INSERT INTO config.org_unit_setting_type
8903 ( name, label, description, datatype ) VALUES
8904 ( 'circ.user_merge.deactivate_cards',
8905 'Circ: Patron Merge Deactivate Card',
8906 'Mark barcode(s) of subordinate user(s) in a patron merge as inactive',
8910 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.cash_payment;
8911 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.cash_payment;
8912 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.cash_payment;
8914 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('cash_payment');
8915 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('cash_payment');
8916 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('cash_payment');
8918 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.check_payment;
8919 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.check_payment;
8920 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.check_payment;
8922 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('check_payment');
8923 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('check_payment');
8924 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('check_payment');
8927 UPDATE metabib.record_attr
8928 SET attrs = attrs || asort
8929 FROM (SELECT record,
8930 HSTORE('authorsort',FIRST(value)) AS asort
8931 FROM metabib.full_rec
8934 WHERE x.record = metabib.record_attr.id;
8936 UPDATE metabib.record_attr
8937 SET attrs = attrs || tsort
8938 FROM (SELECT record,
8939 HSTORE('titlesort',FIRST(value)) AS tsort
8940 FROM metabib.full_rec
8943 WHERE x.record = metabib.record_attr.id;
8945 UPDATE metabib.record_attr
8946 SET attrs = attrs || ('pubdate' => (attrs->'date1'))
8947 WHERE defined(attrs, 'pubdate') IS FALSE
8948 AND defined(attrs, 'date1') IS TRUE;