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;
4324 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
4331 uri_owner_list TEXT[];
4339 -- Clear any URI mappings and call numbers for this bib.
4340 -- This leads to acn / auricnm inflation, but also enables
4341 -- old acn/auricnm's to go away and for bibs to be deleted.
4342 FOR uri_cn_id IN SELECT id FROM asset.call_number WHERE record = bib_id AND label = '##URI##' AND NOT deleted LOOP
4343 DELETE FROM asset.uri_call_number_map WHERE call_number = uri_cn_id;
4344 DELETE FROM asset.call_number WHERE id = uri_cn_id;
4347 uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
4348 IF ARRAY_UPPER(uris,1) > 0 THEN
4349 FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
4350 -- First we pull info out of the 856
4353 uri_href := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
4354 uri_label := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
4355 uri_use := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];
4356 CONTINUE WHEN uri_href IS NULL OR uri_label IS NULL;
4358 -- Get the distinct list of libraries wanting to use
4360 DISTINCT REGEXP_REPLACE(
4362 $re$^.*?\((\w+)\).*$$re$,
4365 ) INTO uri_owner_list
4368 '//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',
4373 IF ARRAY_UPPER(uri_owner_list,1) > 0 THEN
4375 -- look for a matching uri
4376 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
4377 IF NOT FOUND THEN -- create one
4378 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
4379 IF uri_use IS NULL THEN
4380 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active;
4382 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
4386 FOR j IN 1 .. ARRAY_UPPER(uri_owner_list, 1) LOOP
4387 uri_owner := uri_owner_list[j];
4389 SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
4390 CONTINUE WHEN NOT FOUND;
4392 -- we need a call number to link through
4393 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;
4395 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
4396 VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
4397 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;
4400 -- now, link them if they're not already
4401 SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
4403 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
4415 $func$ LANGUAGE PLPGSQL;
4419 UPDATE config.org_unit_setting_type SET datatype = 'string' WHERE name = 'ui.general.button_bar';
4421 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');
4423 UPDATE actor.org_unit_setting SET value='"circ"' WHERE name = 'ui.general.button_bar' AND value='true';
4425 UPDATE actor.org_unit_setting SET value='"none"' WHERE name = 'ui.general.button_bar' AND value='false';
4429 INSERT into config.org_unit_setting_type
4430 ( name, label, description, datatype, fm_class ) VALUES
4431 ( 'cat.default_copy_status_fast',
4432 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Cataloging: Default copy status (fast add)', 'coust', 'label'),
4433 oils_i18n_gettext( 'cat.default_copy_status_fast', 'Default status when a copy is created using the "Fast Add" interface.', 'coust', 'description'),
4437 INSERT into config.org_unit_setting_type
4438 ( name, label, description, datatype, fm_class ) VALUES
4439 ( 'cat.default_copy_status_normal',
4440 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Cataloging: Default copy status (normal)', 'coust', 'label'),
4441 oils_i18n_gettext( 'cat.default_copy_status_normal', 'Default status when a copy is created using the normal volume/copy creator interface.', 'coust', 'description'),
4446 INSERT into config.org_unit_setting_type
4447 ( name, label, description, datatype ) VALUES
4448 ( 'ui.unified_volume_copy_editor',
4449 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'GUI: Unified Volume/Item Creator/Editor', 'coust', 'label'),
4450 oils_i18n_gettext( 'ui.unified_volume_copy_editor', 'If true combines the Volume/Copy Creator and Item Attribute Editor in some instances.', 'coust', 'description'),
4455 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
4457 transformed_xml TEXT;
4460 xfrm config.xml_transform%ROWTYPE;
4462 new_attrs HSTORE := ''::HSTORE;
4463 attr_def config.record_attr_definition%ROWTYPE;
4466 IF NEW.deleted IS TRUE THEN -- If this bib is deleted
4467 DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
4468 DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
4469 DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
4470 DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
4471 RETURN NEW; -- and we're done
4474 IF TG_OP = 'UPDATE' THEN -- re-ingest?
4475 PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
4477 IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
4482 -- Record authority linking
4483 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
4485 PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
4488 -- Flatten and insert the mfr data
4489 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
4491 PERFORM metabib.reingest_metabib_full_rec(NEW.id);
4493 -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
4494 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
4496 FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
4498 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
4499 SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
4500 FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
4501 WHERE record = NEW.id
4502 AND tag LIKE attr_def.tag
4504 WHEN attr_def.sf_list IS NOT NULL
4505 THEN POSITION(subfield IN attr_def.sf_list) > 0
4512 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
4513 attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
4515 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
4517 SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
4519 -- See if we can skip the XSLT ... it's expensive
4520 IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
4521 -- Can't skip the transform
4522 IF xfrm.xslt <> '---' THEN
4523 transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
4525 transformed_xml := NEW.marc;
4528 prev_xfrm := xfrm.name;
4531 IF xfrm.name IS NULL THEN
4532 -- just grab the marcxml (empty) transform
4533 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
4534 prev_xfrm := xfrm.name;
4537 attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
4539 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
4540 SELECT m.value INTO attr_value
4541 FROM biblio.marc21_physical_characteristics(NEW.id) v
4542 JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
4543 WHERE v.subfield = attr_def.phys_char_sf
4544 LIMIT 1; -- Just in case ...
4548 -- apply index normalizers to attr_value
4550 SELECT n.func AS func,
4551 n.param_count AS param_count,
4553 FROM config.index_normalizer n
4554 JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
4555 WHERE attr = attr_def.name
4557 EXECUTE 'SELECT ' || normalizer.func || '(' ||
4558 quote_literal( attr_value ) ||
4560 WHEN normalizer.param_count > 0
4561 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
4564 ')' INTO attr_value;
4568 -- Add the new value to the hstore
4569 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
4573 IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
4574 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
4576 UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
4582 -- Gather and insert the field entry data
4583 PERFORM metabib.reingest_metabib_field_entries(NEW.id);
4585 -- Located URI magic
4586 IF TG_OP = 'INSERT' THEN
4587 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4589 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4592 PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
4594 PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
4598 -- (re)map metarecord-bib linking
4599 IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
4600 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
4602 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4604 ELSE -- we're doing an update, and we're not deleted, remap
4605 PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
4607 PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
4613 $func$ LANGUAGE PLPGSQL;
4615 ALTER TABLE config.circ_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6,2) NOT NULL DEFAULT 0.0;
4617 UPDATE config.circ_matrix_weights
4618 SET marc_bib_level = marc_vr_format;
4620 ALTER TABLE config.hold_matrix_weights ADD COLUMN marc_bib_level NUMERIC(6, 2) NOT NULL DEFAULT 0.0;
4622 UPDATE config.hold_matrix_weights
4623 SET marc_bib_level = marc_vr_format;
4625 ALTER TABLE config.circ_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4627 ALTER TABLE config.hold_matrix_weights ALTER COLUMN marc_bib_level DROP DEFAULT;
4629 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$
4631 cn_object asset.call_number%ROWTYPE;
4632 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4633 cur_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4634 matchpoint config.circ_matrix_matchpoint%ROWTYPE;
4635 weights config.circ_matrix_weights%ROWTYPE;
4637 denominator NUMERIC(6,2);
4639 result action.found_circ_matrix_matchpoint;
4642 result.success = false;
4644 -- Fetch useful data
4645 SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4646 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = cn_object.record;
4648 -- Pre-generate this so we only calc it once
4649 IF user_object.dob IS NOT NULL THEN
4650 SELECT INTO user_age age(user_object.dob);
4653 -- Grab the closest set circ weight setting.
4654 SELECT INTO weights cw.*
4655 FROM config.weight_assoc wa
4656 JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
4657 JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
4662 -- No weights? Bad admin! Defaults to handle that anyway.
4663 IF weights.id IS NULL THEN
4664 weights.grp := 11.0;
4665 weights.org_unit := 10.0;
4666 weights.circ_modifier := 5.0;
4667 weights.marc_type := 4.0;
4668 weights.marc_form := 3.0;
4669 weights.marc_bib_level := 2.0;
4670 weights.marc_vr_format := 2.0;
4671 weights.copy_circ_lib := 8.0;
4672 weights.copy_owning_lib := 8.0;
4673 weights.user_home_ou := 8.0;
4674 weights.ref_flag := 1.0;
4675 weights.juvenile_flag := 6.0;
4676 weights.is_renewal := 7.0;
4677 weights.usr_age_lower_bound := 0.0;
4678 weights.usr_age_upper_bound := 0.0;
4681 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4682 -- If you break your org tree with funky parenting this may be wrong
4683 -- 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
4684 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4685 WITH all_distance(distance) AS (
4686 SELECT depth AS distance FROM actor.org_unit_type
4688 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4690 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4692 -- Loop over all the potential matchpoints
4693 FOR cur_matchpoint IN
4695 FROM config.circ_matrix_matchpoint m
4696 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
4697 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
4698 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
4699 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
4700 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4702 -- Permission Groups
4703 -- AND (m.grp IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
4705 -- AND (m.org_unit IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
4706 AND (m.copy_owning_lib IS NULL OR cnoua.id IS NOT NULL)
4707 AND (m.copy_circ_lib IS NULL OR iooua.id IS NOT NULL)
4708 AND (m.user_home_ou IS NULL OR uhoua.id IS NOT NULL)
4710 AND (m.is_renewal IS NULL OR m.is_renewal = renewal)
4711 -- Static User Checks
4712 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4713 AND (m.usr_age_lower_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
4714 AND (m.usr_age_upper_bound IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
4715 -- Static Item Checks
4716 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4717 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4718 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4719 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4720 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4721 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4723 -- Permission Groups
4724 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
4726 CASE WHEN ctoua.distance IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
4727 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
4728 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
4729 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4730 -- Circ Type -- Note: 4^x is equiv to 2^(2*x)
4731 CASE WHEN m.is_renewal IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
4732 -- Static User Checks
4733 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4734 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
4735 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
4736 -- Static Item Checks
4737 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4738 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4739 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4740 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4741 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4742 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4743 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4746 -- Record the full matching row list
4747 row_list := row_list || cur_matchpoint.id;
4749 -- No matchpoint yet?
4750 IF matchpoint.id IS NULL THEN
4751 -- Take the entire matchpoint as a starting point
4752 matchpoint := cur_matchpoint;
4753 CONTINUE; -- No need to look at this row any more.
4756 -- Incomplete matchpoint?
4757 IF matchpoint.circulate IS NULL THEN
4758 matchpoint.circulate := cur_matchpoint.circulate;
4760 IF matchpoint.duration_rule IS NULL THEN
4761 matchpoint.duration_rule := cur_matchpoint.duration_rule;
4763 IF matchpoint.recurring_fine_rule IS NULL THEN
4764 matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
4766 IF matchpoint.max_fine_rule IS NULL THEN
4767 matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
4769 IF matchpoint.hard_due_date IS NULL THEN
4770 matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
4772 IF matchpoint.total_copy_hold_ratio IS NULL THEN
4773 matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
4775 IF matchpoint.available_copy_hold_ratio IS NULL THEN
4776 matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
4778 IF matchpoint.renewals IS NULL THEN
4779 matchpoint.renewals := cur_matchpoint.renewals;
4781 IF matchpoint.grace_period IS NULL THEN
4782 matchpoint.grace_period := cur_matchpoint.grace_period;
4786 -- Check required fields
4787 IF matchpoint.circulate IS NOT NULL AND
4788 matchpoint.duration_rule IS NOT NULL AND
4789 matchpoint.recurring_fine_rule IS NOT NULL AND
4790 matchpoint.max_fine_rule IS NOT NULL THEN
4791 -- All there? We have a completed match.
4792 result.success := true;
4795 -- Include the assembled matchpoint, even if it isn't complete
4796 result.matchpoint := matchpoint;
4798 -- Include (for debugging) the full list of matching rows
4799 result.buildrows := row_list;
4801 -- Hand the result back to caller
4804 $func$ LANGUAGE plpgsql;
4806 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
4810 requestor_object actor.usr%ROWTYPE;
4811 user_object actor.usr%ROWTYPE;
4812 item_object asset.copy%ROWTYPE;
4813 item_cn_object asset.call_number%ROWTYPE;
4814 rec_descriptor metabib.rec_descriptor%ROWTYPE;
4815 matchpoint config.hold_matrix_matchpoint%ROWTYPE;
4816 weights config.hold_matrix_weights%ROWTYPE;
4817 denominator NUMERIC(6,2);
4819 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
4820 SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
4821 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
4822 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
4823 SELECT INTO rec_descriptor * FROM metabib.rec_descriptor WHERE record = item_cn_object.record;
4825 -- The item's owner should probably be the one determining if the item is holdable
4826 -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
4827 -- This flag will allow for setting it to the owning library (where the call number "lives")
4828 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
4830 -- Grab the closest set circ weight setting.
4832 -- Default to circ library
4833 SELECT INTO weights hw.*
4834 FROM config.weight_assoc wa
4835 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4836 JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
4841 -- Flag is set, use owning library
4842 SELECT INTO weights hw.*
4843 FROM config.weight_assoc wa
4844 JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
4845 JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) d ON (wa.org_unit = d.id)
4851 -- No weights? Bad admin! Defaults to handle that anyway.
4852 IF weights.id IS NULL THEN
4853 weights.user_home_ou := 5.0;
4854 weights.request_ou := 5.0;
4855 weights.pickup_ou := 5.0;
4856 weights.item_owning_ou := 5.0;
4857 weights.item_circ_ou := 5.0;
4858 weights.usr_grp := 7.0;
4859 weights.requestor_grp := 8.0;
4860 weights.circ_modifier := 4.0;
4861 weights.marc_type := 3.0;
4862 weights.marc_form := 2.0;
4863 weights.marc_bib_level := 1.0;
4864 weights.marc_vr_format := 1.0;
4865 weights.juvenile_flag := 4.0;
4866 weights.ref_flag := 0.0;
4869 -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
4870 -- If you break your org tree with funky parenting this may be wrong
4871 -- 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
4872 -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
4873 WITH all_distance(distance) AS (
4874 SELECT depth AS distance FROM actor.org_unit_type
4876 SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
4878 SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
4880 -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
4881 -- This may be better implemented as part of the upgrade script?
4882 -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
4883 -- Then remove this flag, of course.
4884 PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
4887 -- Note: This, to me, is REALLY hacky. I put it in anyway.
4888 -- If you can't tell, this is a single call swap on two variables.
4889 SELECT INTO user_object.profile, requestor_object.profile
4890 requestor_object.profile, user_object.profile;
4893 -- Select the winning matchpoint into the matchpoint variable for returning
4894 SELECT INTO matchpoint m.*
4895 FROM config.hold_matrix_matchpoint m
4896 /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
4897 LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
4898 LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
4899 LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
4900 LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
4901 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
4902 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou ) uhoua ON m.user_home_ou = uhoua.id
4904 -- Permission Groups
4905 -- AND (m.requestor_grp IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
4906 AND (m.usr_grp IS NULL OR upgad.id IS NOT NULL)
4908 AND (m.pickup_ou IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
4909 AND (m.request_ou IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
4910 AND (m.item_owning_ou IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
4911 AND (m.item_circ_ou IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
4912 AND (m.user_home_ou IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
4913 -- Static User Checks
4914 AND (m.juvenile_flag IS NULL OR m.juvenile_flag = user_object.juvenile)
4915 -- Static Item Checks
4916 AND (m.circ_modifier IS NULL OR m.circ_modifier = item_object.circ_modifier)
4917 AND (m.marc_type IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
4918 AND (m.marc_form IS NULL OR m.marc_form = rec_descriptor.item_form)
4919 AND (m.marc_bib_level IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
4920 AND (m.marc_vr_format IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
4921 AND (m.ref_flag IS NULL OR m.ref_flag = item_object.ref)
4923 -- Permission Groups
4924 CASE WHEN rpgad.distance IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
4925 CASE WHEN upgad.distance IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
4927 CASE WHEN puoua.distance IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
4928 CASE WHEN rqoua.distance IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
4929 CASE WHEN cnoua.distance IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
4930 CASE WHEN iooua.distance IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
4931 CASE WHEN uhoua.distance IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
4932 -- Static User Checks -- Note: 4^x is equiv to 2^(2*x)
4933 CASE WHEN m.juvenile_flag IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
4934 -- Static Item Checks
4935 CASE WHEN m.circ_modifier IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
4936 CASE WHEN m.marc_type IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
4937 CASE WHEN m.marc_form IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
4938 CASE WHEN m.marc_vr_format IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
4939 CASE WHEN m.ref_flag IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END DESC,
4940 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
4941 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
4944 -- Return just the ID for now
4945 RETURN matchpoint.id;
4947 $func$ LANGUAGE 'plpgsql';
4950 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
4953 use MARC::File::XML (BinaryEncoding => 'UTF-8');
4956 use Unicode::Normalize;
4958 MARC::Charset->assume_unicode(1);
4960 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
4961 my $schema = $_TD->{table_schema};
4962 my $rec_id = $_TD->{new}{id};
4964 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
4965 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
4966 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
4970 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
4971 my $ou_cni = 'EVRGRN';
4974 if ($schema eq 'serial') {
4975 $owner = $_TD->{new}{owning_lib};
4977 # are.owner and bre.owner can be null, so fall back to the consortial setting
4978 $owner = $_TD->{new}{owner} || 1;
4981 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
4982 if ($ous_rv->{processed}) {
4983 $ou_cni = $ous_rv->{rows}[0]->{value};
4984 $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
4986 # Fall back to the shortname of the OU if there was no OU setting
4987 $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
4988 if ($ous_rv->{processed}) {
4989 $ou_cni = $ous_rv->{rows}[0]->{shortname};
4993 my ($create, $munge) = (0, 0);
4995 my @scns = $record->field('035');
4997 foreach my $id_field ('001', '003') {
4999 my @controls = $record->field($id_field);
5001 if ($id_field eq '001') {
5002 $spec_value = $rec_id;
5004 $spec_value = $ou_cni;
5007 # Create the 001/003 if none exist
5008 if (scalar(@controls) == 1) {
5009 # Only one field; check to see if we need to munge it
5010 unless (grep $_->data() eq $spec_value, @controls) {
5014 # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
5015 foreach my $control (@controls) {
5016 unless ($control->data() eq $spec_value) {
5017 $record->delete_field($control);
5020 $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
5025 # Now, if we need to munge the 001, we will first push the existing 001/003
5026 # into the 035; but if the record did not have one (and one only) 001 and 003
5027 # to begin with, skip this process
5028 if ($munge and not $create) {
5029 my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
5031 # Do not create duplicate 035 fields
5032 unless (grep $_->subfield('a') eq $scn, @scns) {
5033 $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
5037 # Set the 001/003 and update the MARC
5038 if ($create or $munge) {
5039 $record->field('001')->data($rec_id);
5040 $record->field('003')->data($ou_cni);
5042 my $xml = $record->as_xml_record();
5044 $xml =~ s/^<\?xml.+\?\s*>//go;
5045 $xml =~ s/>\s+</></go;
5046 $xml =~ s/\p{Cc}//go;
5048 # Embed a version of OpenILS::Application::AppUtils->entityize()
5049 # to avoid having to set PERL5LIB for PostgreSQL as well
5051 # If we are going to convert non-ASCII characters to XML entities,
5052 # we had better be dealing with a UTF8 string to begin with
5053 $xml = decode_utf8($xml);
5057 # Convert raw ampersands to entities
5058 $xml =~ s/&(?!\S+;)/&/gso;
5060 # Convert Unicode characters to entities
5061 $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
5063 $xml =~ s/[\x00-\x1f]//go;
5064 $_TD->{new}{marc} = $xml;
5070 $func$ LANGUAGE PLPERLU;
5072 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
5075 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5078 MARC::Charset->assume_unicode(1);
5081 my $r = MARC::Record->new_from_xml( $xml );
5083 return undef unless ($r);
5085 my $id = shift() || $r->subfield( '901' => 'c' );
5086 $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
5087 return undef unless ($id); # We need an ID!
5089 my $tmpl = MARC::Record->new();
5090 $tmpl->encoding( 'UTF-8' );
5093 for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
5095 my $tag = $field->tag;
5096 my $i1 = $field->indicator(1);
5097 my $i2 = $field->indicator(2);
5098 my $sf = join '', map { $_->[0] } $field->subfields;
5099 my @data = map { @$_ } $field->subfields;
5103 # Map the authority field to bib fields it can control.
5104 if ($tag >= 100 and $tag <= 111) { # names
5105 @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
5106 } elsif ($tag eq '130') { # uniform title
5107 @replace_them = qw/130 240 440 730 830/;
5108 } elsif ($tag >= 150 and $tag <= 155) { # subjects
5109 @replace_them = ($tag + 500);
5110 } elsif ($tag >= 180 and $tag <= 185) { # floating subdivisions
5111 @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/;
5116 # Dummy up the bib-side data
5117 $tmpl->append_fields(
5119 MARC::Field->new( $_, $i1, $i2, @data )
5123 # Construct some 'replace' rules
5124 push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
5127 # Insert the replace rules into the template
5128 $tmpl->append_fields(
5129 MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
5132 $xml = $tmpl->as_xml_record;
5133 $xml =~ s/^<\?.+?\?>$//mo;
5135 $xml =~ s/>\s+</></sgo;
5139 $func$ LANGUAGE PLPERLU;
5141 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
5144 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5148 MARC::Charset->assume_unicode(1);
5150 my $target_xml = shift;
5151 my $source_xml = shift;
5152 my $field_spec = shift;
5153 my $force_add = shift || 0;
5155 my $target_r = MARC::Record->new_from_xml( $target_xml );
5156 my $source_r = MARC::Record->new_from_xml( $source_xml );
5158 return $target_xml unless ($target_r && $source_r);
5160 my @field_list = split(',', $field_spec);
5163 for my $f (@field_list) {
5164 $f =~ s/^\s*//; $f =~ s/\s*$//;
5165 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5171 $match =~ s/^\s*//; $match =~ s/\s*$//;
5172 $fields{$field} = { sf => [ split('', $sf) ] };
5174 my ($msf,$mre) = split('~', $match);
5175 if (length($msf) > 0 and length($mre) > 0) {
5176 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5177 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5178 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5184 for my $f ( keys %fields) {
5185 if ( @{$fields{$f}{sf}} ) {
5186 for my $from_field ($source_r->field( $f )) {
5187 my @tos = $target_r->field( $f );
5189 next if (exists($fields{$f}{match}) and !$force_add);
5190 my @new_fields = map { $_->clone } $source_r->field( $f );
5191 $target_r->insert_fields_ordered( @new_fields );
5193 for my $to_field (@tos) {
5194 if (exists($fields{$f}{match})) {
5195 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5197 my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
5198 $to_field->add_subfields( @new_sf );
5203 my @new_fields = map { $_->clone } $source_r->field( $f );
5204 $target_r->insert_fields_ordered( @new_fields );
5208 $target_xml = $target_r->as_xml_record;
5209 $target_xml =~ s/^<\?.+?\?>$//mo;
5210 $target_xml =~ s/\n//sgo;
5211 $target_xml =~ s/>\s+</></sgo;
5215 $_$ LANGUAGE PLPERLU;
5217 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
5223 use MARC::File::XML (BinaryEncoding => 'UTF8');
5225 use UUID::Tiny ':std';
5227 MARC::Charset->assume_unicode(1);
5229 my $xml = shift() or return undef;
5233 # Prevent errors in XML parsing from blowing out ungracefully
5235 $r = MARC::Record->new_from_xml( $xml );
5238 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5242 return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
5245 # From http://www.loc.gov/standards/sourcelist/subject.html
5246 my $thes_code_map = {
5252 n => 'notapplicable',
5258 # Default to "No attempt to code" if the leader is horribly broken
5259 my $fixed_field = $r->field('008');
5260 my $thes_char = '|';
5262 $thes_char = substr($fixed_field->data(), 11, 1) || '|';
5265 my $thes_code = 'UNDEFINED';
5267 if ($thes_char eq 'z') {
5268 # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
5269 $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
5270 } elsif ($thes_code_map->{$thes_char}) {
5271 $thes_code = $thes_code_map->{$thes_char};
5275 my $head = $r->field('1..');
5277 # Concatenate all of these subfields together, prefixed by their code
5278 # to prevent collisions along the lines of "Fiction, North Carolina"
5279 foreach my $sf ($head->subfields()) {
5280 $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
5285 my $stmt = spi_prepare('SELECT public.naco_normalize($1) AS norm_text', 'TEXT');
5286 my $result = spi_exec_prepared($stmt, $auth_txt);
5287 my $norm_txt = $result->{rows}[0]->{norm_text};
5288 spi_freeplan($stmt);
5290 return $head->tag() . "_" . $thes_code . " " . $norm_txt;
5293 return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
5294 $func$ LANGUAGE 'plperlu' IMMUTABLE;
5296 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
5299 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5303 MARC::Charset->assume_unicode(1);
5306 my $r = MARC::Record->new_from_xml( $xml );
5308 return $xml unless ($r);
5310 my $field_spec = shift;
5311 my @field_list = split(',', $field_spec);
5314 for my $f (@field_list) {
5315 $f =~ s/^\s*//; $f =~ s/\s*$//;
5316 if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
5322 $match =~ s/^\s*//; $match =~ s/\s*$//;
5323 $fields{$field} = { sf => [ split('', $sf) ] };
5325 my ($msf,$mre) = split('~', $match);
5326 if (length($msf) > 0 and length($mre) > 0) {
5327 $msf =~ s/^\s*//; $msf =~ s/\s*$//;
5328 $mre =~ s/^\s*//; $mre =~ s/\s*$//;
5329 $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
5335 for my $f ( keys %fields) {
5336 for my $to_field ($r->field( $f )) {
5337 if (exists($fields{$f}{match})) {
5338 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
5341 if ( @{$fields{$f}{sf}} ) {
5342 $to_field->delete_subfield(code => $fields{$f}{sf});
5344 $r->delete_field( $to_field );
5349 $xml = $r->as_xml_record;
5350 $xml =~ s/^<\?.+?\?>$//mo;
5352 $xml =~ s/>\s+</></sgo;
5356 $_$ LANGUAGE PLPERLU;
5358 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
5361 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5364 MARC::Charset->assume_unicode(1);
5367 my $r = MARC::Record->new_from_xml( $xml );
5369 return_next( { tag => 'LDR', value => $r->leader } );
5371 for my $f ( $r->fields ) {
5372 if ($f->is_control_field) {
5373 return_next({ tag => $f->tag, value => $f->data });
5375 for my $s ($f->subfields) {
5378 ind1 => $f->indicator(1),
5379 ind2 => $f->indicator(2),
5380 subfield => $s->[0],
5384 if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
5385 my $trim = $f->indicator(2) || 0;
5388 ind1 => $f->indicator(1),
5389 ind2 => $f->indicator(2),
5391 value => substr( $s->[1], $trim )
5400 $func$ LANGUAGE PLPERLU;
5402 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
5405 use MARC::File::XML (BinaryEncoding => 'UTF-8');
5408 MARC::Charset->assume_unicode(1);
5411 my $r = MARC::Record->new_from_xml( $xml );
5413 return_next( { tag => 'LDR', value => $r->leader } );
5415 for my $f ( $r->fields ) {
5416 if ($f->is_control_field) {
5417 return_next({ tag => $f->tag, value => $f->data });
5419 for my $s ($f->subfields) {
5422 ind1 => $f->indicator(1),
5423 ind2 => $f->indicator(2),
5424 subfield => $s->[0],
5434 $func$ LANGUAGE PLPERLU;
5437 CREATE INDEX actor_usr_day_phone_idx_numeric ON actor.usr USING BTREE
5438 (evergreen.lowercase(REGEXP_REPLACE(day_phone, '[^0-9]', '', 'g')));
5440 CREATE INDEX actor_usr_evening_phone_idx_numeric ON actor.usr USING BTREE
5441 (evergreen.lowercase(REGEXP_REPLACE(evening_phone, '[^0-9]', '', 'g')));
5443 CREATE INDEX actor_usr_other_phone_idx_numeric ON actor.usr USING BTREE
5444 (evergreen.lowercase(REGEXP_REPLACE(other_phone, '[^0-9]', '', 'g')));
5447 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
5452 -- If there are any renewals for this circulation, don't archive or delete
5453 -- it yet. We'll do so later, when we archive and delete the renewals.
5455 SELECT 'Y' INTO found
5456 FROM action.circulation
5457 WHERE parent_circ = OLD.id
5461 RETURN NULL; -- don't delete
5464 -- Archive a copy of the old row to action.aged_circulation
5466 INSERT INTO action.aged_circulation
5467 (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5468 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5469 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5470 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5471 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5472 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
5474 id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
5475 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
5476 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
5477 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
5478 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
5479 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
5480 FROM action.all_circulation WHERE id = OLD.id;
5484 $$ LANGUAGE 'plpgsql';
5487 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$
5490 user_object actor.usr%ROWTYPE;
5491 age_protect_object config.rule_age_hold_protect%ROWTYPE;
5492 standing_penalty config.standing_penalty%ROWTYPE;
5493 transit_range_ou_type actor.org_unit_type%ROWTYPE;
5494 transit_source actor.org_unit%ROWTYPE;
5495 item_object asset.copy%ROWTYPE;
5496 item_cn_object asset.call_number%ROWTYPE;
5497 ou_skip actor.org_unit_setting%ROWTYPE;
5498 result action.matrix_test_result;
5499 hold_test config.hold_matrix_matchpoint%ROWTYPE;
5501 hold_transit_prox INT;
5502 frozen_hold_count INT;
5503 context_org_list INT[];
5506 SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
5507 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
5509 result.success := TRUE;
5511 -- Fail if we couldn't find a user
5512 IF user_object.id IS NULL THEN
5513 result.fail_part := 'no_user';
5514 result.success := FALSE;
5520 SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
5522 -- Fail if we couldn't find a copy
5523 IF item_object.id IS NULL THEN
5524 result.fail_part := 'no_item';
5525 result.success := FALSE;
5531 SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
5532 result.matchpoint := matchpoint_id;
5534 SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
5536 -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
5537 IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
5538 result.fail_part := 'circ.holds.target_skip_me';
5539 result.success := FALSE;
5545 -- Fail if user is barred
5546 IF user_object.barred IS TRUE THEN
5547 result.fail_part := 'actor.usr.barred';
5548 result.success := FALSE;
5554 -- Fail if we couldn't find any matchpoint (requires a default)
5555 IF matchpoint_id IS NULL THEN
5556 result.fail_part := 'no_matchpoint';
5557 result.success := FALSE;
5563 SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
5565 IF hold_test.holdable IS FALSE THEN
5566 result.fail_part := 'config.hold_matrix_test.holdable';
5567 result.success := FALSE;
5572 IF hold_test.transit_range IS NOT NULL THEN
5573 SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
5574 IF hold_test.distance_is_from_owner THEN
5575 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;
5577 SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
5580 PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
5583 result.fail_part := 'transit_range';
5584 result.success := FALSE;
5590 FOR standing_penalty IN
5591 SELECT DISTINCT csp.*
5592 FROM actor.usr_standing_penalty usp
5593 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5594 WHERE usr = match_user
5595 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5596 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5597 AND csp.block_list LIKE '%HOLD%' LOOP
5599 result.fail_part := standing_penalty.name;
5600 result.success := FALSE;
5605 IF hold_test.stop_blocked_user IS TRUE THEN
5606 FOR standing_penalty IN
5607 SELECT DISTINCT csp.*
5608 FROM actor.usr_standing_penalty usp
5609 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
5610 WHERE usr = match_user
5611 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
5612 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
5613 AND csp.block_list LIKE '%CIRC%' LOOP
5615 result.fail_part := standing_penalty.name;
5616 result.success := FALSE;
5622 IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
5623 SELECT INTO hold_count COUNT(*)
5624 FROM action.hold_request
5625 WHERE usr = match_user
5626 AND fulfillment_time IS NULL
5627 AND cancel_time IS NULL
5628 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
5630 IF hold_count >= hold_test.max_holds THEN
5631 result.fail_part := 'config.hold_matrix_test.max_holds';
5632 result.success := FALSE;
5638 IF item_object.age_protect IS NOT NULL THEN
5639 SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
5641 IF item_object.create_date + age_protect_object.age > NOW() THEN
5642 IF hold_test.distance_is_from_owner THEN
5643 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
5644 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
5646 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
5649 IF hold_transit_prox > age_protect_object.prox THEN
5650 result.fail_part := 'config.rule_age_hold_protect.prox';
5651 result.success := FALSE;
5664 $func$ LANGUAGE plpgsql;
5666 -- do potentially large updates last to save time if upgrader needs
5667 -- to manually tweak the upgrade script to resolve errors
5670 UPDATE metabib.facet_entry SET value = evergreen.force_unicode_normal_form(value,'NFC');
5672 UPDATE asset.call_number SET id = id;
5674 -- Update reporter.materialized_simple_record with normalized ISBN values
5675 -- This might not get all of them, but most ISBNs will have more than one hyphen
5676 DELETE FROM reporter.materialized_simple_record WHERE id IN (
5677 SELECT record FROM metabib.full_rec WHERE tag = '020' AND subfield IN ('a', 'z') AND value LIKE '%-%-%'
5680 INSERT INTO reporter.materialized_simple_record
5681 SELECT DISTINCT rossr.* FROM reporter.old_super_simple_record rossr INNER JOIN metabib.full_rec mfr ON mfr.record = rossr.id
5682 WHERE mfr.tag = '020' AND mfr.subfield IN ('a', 'z') AND mfr.value LIKE '%-%-%'
5685 INSERT INTO config.upgrade_log (version) VALUES ('0542'); -- phasefx
5687 INSERT INTO permission.perm_list
5690 (485, 'CREATE_VOLUME_SUFFIX', oils_i18n_gettext(485, 'Create suffix label definition.', 'ppl', 'description'))
5691 ,(486, 'UPDATE_VOLUME_SUFFIX', oils_i18n_gettext(486, 'Update suffix label definition.', 'ppl', 'description'))
5692 ,(487, 'DELETE_VOLUME_SUFFIX', oils_i18n_gettext(487, 'Delete suffix label definition.', 'ppl', 'description'))
5693 ,(488, 'CREATE_VOLUME_PREFIX', oils_i18n_gettext(488, 'Create prefix label definition.', 'ppl', 'description'))
5694 ,(489, 'UPDATE_VOLUME_PREFIX', oils_i18n_gettext(489, 'Update prefix label definition.', 'ppl', 'description'))
5695 ,(490, 'DELETE_VOLUME_PREFIX', oils_i18n_gettext(490, 'Delete prefix label definition.', 'ppl', 'description'))
5696 ,(491, 'CREATE_MONOGRAPH_PART', oils_i18n_gettext(491, 'Create monograph part definition.', 'ppl', 'description'))
5697 ,(492, 'UPDATE_MONOGRAPH_PART', oils_i18n_gettext(492, 'Update monograph part definition.', 'ppl', 'description'))
5698 ,(493, 'DELETE_MONOGRAPH_PART', oils_i18n_gettext(493, 'Delete monograph part definition.', 'ppl', 'description'))
5699 ,(494, 'ADMIN_CODED_VALUE', oils_i18n_gettext(494, 'Create/Update/Delete SVF Record Attribute Coded Value Map', 'ppl', 'description'))
5700 ,(495, 'ADMIN_SERIAL_ITEM', oils_i18n_gettext(495, 'Create/Retrieve/Update/Delete Serial Item', 'ppl', 'description'))
5701 ,(496, 'ADMIN_SVF', oils_i18n_gettext(496, 'Create/Update/Delete SVF Record Attribute Defintion', 'ppl', 'description'))
5702 ,(497, 'CREATE_BIB_PTYPE', oils_i18n_gettext(497, 'Create Bibliographic Record Peer Type', 'ppl', 'description'))
5703 ,(498, 'CREATE_PURCHASE_REQUEST', oils_i18n_gettext(498, 'Create User Purchase Request', 'ppl', 'description'))
5704 ,(499, 'DELETE_BIB_PTYPE', oils_i18n_gettext(499, 'Delete Bibliographic Record Peer Type', 'ppl', 'description'))
5705 ,(500, 'MAP_MONOGRAPH_PART', oils_i18n_gettext(500, 'Create/Update/Delete Copy Monograph Part Map', 'ppl', 'description'))
5706 ,(501, 'MARK_ITEM_MISSING_PIECES', oils_i18n_gettext(501, 'Allows the Mark Item Missing Pieces action.', 'ppl', 'description'))
5707 ,(502, 'UPDATE_BIB_PTYPE', oils_i18n_gettext(502, 'Update Bibliographic Record Peer Type', 'ppl', 'description'))
5708 ,(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'))
5709 ,(504, 'UPDATE_PICKLIST', oils_i18n_gettext(504, 'Allows update/re-use of an acquisitions pick/selection list.', 'ppl', 'description'))
5710 ,(505, 'UPDATE_WORKSTATION', oils_i18n_gettext(505, 'Allows update of a workstation during workstation registration override.', 'ppl', 'description'))
5711 ,(506, 'VIEW_USER_SETTING_TYPE', oils_i18n_gettext(506, 'Allows viewing of configurable user setting types.', 'ppl', 'description'))
5712 ) AS np(id,code,description)
5713 LEFT JOIN permission.perm_list pl USING (code)
5714 WHERE pl.id IS NULL;
5718 -- add new perms AND catch up on some missed upgrade data, if needed
5720 -- we could get away from these fixed-id inserts here, but then this
5721 -- upgrade would be ahead of the mainline, I think
5723 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5724 SELECT 8, oils_i18n_gettext(8, 'Cataloging Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.cat_admin'
5727 FROM permission.grp_tree
5732 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5733 SELECT 9, oils_i18n_gettext(9, 'Circulation Administrator', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.circ_admin'
5736 FROM permission.grp_tree
5741 UPDATE permission.grp_tree SET description = oils_i18n_gettext(10, 'Can do anything at the Branch level', 'pgt', 'description') WHERE id = 10;
5743 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5744 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'
5747 FROM permission.grp_tree
5752 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5753 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'
5756 FROM permission.grp_tree
5761 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5762 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'
5765 FROM permission.grp_tree
5770 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5771 SELECT 14, oils_i18n_gettext(14, 'Data Review', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.data_review'
5774 FROM permission.grp_tree
5779 INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm)
5780 SELECT 15, oils_i18n_gettext(15, 'Volunteers', 'pgt', 'name'), 3, NULL, '3 years', TRUE, 'group_application.user.staff.volunteers'
5783 FROM permission.grp_tree
5790 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5792 pgt.id, perm.id, aout.depth, TRUE
5794 permission.grp_tree pgt,
5795 permission.perm_list perm,
5796 actor.org_unit_type aout
5798 pgt.name = 'Cataloging Administrator' AND
5799 aout.name = 'Consortium' AND
5801 'ADMIN_IMPORT_ITEM_ATTR_DEF',
5802 'ADMIN_MERGE_PROFILE',
5803 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
5804 'CREATE_BIB_IMPORT_FIELD_DEF',
5806 'CREATE_BIB_SOURCE',
5807 'CREATE_IMPORT_ITEM_ATTR_DEF',
5808 'CREATE_IMPORT_TRASH_FIELD',
5809 'CREATE_MERGE_PROFILE',
5810 'CREATE_MONOGRAPH_PART',
5811 'CREATE_VOLUME_PREFIX',
5812 'CREATE_VOLUME_SUFFIX',
5813 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5815 'DELETE_BIB_SOURCE',
5816 'DELETE_IMPORT_ITEM_ATTR_DEF',
5817 'DELETE_IMPORT_TRASH_FIELD',
5818 'DELETE_MERGE_PROFILE',
5819 'DELETE_MONOGRAPH_PART',
5820 'DELETE_VOLUME_PREFIX',
5821 'DELETE_VOLUME_SUFFIX',
5822 'MAP_MONOGRAPH_PART',
5823 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
5824 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
5826 'UPDATE_IMPORT_ITEM_ATTR_DEF',
5827 'UPDATE_IMPORT_TRASH_FIELD',
5828 'UPDATE_MERGE_PROFILE',
5829 'UPDATE_MONOGRAPH_PART',
5830 'UPDATE_VOLUME_PREFIX',
5831 'UPDATE_VOLUME_SUFFIX'
5834 FROM permission.grp_perm_map AS map
5837 AND map.perm = perm.id
5840 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5842 pgt.id, perm.id, aout.depth, TRUE
5844 permission.grp_tree pgt,
5845 permission.perm_list perm,
5846 actor.org_unit_type aout
5848 pgt.name = 'Circulation Administrator' AND
5849 aout.name = 'Branch' AND
5854 FROM permission.grp_perm_map AS map
5857 AND map.perm = perm.id
5860 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5862 pgt.id, perm.id, aout.depth, TRUE
5864 permission.grp_tree pgt,
5865 permission.perm_list perm,
5866 actor.org_unit_type aout
5868 pgt.name = 'Circulation Administrator' AND
5869 aout.name = 'Consortium' AND
5871 'ADMIN_MAX_FINE_RULE',
5872 'CREATE_CIRC_DURATION',
5873 'DELETE_CIRC_DURATION',
5874 'MARK_ITEM_MISSING_PIECES',
5875 'UPDATE_CIRC_DURATION',
5876 'UPDATE_HOLD_REQUEST_TIME',
5877 'UPDATE_NET_ACCESS_LEVEL',
5878 'VIEW_CIRC_MATRIX_MATCHPOINT',
5879 'VIEW_HOLD_MATRIX_MATCHPOINT'
5882 FROM permission.grp_perm_map AS map
5885 AND map.perm = perm.id
5888 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5890 pgt.id, perm.id, aout.depth, TRUE
5892 permission.grp_tree pgt,
5893 permission.perm_list perm,
5894 actor.org_unit_type aout
5896 pgt.name = 'Circulation Administrator' AND
5897 aout.name = 'System' AND
5899 'ADMIN_BOOKING_RESERVATION',
5900 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
5901 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
5902 'ADMIN_BOOKING_RESOURCE',
5903 'ADMIN_BOOKING_RESOURCE_ATTR',
5904 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
5905 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
5906 'ADMIN_BOOKING_RESOURCE_TYPE',
5907 'ADMIN_COPY_LOCATION_ORDER',
5908 'ADMIN_HOLD_CANCEL_CAUSE',
5909 'ASSIGN_GROUP_PERM',
5912 'COPY_TRANSIT_RECEIVE',
5914 'CREATE_BILLING_TYPE',
5915 'CREATE_NON_CAT_TYPE',
5916 'CREATE_PATRON_STAT_CAT',
5917 'CREATE_PATRON_STAT_CAT_ENTRY',
5918 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
5919 'CREATE_USER_GROUP_LINK',
5920 'DELETE_BILLING_TYPE',
5921 'DELETE_NON_CAT_TYPE',
5922 'DELETE_PATRON_STAT_CAT',
5923 'DELETE_PATRON_STAT_CAT_ENTRY',
5924 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
5926 'group_application.user.staff',
5928 'MARK_ITEM_AVAILABLE',
5929 'MARK_ITEM_BINDERY',
5930 'MARK_ITEM_CHECKED_OUT',
5932 'MARK_ITEM_IN_PROCESS',
5933 'MARK_ITEM_IN_TRANSIT',
5935 'MARK_ITEM_MISSING',
5936 'MARK_ITEM_ON_HOLDS_SHELF',
5937 'MARK_ITEM_ON_ORDER',
5938 'MARK_ITEM_RESHELVING',
5940 'money.collections_tracker.create',
5941 'money.collections_tracker.delete',
5945 'REMOVE_USER_GROUP_LINK',
5946 'SET_CIRC_CLAIMS_RETURNED',
5947 'SET_CIRC_CLAIMS_RETURNED.override',
5952 'UPDATE_NON_CAT_TYPE',
5953 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
5954 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
5955 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
5956 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
5958 'VIEW_REPORT_OUTPUT',
5959 'VIEW_STANDING_PENALTY',
5964 FROM permission.grp_perm_map AS map
5967 AND map.perm = perm.id
5970 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5972 pgt.id, perm.id, aout.depth, TRUE
5974 permission.grp_tree pgt,
5975 permission.perm_list perm,
5976 actor.org_unit_type aout
5978 pgt.name = 'Local Administrator' AND
5979 aout.name = 'Branch' AND
5984 FROM permission.grp_perm_map AS map
5987 AND map.perm = perm.id
5990 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
5992 pgt.id, perm.id, aout.depth, FALSE
5994 permission.grp_tree pgt,
5995 permission.perm_list perm,
5996 actor.org_unit_type aout
5998 pgt.name = 'Serials' AND
5999 aout.name = 'System' AND
6001 'ADMIN_ASSET_COPY_TEMPLATE',
6002 'ADMIN_SERIAL_CAPTION_PATTERN',
6003 'ADMIN_SERIAL_DISTRIBUTION',
6004 'ADMIN_SERIAL_ITEM',
6005 'ADMIN_SERIAL_STREAM',
6006 'ADMIN_SERIAL_SUBSCRIPTION',
6011 FROM permission.grp_perm_map AS map
6014 AND map.perm = perm.id
6017 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6019 pgt.id, perm.id, aout.depth, TRUE
6021 permission.grp_tree pgt,
6022 permission.perm_list perm,
6023 actor.org_unit_type aout
6025 pgt.name = 'System Administrator' AND
6026 aout.name = 'System' AND
6031 FROM permission.grp_perm_map AS map
6034 AND map.perm = perm.id
6037 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6039 pgt.id, perm.id, aout.depth, FALSE
6041 permission.grp_tree pgt,
6042 permission.perm_list perm,
6043 actor.org_unit_type aout
6045 pgt.name = 'System Administrator' AND
6046 aout.name = 'Consortium' AND
6047 perm.code ~ '^VIEW_TRIGGER'
6050 FROM permission.grp_perm_map AS map
6053 AND map.perm = perm.id
6056 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6058 pgt.id, perm.id, aout.depth, TRUE
6060 permission.grp_tree pgt,
6061 permission.perm_list perm,
6062 actor.org_unit_type aout
6064 pgt.name = 'Global Administrator' AND
6065 aout.name = 'Consortium' AND
6070 FROM permission.grp_perm_map AS map
6073 AND map.perm = perm.id
6076 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6078 pgt.id, perm.id, aout.depth, FALSE
6080 permission.grp_tree pgt,
6081 permission.perm_list perm,
6082 actor.org_unit_type aout
6084 pgt.name = 'Data Review' AND
6085 aout.name = 'Consortium' AND
6087 'CREATE_COPY_TRANSIT',
6088 'VIEW_BILLING_TYPE',
6089 'VIEW_CIRCULATIONS',
6092 'VIEW_ORG_SETTINGS',
6096 'VIEW_USER_FINES_SUMMARY',
6097 'VIEW_USER_TRANSACTIONS',
6098 'VIEW_VOLUME_NOTES',
6102 FROM permission.grp_perm_map AS map
6105 AND map.perm = perm.id
6108 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6110 pgt.id, perm.id, aout.depth, FALSE
6112 permission.grp_tree pgt,
6113 permission.perm_list perm,
6114 actor.org_unit_type aout
6116 pgt.name = 'Data Review' AND
6117 aout.name = 'System' AND
6121 'CREATE_IN_HOUSE_USE',
6122 'CREATE_TRANSACTION',
6129 FROM permission.grp_perm_map AS map
6132 AND map.perm = perm.id
6135 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6137 pgt.id, perm.id, aout.depth, FALSE
6139 permission.grp_tree pgt,
6140 permission.perm_list perm,
6141 actor.org_unit_type aout
6143 pgt.name = 'Volunteers' AND
6144 aout.name = 'Branch' AND
6148 'CREATE_IN_HOUSE_USE',
6150 'VIEW_BILLING_TYPE',
6152 'VIEW_COPY_CHECKOUT',
6157 'VIEW_USER_FINES_SUMMARY',
6158 'VIEW_USER_TRANSACTIONS'
6161 FROM permission.grp_perm_map AS map
6164 AND map.perm = perm.id
6167 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6169 pgt.id, perm.id, aout.depth, FALSE
6171 permission.grp_tree pgt,
6172 permission.perm_list perm,
6173 actor.org_unit_type aout
6175 pgt.name = 'Volunteers' AND
6176 aout.name = 'Consortium' AND
6178 'CREATE_COPY_TRANSIT',
6179 'CREATE_TRANSACTION',
6186 FROM permission.grp_perm_map AS map
6189 AND map.perm = perm.id
6193 -- stock Users group
6194 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6196 pgt.id, perm.id, aout.depth, FALSE
6198 permission.grp_tree pgt,
6199 permission.perm_list perm,
6200 actor.org_unit_type aout
6202 pgt.name = 'Users' AND
6203 aout.name = 'Consortium' AND
6205 'CREATE_PURCHASE_REQUEST'
6208 FROM permission.grp_perm_map AS map
6211 AND map.perm = perm.id
6214 -- stock Staff group
6215 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6217 pgt.id, perm.id, aout.depth, FALSE
6219 permission.grp_tree pgt,
6220 permission.perm_list perm,
6221 actor.org_unit_type aout
6223 pgt.name = 'Staff' AND
6224 aout.name = 'Consortium' AND
6226 'VIEW_USER_SETTING_TYPE'
6229 FROM permission.grp_perm_map AS map
6232 AND map.perm = perm.id
6235 -- stock Circulators group
6236 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6238 pgt.id, perm.id, aout.depth, FALSE
6240 permission.grp_tree pgt,
6241 permission.perm_list perm,
6242 actor.org_unit_type aout
6244 pgt.name = 'Circulators' AND
6245 aout.name = 'Branch' AND
6247 'MARK_ITEM_MISSING_PIECES'
6250 FROM permission.grp_perm_map AS map
6253 AND map.perm = perm.id
6256 -- stock Catalogers group
6257 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6259 pgt.id, perm.id, aout.depth, FALSE
6261 permission.grp_tree pgt,
6262 permission.perm_list perm,
6263 actor.org_unit_type aout
6265 pgt.name = 'Catalogers' AND
6266 aout.name = 'System' AND
6268 'MAP_MONOGRAPH_PART'
6271 FROM permission.grp_perm_map AS map
6274 AND map.perm = perm.id
6277 -- stock Acquisitions group
6278 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6280 pgt.id, perm.id, aout.depth, FALSE
6282 permission.grp_tree pgt,
6283 permission.perm_list perm,
6284 actor.org_unit_type aout
6286 pgt.name = 'Acquisitions' AND
6287 aout.name = 'Consortium' AND
6292 FROM permission.grp_perm_map AS map
6295 AND map.perm = perm.id
6298 -- stock Acq Admin group
6299 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6301 pgt.id, perm.id, aout.depth, TRUE
6303 permission.grp_tree pgt,
6304 permission.perm_list perm,
6305 actor.org_unit_type aout
6307 pgt.name = 'Acquisitions Administrator' AND
6308 aout.name = 'Consortium' AND
6313 FROM permission.grp_perm_map AS map
6316 AND map.perm = perm.id
6319 INSERT INTO config.upgrade_log (version) VALUES ('0547'); -- dbwells
6321 -- account for spelling errors (Admin != Administrator)
6322 \qecho This might not insert much if you passed through 0542 on your way here,
6323 \qecho but one group was missed there as well
6325 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6327 pgt.id, perm.id, aout.depth, TRUE
6329 permission.grp_tree pgt,
6330 permission.perm_list perm,
6331 actor.org_unit_type aout
6333 pgt.name = 'Cataloging Administrator' AND
6334 aout.name = 'Consortium' AND
6336 'ADMIN_IMPORT_ITEM_ATTR_DEF',
6337 'ADMIN_MERGE_PROFILE',
6338 'CREATE_AUTHORITY_IMPORT_IMPORT_DEF',
6339 'CREATE_BIB_IMPORT_FIELD_DEF',
6341 'CREATE_BIB_SOURCE',
6342 'CREATE_IMPORT_ITEM_ATTR_DEF',
6343 'CREATE_IMPORT_TRASH_FIELD',
6344 'CREATE_MERGE_PROFILE',
6345 'CREATE_MONOGRAPH_PART',
6346 'CREATE_VOLUME_PREFIX',
6347 'CREATE_VOLUME_SUFFIX',
6348 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6350 'DELETE_BIB_SOURCE',
6351 'DELETE_IMPORT_ITEM_ATTR_DEF',
6352 'DELETE_IMPORT_TRASH_FIELD',
6353 'DELETE_MERGE_PROFILE',
6354 'DELETE_MONOGRAPH_PART',
6355 'DELETE_VOLUME_PREFIX',
6356 'DELETE_VOLUME_SUFFIX',
6357 'MAP_MONOGRAPH_PART',
6358 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
6359 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
6361 'UPDATE_IMPORT_ITEM_ATTR_DEF',
6362 'UPDATE_IMPORT_TRASH_FIELD',
6363 'UPDATE_MERGE_PROFILE',
6364 'UPDATE_MONOGRAPH_PART',
6365 'UPDATE_VOLUME_PREFIX',
6366 'UPDATE_VOLUME_SUFFIX'
6369 FROM permission.grp_perm_map AS map
6372 AND map.perm = perm.id
6375 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6377 pgt.id, perm.id, aout.depth, TRUE
6379 permission.grp_tree pgt,
6380 permission.perm_list perm,
6381 actor.org_unit_type aout
6383 pgt.name = 'Cataloging Administrator' AND
6384 aout.name = 'System' AND
6386 'CREATE_COPY_STAT_CAT',
6387 'CREATE_COPY_STAT_CAT_ENTRY',
6388 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
6390 'SHARE_REPORT_FOLDER',
6391 'UPDATE_COPY_LOCATION',
6392 'UPDATE_COPY_STAT_CAT',
6393 'UPDATE_COPY_STAT_CAT_ENTRY',
6394 'VIEW_REPORT_OUTPUT'
6397 FROM permission.grp_perm_map AS map
6400 AND map.perm = perm.id
6403 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6405 pgt.id, perm.id, aout.depth, TRUE
6407 permission.grp_tree pgt,
6408 permission.perm_list perm,
6409 actor.org_unit_type aout
6411 pgt.name = 'Circulation Administrator' AND
6412 aout.name = 'Branch' AND
6417 FROM permission.grp_perm_map AS map
6420 AND map.perm = perm.id
6423 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6425 pgt.id, perm.id, aout.depth, TRUE
6427 permission.grp_tree pgt,
6428 permission.perm_list perm,
6429 actor.org_unit_type aout
6431 pgt.name = 'Circulation Administrator' AND
6432 aout.name = 'Consortium' AND
6434 'ADMIN_MAX_FINE_RULE',
6435 'CREATE_CIRC_DURATION',
6436 'DELETE_CIRC_DURATION',
6437 'MARK_ITEM_MISSING_PIECES',
6438 'UPDATE_CIRC_DURATION',
6439 'UPDATE_HOLD_REQUEST_TIME',
6440 'UPDATE_NET_ACCESS_LEVEL',
6441 'VIEW_CIRC_MATRIX_MATCHPOINT',
6442 'VIEW_HOLD_MATRIX_MATCHPOINT'
6445 FROM permission.grp_perm_map AS map
6448 AND map.perm = perm.id
6451 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6453 pgt.id, perm.id, aout.depth, TRUE
6455 permission.grp_tree pgt,
6456 permission.perm_list perm,
6457 actor.org_unit_type aout
6459 pgt.name = 'Circulation Administrator' AND
6460 aout.name = 'System' AND
6462 'ADMIN_BOOKING_RESERVATION',
6463 'ADMIN_BOOKING_RESERVATION_ATTR_MAP',
6464 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
6465 'ADMIN_BOOKING_RESOURCE',
6466 'ADMIN_BOOKING_RESOURCE_ATTR',
6467 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
6468 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
6469 'ADMIN_BOOKING_RESOURCE_TYPE',
6470 'ADMIN_COPY_LOCATION_ORDER',
6471 'ADMIN_HOLD_CANCEL_CAUSE',
6472 'ASSIGN_GROUP_PERM',
6475 'COPY_TRANSIT_RECEIVE',
6477 'CREATE_BILLING_TYPE',
6478 'CREATE_NON_CAT_TYPE',
6479 'CREATE_PATRON_STAT_CAT',
6480 'CREATE_PATRON_STAT_CAT_ENTRY',
6481 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
6482 'CREATE_USER_GROUP_LINK',
6483 'DELETE_BILLING_TYPE',
6484 'DELETE_NON_CAT_TYPE',
6485 'DELETE_PATRON_STAT_CAT',
6486 'DELETE_PATRON_STAT_CAT_ENTRY',
6487 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
6489 'group_application.user.staff',
6491 'MARK_ITEM_AVAILABLE',
6492 'MARK_ITEM_BINDERY',
6493 'MARK_ITEM_CHECKED_OUT',
6495 'MARK_ITEM_IN_PROCESS',
6496 'MARK_ITEM_IN_TRANSIT',
6498 'MARK_ITEM_MISSING',
6499 'MARK_ITEM_ON_HOLDS_SHELF',
6500 'MARK_ITEM_ON_ORDER',
6501 'MARK_ITEM_RESHELVING',
6503 'money.collections_tracker.create',
6504 'money.collections_tracker.delete',
6508 'REMOVE_USER_GROUP_LINK',
6509 'SET_CIRC_CLAIMS_RETURNED',
6510 'SET_CIRC_CLAIMS_RETURNED.override',
6515 'UPDATE_NON_CAT_TYPE',
6516 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
6517 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
6518 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
6519 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
6521 'VIEW_REPORT_OUTPUT',
6522 'VIEW_STANDING_PENALTY',
6527 FROM permission.grp_perm_map AS map
6530 AND map.perm = perm.id
6533 INSERT INTO config.upgrade_log (version) VALUES ('0557'); -- miker
6535 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$
6539 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6543 label_prefix AS prefix,
6544 label_suffix AS suffix
6548 FROM asset.copy_location
6552 INSERT INTO config.upgrade_log (version) VALUES ('0558'); -- miker
6554 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$
6558 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6565 FROM config.copy_status
6569 INSERT INTO config.upgrade_log (version) VALUES ('0560'); -- miker
6571 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
6575 do_add BOOLEAN := false;
6576 do_remove BOOLEAN := false;
6579 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
6580 SELECT id, circ_lib, record FROM (
6581 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
6583 JOIN asset.call_number cn ON (cn.id = cp.call_number)
6584 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6585 JOIN asset.copy_location cl ON (cp.location = cl.id)
6586 JOIN config.copy_status cs ON (cp.status = cs.id)
6587 JOIN biblio.record_entry b ON (cn.record = b.id)
6588 WHERE NOT cp.deleted
6596 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
6598 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
6599 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
6600 JOIN asset.copy_location cl ON (cp.location = cl.id)
6601 JOIN config.copy_status cs ON (cp.status = cs.id)
6602 WHERE NOT cp.deleted
6611 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
6613 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
6614 IF TG_OP = 'INSERT' THEN
6615 add_query := add_query || 'WHERE x.id = ' || NEW.target_copy || ' AND x.record = ' || NEW.peer_record || ';';
6619 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
6620 EXECUTE remove_query;
6625 IF TG_OP = 'INSERT' THEN
6627 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6628 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6636 -- handle items first, since with circulation activity
6637 -- their statuses change frequently
6638 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
6640 IF OLD.location <> NEW.location OR
6641 OLD.call_number <> NEW.call_number OR
6642 OLD.status <> NEW.status OR
6643 OLD.circ_lib <> NEW.circ_lib THEN
6644 -- any of these could change visibility, but
6645 -- we'll save some queries and not try to calculate
6646 -- the change directly
6651 IF OLD.deleted <> NEW.deleted THEN
6659 IF OLD.opac_visible <> NEW.opac_visible THEN
6660 IF OLD.opac_visible THEN
6662 ELSIF NOT do_remove THEN -- handle edge case where deleted item
6663 -- is also marked opac_visible
6671 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
6674 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6682 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
6684 IF OLD.deleted AND NEW.deleted THEN -- do nothing
6688 ELSIF NEW.deleted THEN -- remove rows
6690 IF TG_TABLE_NAME = 'call_number' THEN
6691 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
6692 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6693 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
6698 ELSIF OLD.deleted THEN -- add rows
6700 IF TG_TABLE_NAME IN ('copy','unit') THEN
6701 add_query := add_query || 'WHERE x.id = ' || NEW.id || ';';
6702 ELSIF TG_TABLE_NAME = 'call_number' THEN
6703 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6704 ELSIF TG_TABLE_NAME = 'record_entry' THEN
6705 add_query := add_query || 'WHERE x.record = ' || NEW.id || ';';
6715 IF TG_TABLE_NAME = 'call_number' THEN
6717 IF OLD.record <> NEW.record THEN
6718 -- call number is linked to different bib
6719 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
6720 EXECUTE remove_query;
6721 add_query := add_query || 'WHERE x.call_number = ' || NEW.id || ';';
6729 IF TG_TABLE_NAME IN ('record_entry') THEN
6730 RETURN NEW; -- don't have 'opac_visible'
6733 -- actor.org_unit, asset.copy_location, asset.copy_status
6734 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
6738 ELSIF NEW.opac_visible THEN -- add rows
6740 IF TG_TABLE_NAME = 'org_unit' THEN
6741 add_query := add_query || 'WHERE x.circ_lib = ' || NEW.id || ';';
6742 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6743 add_query := add_query || 'WHERE x.location = ' || NEW.id || ';';
6744 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6745 add_query := add_query || 'WHERE x.status = ' || NEW.id || ';';
6752 IF TG_TABLE_NAME = 'org_unit' THEN
6753 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
6754 ELSIF TG_TABLE_NAME = 'copy_location' THEN
6755 remove_query := remove_query || 'location = ' || NEW.id || ');';
6756 ELSIF TG_TABLE_NAME = 'copy_status' THEN
6757 remove_query := remove_query || 'status = ' || NEW.id || ');';
6760 EXECUTE remove_query;
6766 $func$ LANGUAGE PLPGSQL;
6768 INSERT INTO config.upgrade_log (version) VALUES ('0563');
6770 INSERT INTO permission.perm_list ( id, code, description )
6771 VALUES ( 510, 'UPDATE_PATRON_COLLECTIONS_EXEMPT', oils_i18n_gettext(510,
6772 'Allows a user to indicate that a patron is exempt from collections processing', 'ppl', 'description'));
6774 --- stock Circulation Administrator group
6776 INSERT INTO permission.grp_perm_map ( grp, perm, depth, grantable )
6782 FROM permission.perm_list
6783 WHERE code in ('UPDATE_PATRON_COLLECTIONS_EXEMPT');
6785 INSERT INTO config.upgrade_log (version) VALUES ('0566');
6787 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$
6789 me biblio.record_entry%ROWTYPE;
6790 layout unapi.bre_output_layout%ROWTYPE;
6791 xfrm config.xml_transform%ROWTYPE;
6800 SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6802 IF ouid IS NULL THEN
6806 IF format = 'holdings_xml' THEN -- the special case
6807 output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6811 SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6813 IF layout.name IS NULL THEN
6817 SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6819 SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6821 -- grab SVF if we need them
6822 IF ('mra' = ANY (includes)) THEN
6823 axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6828 -- grab hodlings if we need them
6829 IF ('holdings_xml' = ANY (includes)) THEN
6830 hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6836 -- generate our item node
6839 IF format = 'marcxml' THEN
6841 IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6842 tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6845 tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6848 top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6850 IF axml IS NOT NULL THEN
6851 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6854 IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6855 tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6858 IF ('bre.unapi' = ANY (includes)) THEN
6859 output := REGEXP_REPLACE(
6861 '</' || top_el || '>(.*?)',
6865 'http://www.w3.org/1999/xhtml' AS xmlns,
6866 'unapi-id' AS class,
6867 'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6869 )::TEXT || '</' || top_el || E'>\\1'
6875 output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6878 $F$ LANGUAGE PLPGSQL;
6880 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$
6884 CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6885 CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6889 (SELECT XMLAGG(XMLELEMENT::XML) FROM (
6892 XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6894 FROM asset.opac_ou_record_copy_count($2, $1)
6898 XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6900 FROM asset.staff_ou_record_copy_count($2, $1)
6905 WHEN ('bmp' = ANY ($5)) THEN
6907 name monograph_parts,
6908 (SELECT XMLAGG(bmp) FROM (
6909 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)
6910 FROM biblio.monograph_part
6918 (SELECT XMLAGG(acn) FROM (
6919 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)
6920 FROM asset.call_number acn
6921 WHERE acn.record = $1
6925 JOIN actor.org_unit_descendants(
6930 FROM actor.org_unit_type aout
6931 JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6934 ) aoud ON (acp.circ_lib = aoud.id)
6937 ORDER BY label_sortkey
6942 CASE WHEN ('ssub' = ANY ($5)) THEN
6945 (SELECT XMLAGG(ssub) FROM (
6946 SELECT unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6947 FROM serial.subscription
6948 WHERE record_entry = $1
6952 CASE WHEN ('acp' = ANY ($5)) THEN
6954 name foreign_copies,
6955 (SELECT XMLAGG(acp) FROM (
6956 SELECT unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6957 FROM biblio.peer_bib_copy_map p
6958 JOIN asset.copy c ON (p.target_copy = c.id)
6959 WHERE NOT c.deleted AND peer_record = $1
6966 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$
6970 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6971 'tag:open-ils.org:U2@ssub/' || id AS id,
6972 start_date AS start, end_date AS end, expected_date_offset
6974 unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8),
6975 XMLELEMENT( name distributions,
6977 WHEN ('sdist' = ANY ($4)) THEN
6978 (SELECT XMLAGG(sdist) FROM (
6979 SELECT unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
6980 FROM serial.distribution
6981 WHERE subscription = ssub.id
6987 FROM serial.subscription ssub
6989 GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
6992 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$
6996 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6997 'tag:open-ils.org:U2@sdist/' || id AS id,
6998 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
6999 'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
7000 unit_label_prefix, label, unit_label_suffix, summary_method
7002 unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
7003 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,
7004 XMLELEMENT( name streams,
7006 WHEN ('sstr' = ANY ($4)) THEN
7007 (SELECT XMLAGG(sstr) FROM (
7008 SELECT unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7010 WHERE distribution = sdist.id
7015 XMLELEMENT( name summaries,
7017 WHEN ('ssum' = ANY ($4)) THEN
7018 (SELECT XMLAGG(sbsum) FROM (
7019 SELECT unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7020 FROM serial.basic_summary
7021 WHERE distribution = sdist.id
7026 WHEN ('ssum' = ANY ($4)) THEN
7027 (SELECT XMLAGG(sisum) FROM (
7028 SELECT unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7029 FROM serial.index_summary
7030 WHERE distribution = sdist.id
7035 WHEN ('ssum' = ANY ($4)) THEN
7036 (SELECT XMLAGG(sssum) FROM (
7037 SELECT unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
7038 FROM serial.supplement_summary
7039 WHERE distribution = sdist.id
7045 FROM serial.distribution sdist
7047 GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
7050 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$
7054 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7055 'tag:open-ils.org:U2@sstr/' || id AS id,
7058 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,
7059 XMLELEMENT( name items,
7061 WHEN ('sitem' = ANY ($4)) THEN
7062 (SELECT XMLAGG(sitem) FROM (
7063 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
7065 WHERE stream = sstr.id
7071 FROM serial.stream sstr
7073 GROUP BY id, routing_label, distribution;
7076 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$
7080 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7081 'tag:open-ils.org:U2@siss/' || id AS id,
7082 create_date, edit_date, label, date_published,
7083 holding_code, holding_type, holding_link_id
7085 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,
7086 XMLELEMENT( name items,
7088 WHEN ('sitem' = ANY ($4)) THEN
7089 (SELECT XMLAGG(sitem) FROM (
7090 SELECT unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
7092 WHERE issuance = sstr.id
7098 FROM serial.issuance sstr
7100 GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
7103 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$
7107 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7108 'tag:open-ils.org:U2@sitem/' || id AS id,
7109 'tag:open-ils.org:U2@siss/' || issuance AS issuance,
7110 date_expected, date_received
7112 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,
7113 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,
7114 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,
7115 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
7116 -- XMLELEMENT( name notes,
7118 -- WHEN ('acpn' = ANY ($4)) THEN
7119 -- (SELECT XMLAGG(acpn) FROM (
7120 -- SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
7121 -- FROM asset.copy_note
7122 -- WHERE owning_copy = cp.id AND pub
7128 FROM serial.item sitem
7133 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$
7135 name monograph_part,
7137 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7138 'tag:open-ils.org:U2@bmp/' || id AS id,
7142 'tag:open-ils.org:U2@bre/' || record AS record
7145 WHEN ('acp' = ANY ($4)) THEN
7146 XMLELEMENT( name copies,
7147 (SELECT XMLAGG(acp) FROM (
7148 SELECT unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
7150 JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
7152 ORDER BY COALESCE(cp.copy_number,0), cp.barcode
7159 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
7161 FROM biblio.monograph_part
7163 GROUP BY id, label, label_sortkey, record;
7166 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$
7170 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7171 'tag:open-ils.org:U2@acp/' || id AS id,
7172 create_date, edit_date, copy_number, circulate, deposit,
7173 ref, holdable, deleted, deposit_amount, price, barcode,
7174 circ_modifier, circ_as_type, opac_visible
7176 unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7177 unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
7178 unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7179 unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
7180 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,
7181 XMLELEMENT( name copy_notes,
7183 WHEN ('acpn' = ANY ($4)) THEN
7184 (SELECT XMLAGG(acpn) FROM (
7185 SELECT unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7186 FROM asset.copy_note
7187 WHERE owning_copy = cp.id AND pub
7192 XMLELEMENT( name statcats,
7194 WHEN ('ascecm' = ANY ($4)) THEN
7195 (SELECT XMLAGG(ascecm) FROM (
7196 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7197 FROM asset.stat_cat_entry_copy_map
7198 WHERE owning_copy = cp.id
7203 XMLELEMENT( name foreign_records,
7205 WHEN ('bre' = ANY ($4)) THEN
7206 (SELECT XMLAGG(bre) FROM (
7207 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7208 FROM biblio.peer_bib_copy_map
7209 WHERE target_copy = cp.id
7216 WHEN ('bmp' = ANY ($4)) THEN
7217 XMLELEMENT( name monograph_parts,
7218 (SELECT XMLAGG(bmp) FROM (
7219 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7220 FROM asset.copy_part_map
7221 WHERE target_copy = cp.id
7229 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;
7232 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$
7236 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7237 'tag:open-ils.org:U2@acp/' || id AS id,
7238 create_date, edit_date, copy_number, circulate, deposit,
7239 ref, holdable, deleted, deposit_amount, price, barcode,
7240 circ_modifier, circ_as_type, opac_visible, status_changed_time,
7241 floating, mint_condition, detailed_contents, sort_key, summary_contents, cost
7243 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),
7244 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),
7245 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),
7246 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),
7247 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,
7248 XMLELEMENT( name copy_notes,
7250 WHEN ('acpn' = ANY ($4)) THEN
7251 (SELECT XMLAGG(acpn) FROM (
7252 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)
7253 FROM asset.copy_note
7254 WHERE owning_copy = cp.id AND pub
7259 XMLELEMENT( name statcats,
7261 WHEN ('ascecm' = ANY ($4)) THEN
7262 (SELECT XMLAGG(ascecm) FROM (
7263 SELECT unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7264 FROM asset.stat_cat_entry_copy_map
7265 WHERE owning_copy = cp.id
7270 XMLELEMENT( name foreign_records,
7272 WHEN ('bre' = ANY ($4)) THEN
7273 (SELECT XMLAGG(bre) FROM (
7274 SELECT unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
7275 FROM biblio.peer_bib_copy_map
7276 WHERE target_copy = cp.id
7283 WHEN ('bmp' = ANY ($4)) THEN
7284 XMLELEMENT( name monograph_parts,
7285 (SELECT XMLAGG(bmp) FROM (
7286 SELECT unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
7287 FROM asset.copy_part_map
7288 WHERE target_copy = cp.id
7296 GROUP BY id, status, location, circ_lib, call_number, create_date, edit_date, copy_number, circulate, floating, mint_condition,
7297 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;
7300 INSERT INTO config.upgrade_log (version) VALUES ('0568'); -- miker for tsbere
7302 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7306 add_base_query TEXT;
7307 add_peer_query TEXT;
7309 do_add BOOLEAN := false;
7310 do_remove BOOLEAN := false;
7312 add_base_query := $$
7313 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7315 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7316 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7317 JOIN asset.copy_location cl ON (cp.location = cl.id)
7318 JOIN config.copy_status cs ON (cp.status = cs.id)
7319 JOIN biblio.record_entry b ON (cn.record = b.id)
7320 WHERE NOT cp.deleted
7328 add_peer_query := $$
7329 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7331 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7332 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7333 JOIN asset.copy_location cl ON (cp.location = cl.id)
7334 JOIN config.copy_status cs ON (cp.status = cs.id)
7335 WHERE NOT cp.deleted
7342 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7343 SELECT id, circ_lib, record FROM (
7349 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7351 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7352 IF TG_OP = 'INSERT' THEN
7353 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7354 EXECUTE add_front || add_peer_query || add_back;
7357 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7358 EXECUTE remove_query;
7363 IF TG_OP = 'INSERT' THEN
7365 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7366 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7367 EXECUTE add_front || add_base_query || add_back;
7374 -- handle items first, since with circulation activity
7375 -- their statuses change frequently
7376 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7378 IF OLD.location <> NEW.location OR
7379 OLD.call_number <> NEW.call_number OR
7380 OLD.status <> NEW.status OR
7381 OLD.circ_lib <> NEW.circ_lib THEN
7382 -- any of these could change visibility, but
7383 -- we'll save some queries and not try to calculate
7384 -- the change directly
7389 IF OLD.deleted <> NEW.deleted THEN
7397 IF OLD.opac_visible <> NEW.opac_visible THEN
7398 IF OLD.opac_visible THEN
7400 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7401 -- is also marked opac_visible
7409 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7412 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7413 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7414 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7421 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7423 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7427 ELSIF NEW.deleted THEN -- remove rows
7429 IF TG_TABLE_NAME = 'call_number' THEN
7430 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7431 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7432 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7437 ELSIF OLD.deleted THEN -- add rows
7439 IF TG_TABLE_NAME = 'call_number' THEN
7440 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7441 EXECUTE add_front || add_base_query || add_back;
7442 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7443 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7444 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7445 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7454 IF TG_TABLE_NAME = 'call_number' THEN
7456 IF OLD.record <> NEW.record THEN
7457 -- call number is linked to different bib
7458 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7459 EXECUTE remove_query;
7460 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7461 EXECUTE add_front || add_base_query || add_back;
7468 IF TG_TABLE_NAME IN ('record_entry') THEN
7469 RETURN NEW; -- don't have 'opac_visible'
7472 -- actor.org_unit, asset.copy_location, asset.copy_status
7473 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7477 ELSIF NEW.opac_visible THEN -- add rows
7479 IF TG_TABLE_NAME = 'org_unit' THEN
7480 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7481 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id || ';';
7482 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7483 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id || ';';
7484 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id || ';';
7485 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7486 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id || ';';
7487 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id || ';';
7490 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7494 IF TG_TABLE_NAME = 'org_unit' THEN
7495 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7496 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7497 remove_query := remove_query || 'location = ' || NEW.id || ');';
7498 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7499 remove_query := remove_query || 'status = ' || NEW.id || ');';
7502 EXECUTE remove_query;
7508 $func$ LANGUAGE PLPGSQL;
7510 INSERT INTO config.upgrade_log (version) VALUES ('0569'); --miker
7512 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$
7516 CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
7517 'tag:open-ils.org:U2@auri/' || uri.id AS id,
7522 XMLELEMENT( name copies,
7524 WHEN ('acn' = ANY ($4)) THEN
7525 (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)
7532 GROUP BY uri.id, use_restriction, href, label;
7535 INSERT INTO config.upgrade_log (version) VALUES ('0570');
7537 -- Not everything in 1XX tags should become part of the authorsort field
7538 -- ($0 for example). The list of subfields chosen here is a superset of all
7539 -- the fields found in the LoC authority mappin definitions for 1XX fields.
7540 -- Anyway, if more fields should be here, add them.
7542 UPDATE config.record_attr_definition
7543 SET sf_list = 'abcdefgklmnopqrstvxyz'
7544 WHERE name='authorsort' AND sf_list IS NULL;
7546 INSERT INTO config.upgrade_log (version) VALUES ('0571');
7548 -- FIXME: add/check SQL statements to perform the upgrade
7549 CREATE OR REPLACE FUNCTION metabib.facet_normalize_trigger () RETURNS TRIGGER AS $$
7554 facet_text := NEW.value;
7557 SELECT n.func AS func,
7558 n.param_count AS param_count,
7560 FROM config.index_normalizer n
7561 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7562 WHERE m.field = NEW.field AND m.pos < 0
7565 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7566 quote_literal( facet_text ) ||
7568 WHEN normalizer.param_count > 0
7569 THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7572 ')' INTO facet_text;
7576 NEW.value = facet_text;
7580 $$ LANGUAGE PLPGSQL;
7582 CREATE TRIGGER facet_normalize_tgr
7583 BEFORE UPDATE OR INSERT ON metabib.facet_entry
7584 FOR EACH ROW EXECUTE PROCEDURE metabib.facet_normalize_trigger();
7588 INSERT INTO config.upgrade_log (version) VALUES ('0578'); -- tsbere via miker
7590 CREATE OR REPLACE VIEW reporter.hold_request_record AS
7595 WHEN hold_type = 'T'
7597 WHEN hold_type = 'I'
7598 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
7599 WHEN hold_type = 'V'
7600 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
7601 WHEN hold_type IN ('C','R','F')
7602 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
7603 WHEN hold_type = 'M'
7604 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
7605 WHEN hold_type = 'P'
7606 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
7608 FROM action.hold_request ahr;
7610 INSERT INTO config.upgrade_log (version) VALUES ('0583');
7612 CREATE OR REPLACE VIEW action.all_circulation AS
7613 SELECT id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
7614 copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
7615 circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
7616 stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
7617 max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
7618 max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
7619 FROM action.aged_circulation
7621 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,
7622 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,
7623 cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
7624 circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
7625 circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
7626 circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
7628 FROM action.circulation circ
7629 JOIN asset.copy cp ON (circ.target_copy = cp.id)
7630 JOIN asset.call_number cn ON (cp.call_number = cn.id)
7631 JOIN actor.usr p ON (circ.usr = p.id)
7632 LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
7633 LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
7637 INSERT INTO config.upgrade_log (version) VALUES ('0590'); -- miker/tsbere
7639 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
7643 add_base_query TEXT;
7644 add_peer_query TEXT;
7646 do_add BOOLEAN := false;
7647 do_remove BOOLEAN := false;
7649 add_base_query := $$
7650 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
7652 JOIN asset.call_number cn ON (cn.id = cp.call_number)
7653 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7654 JOIN asset.copy_location cl ON (cp.location = cl.id)
7655 JOIN config.copy_status cs ON (cp.status = cs.id)
7656 JOIN biblio.record_entry b ON (cn.record = b.id)
7657 WHERE NOT cp.deleted
7665 add_peer_query := $$
7666 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
7668 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
7669 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
7670 JOIN asset.copy_location cl ON (cp.location = cl.id)
7671 JOIN config.copy_status cs ON (cp.status = cs.id)
7672 WHERE NOT cp.deleted
7679 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
7680 SELECT id, circ_lib, record FROM (
7686 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
7688 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
7689 IF TG_OP = 'INSERT' THEN
7690 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.record = ' || NEW.peer_record;
7691 EXECUTE add_front || add_peer_query || add_back;
7694 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
7695 EXECUTE remove_query;
7700 IF TG_OP = 'INSERT' THEN
7702 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7703 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7704 EXECUTE add_front || add_base_query || add_back;
7711 -- handle items first, since with circulation activity
7712 -- their statuses change frequently
7713 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
7715 IF OLD.location <> NEW.location OR
7716 OLD.call_number <> NEW.call_number OR
7717 OLD.status <> NEW.status OR
7718 OLD.circ_lib <> NEW.circ_lib THEN
7719 -- any of these could change visibility, but
7720 -- we'll save some queries and not try to calculate
7721 -- the change directly
7726 IF OLD.deleted <> NEW.deleted THEN
7734 IF OLD.opac_visible <> NEW.opac_visible THEN
7735 IF OLD.opac_visible THEN
7737 ELSIF NOT do_remove THEN -- handle edge case where deleted item
7738 -- is also marked opac_visible
7746 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
7749 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
7750 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
7751 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7758 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
7760 IF OLD.deleted AND NEW.deleted THEN -- do nothing
7764 ELSIF NEW.deleted THEN -- remove rows
7766 IF TG_TABLE_NAME = 'call_number' THEN
7767 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
7768 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7769 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
7774 ELSIF OLD.deleted THEN -- add rows
7776 IF TG_TABLE_NAME = 'call_number' THEN
7777 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7778 EXECUTE add_front || add_base_query || add_back;
7779 ELSIF TG_TABLE_NAME = 'record_entry' THEN
7780 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
7781 add_peer_query := add_peer_query || ' AND pbcm.record = ' || NEW.id;
7782 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7791 IF TG_TABLE_NAME = 'call_number' THEN
7793 IF OLD.record <> NEW.record THEN
7794 -- call number is linked to different bib
7795 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
7796 EXECUTE remove_query;
7797 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
7798 EXECUTE add_front || add_base_query || add_back;
7805 IF TG_TABLE_NAME IN ('record_entry') THEN
7806 RETURN NEW; -- don't have 'opac_visible'
7809 -- actor.org_unit, asset.copy_location, asset.copy_status
7810 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
7814 ELSIF NEW.opac_visible THEN -- add rows
7816 IF TG_TABLE_NAME = 'org_unit' THEN
7817 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
7818 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
7819 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7820 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
7821 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
7822 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7823 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
7824 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
7827 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
7831 IF TG_TABLE_NAME = 'org_unit' THEN
7832 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
7833 ELSIF TG_TABLE_NAME = 'copy_location' THEN
7834 remove_query := remove_query || 'location = ' || NEW.id || ');';
7835 ELSIF TG_TABLE_NAME = 'copy_status' THEN
7836 remove_query := remove_query || 'status = ' || NEW.id || ');';
7839 EXECUTE remove_query;
7845 $func$ LANGUAGE PLPGSQL;
7847 INSERT INTO config.upgrade_log (version) VALUES ('0591'); -- berick/miker
7849 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
7851 c action.circulation%ROWTYPE;
7853 usr_view_age actor.usr_setting%ROWTYPE;
7854 usr_view_start actor.usr_setting%ROWTYPE;
7856 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
7857 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
7859 IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
7860 -- User opted in and supplied a retention age
7861 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7862 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7864 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7866 ELSIF usr_view_start.value IS NOT NULL THEN
7868 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7870 -- User did not opt in
7876 FROM action.circulation
7878 AND parent_circ IS NULL
7879 AND xact_start > NOW() - view_age
7880 ORDER BY xact_start DESC
7887 $func$ LANGUAGE PLPGSQL;
7889 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
7891 h action.hold_request%ROWTYPE;
7894 usr_view_count actor.usr_setting%ROWTYPE;
7895 usr_view_age actor.usr_setting%ROWTYPE;
7896 usr_view_start actor.usr_setting%ROWTYPE;
7898 SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
7899 SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
7900 SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
7904 FROM action.hold_request
7906 AND fulfillment_time IS NULL
7907 AND cancel_time IS NULL
7908 ORDER BY request_time DESC
7913 IF usr_view_start.value IS NULL THEN
7917 IF usr_view_age.value IS NOT NULL THEN
7918 -- User opted in and supplied a retention age
7919 IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
7920 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7922 view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
7926 view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
7929 IF usr_view_count.value IS NOT NULL THEN
7930 view_count := oils_json_to_text(usr_view_count.value)::INT;
7935 -- show some fulfilled/canceled holds
7938 FROM action.hold_request
7940 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
7941 AND request_time > NOW() - view_age
7942 ORDER BY request_time DESC
7950 $func$ LANGUAGE PLPGSQL;
7952 INSERT INTO config.upgrade_log (version) VALUES ('0599'); -- miker/gmc
7954 UPDATE config.metabib_field
7955 SET xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role/mods32:roleTerm[text()='creator'])]$$
7956 WHERE field_class = 'author'
7958 AND xpath = $$//mods32:mods/mods32:name[@type='personal' and not(mods32:role)]$$
7959 AND format = 'mods32';
7961 \qecho To reindex bibs that use the author|other index definition,
7962 \qecho you can run something like this:
7964 \qecho SELECT metabib.reingest_metabib_field_entries(record)
7966 \qecho SELECT DISTINCT record
7967 \qecho FROM metabib.real_full_rec
7968 \qecho WHERE tag IN ('600', '700', '720', '800')
7969 \qecho AND subfield IN ('4', 'e')
7972 -- Resolves an error in calculating copy counts for org lassos
7974 INSERT INTO config.upgrade_log (version) VALUES ('0603');
7976 -- FIXME: add/check SQL statements to perform the upgrade
7977 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$
7982 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;
7984 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
7989 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
7993 actor.org_unit_descendants(ans.id) d
7994 JOIN asset.opac_visible_copies av ON (av.record = rid AND av.circ_lib = d.id)
7995 JOIN asset.copy cp ON (cp.id = av.copy_id)
7999 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8006 $f$ LANGUAGE PLPGSQL;
8009 -- Staff record copy counts also triggered an SQL error for org lassos
8012 INSERT INTO config.upgrade_log (version) VALUES ('0604');
8014 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$
8019 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;
8021 FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
8026 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
8030 actor.org_unit_descendants(ans.id) d
8031 JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
8032 JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
8036 RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
8043 $f$ LANGUAGE PLPGSQL;
8045 INSERT INTO config.upgrade_log (version) VALUES ('0614'); --miker/phasefx
8047 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
8051 add_base_query TEXT;
8052 add_peer_query TEXT;
8054 do_add BOOLEAN := false;
8055 do_remove BOOLEAN := false;
8057 add_base_query := $$
8058 SELECT cp.id, cp.circ_lib, cn.record, cn.id AS call_number, cp.location, cp.status
8060 JOIN asset.call_number cn ON (cn.id = cp.call_number)
8061 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8062 JOIN asset.copy_location cl ON (cp.location = cl.id)
8063 JOIN config.copy_status cs ON (cp.status = cs.id)
8064 JOIN biblio.record_entry b ON (cn.record = b.id)
8065 WHERE NOT cp.deleted
8073 add_peer_query := $$
8074 SELECT cp.id, cp.circ_lib, pbcm.peer_record AS record, NULL AS call_number, cp.location, cp.status
8076 JOIN biblio.peer_bib_copy_map pbcm ON (pbcm.target_copy = cp.id)
8077 JOIN actor.org_unit a ON (cp.circ_lib = a.id)
8078 JOIN asset.copy_location cl ON (cp.location = cl.id)
8079 JOIN config.copy_status cs ON (cp.status = cs.id)
8080 WHERE NOT cp.deleted
8087 INSERT INTO asset.opac_visible_copies (copy_id, circ_lib, record)
8088 SELECT id, circ_lib, record FROM (
8094 remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE copy_id IN ( SELECT id FROM asset.copy WHERE $$;
8096 IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN
8097 IF TG_OP = 'INSERT' THEN
8098 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.target_copy || ' AND pbcm.peer_record = ' || NEW.peer_record;
8099 EXECUTE add_front || add_peer_query || add_back;
8102 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE copy_id = ' || OLD.target_copy || ' AND record = ' || OLD.peer_record || ';';
8103 EXECUTE remove_query;
8108 IF TG_OP = 'INSERT' THEN
8110 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8111 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8112 EXECUTE add_front || add_base_query || add_back;
8119 -- handle items first, since with circulation activity
8120 -- their statuses change frequently
8121 IF TG_TABLE_NAME IN ('copy', 'unit') THEN
8123 IF OLD.location <> NEW.location OR
8124 OLD.call_number <> NEW.call_number OR
8125 OLD.status <> NEW.status OR
8126 OLD.circ_lib <> NEW.circ_lib THEN
8127 -- any of these could change visibility, but
8128 -- we'll save some queries and not try to calculate
8129 -- the change directly
8134 IF OLD.deleted <> NEW.deleted THEN
8142 IF OLD.opac_visible <> NEW.opac_visible THEN
8143 IF OLD.opac_visible THEN
8145 ELSIF NOT do_remove THEN -- handle edge case where deleted item
8146 -- is also marked opac_visible
8154 DELETE FROM asset.opac_visible_copies WHERE copy_id = NEW.id;
8157 add_base_query := add_base_query || ' AND cp.id = ' || NEW.id;
8158 add_peer_query := add_peer_query || ' AND cp.id = ' || NEW.id;
8159 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8166 IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
8168 IF OLD.deleted AND NEW.deleted THEN -- do nothing
8172 ELSIF NEW.deleted THEN -- remove rows
8174 IF TG_TABLE_NAME = 'call_number' THEN
8175 DELETE FROM asset.opac_visible_copies WHERE copy_id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
8176 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8177 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
8182 ELSIF OLD.deleted THEN -- add rows
8184 IF TG_TABLE_NAME = 'call_number' THEN
8185 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8186 EXECUTE add_front || add_base_query || add_back;
8187 ELSIF TG_TABLE_NAME = 'record_entry' THEN
8188 add_base_query := add_base_query || ' AND cn.record = ' || NEW.id;
8189 add_peer_query := add_peer_query || ' AND pbcm.peer_record = ' || NEW.id;
8190 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8199 IF TG_TABLE_NAME = 'call_number' THEN
8201 IF OLD.record <> NEW.record THEN
8202 -- call number is linked to different bib
8203 remove_query := remove_query || 'call_number = ' || NEW.id || ');';
8204 EXECUTE remove_query;
8205 add_base_query := add_base_query || ' AND cn.id = ' || NEW.id;
8206 EXECUTE add_front || add_base_query || add_back;
8213 IF TG_TABLE_NAME IN ('record_entry') THEN
8214 RETURN NEW; -- don't have 'opac_visible'
8217 -- actor.org_unit, asset.copy_location, asset.copy_status
8218 IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
8222 ELSIF NEW.opac_visible THEN -- add rows
8224 IF TG_TABLE_NAME = 'org_unit' THEN
8225 add_base_query := add_base_query || ' AND cp.circ_lib = ' || NEW.id;
8226 add_peer_query := add_peer_query || ' AND cp.circ_lib = ' || NEW.id;
8227 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8228 add_base_query := add_base_query || ' AND cp.location = ' || NEW.id;
8229 add_peer_query := add_peer_query || ' AND cp.location = ' || NEW.id;
8230 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8231 add_base_query := add_base_query || ' AND cp.status = ' || NEW.id;
8232 add_peer_query := add_peer_query || ' AND cp.status = ' || NEW.id;
8235 EXECUTE add_front || add_base_query || ' UNION ' || add_peer_query || add_back;
8239 IF TG_TABLE_NAME = 'org_unit' THEN
8240 remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
8241 ELSIF TG_TABLE_NAME = 'copy_location' THEN
8242 remove_query := remove_query || 'location = ' || NEW.id || ');';
8243 ELSIF TG_TABLE_NAME = 'copy_status' THEN
8244 remove_query := remove_query || 'status = ' || NEW.id || ');';
8247 EXECUTE remove_query;
8253 $func$ LANGUAGE PLPGSQL;
8255 INSERT INTO config.upgrade_log (version) VALUES ('0579'); -- superceded by 0620
8256 INSERT INTO config.upgrade_log (version) VALUES ('0620'); -- tsbere via miker
8258 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$
8260 user_object actor.usr%ROWTYPE;
8261 standing_penalty config.standing_penalty%ROWTYPE;
8262 item_object asset.copy%ROWTYPE;
8263 item_status_object config.copy_status%ROWTYPE;
8264 item_location_object asset.copy_location%ROWTYPE;
8265 result action.circ_matrix_test_result;
8266 circ_test action.found_circ_matrix_matchpoint;
8267 circ_matchpoint config.circ_matrix_matchpoint%ROWTYPE;
8268 out_by_circ_mod config.circ_matrix_circ_mod_test%ROWTYPE;
8269 circ_mod_map config.circ_matrix_circ_mod_test_map%ROWTYPE;
8270 hold_ratio action.hold_stats%ROWTYPE;
8273 context_org_list INT[];
8276 -- Assume success unless we hit a failure condition
8277 result.success := TRUE;
8279 -- Need user info to look up matchpoints
8280 SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
8282 -- (Insta)Fail if we couldn't find the user
8283 IF user_object.id IS NULL THEN
8284 result.fail_part := 'no_user';
8285 result.success := FALSE;
8291 -- Need item info to look up matchpoints
8292 SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
8294 -- (Insta)Fail if we couldn't find the item
8295 IF item_object.id IS NULL THEN
8296 result.fail_part := 'no_item';
8297 result.success := FALSE;
8303 SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
8305 circ_matchpoint := circ_test.matchpoint;
8306 result.matchpoint := circ_matchpoint.id;
8307 result.circulate := circ_matchpoint.circulate;
8308 result.duration_rule := circ_matchpoint.duration_rule;
8309 result.recurring_fine_rule := circ_matchpoint.recurring_fine_rule;
8310 result.max_fine_rule := circ_matchpoint.max_fine_rule;
8311 result.hard_due_date := circ_matchpoint.hard_due_date;
8312 result.renewals := circ_matchpoint.renewals;
8313 result.grace_period := circ_matchpoint.grace_period;
8314 result.buildrows := circ_test.buildrows;
8316 -- (Insta)Fail if we couldn't find a matchpoint
8317 IF circ_test.success = false THEN
8318 result.fail_part := 'no_matchpoint';
8319 result.success := FALSE;
8325 -- All failures before this point are non-recoverable
8326 -- Below this point are possibly overridable failures
8328 -- Fail if the user is barred
8329 IF user_object.barred IS TRUE THEN
8330 result.fail_part := 'actor.usr.barred';
8331 result.success := FALSE;
8336 -- Fail if the item can't circulate
8337 IF item_object.circulate IS FALSE THEN
8338 result.fail_part := 'asset.copy.circulate';
8339 result.success := FALSE;
8344 -- Fail if the item isn't in a circulateable status on a non-renewal
8345 IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN
8346 result.fail_part := 'asset.copy.status';
8347 result.success := FALSE;
8350 -- Alternately, fail if the item isn't checked out on a renewal
8351 ELSIF renewal AND item_object.status <> 1 THEN
8352 result.fail_part := 'asset.copy.status';
8353 result.success := FALSE;
8358 -- Fail if the item can't circulate because of the shelving location
8359 SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8360 IF item_location_object.circulate IS FALSE THEN
8361 result.fail_part := 'asset.copy_location.circulate';
8362 result.success := FALSE;
8367 -- Use Circ OU for penalties and such
8368 SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_ou );
8371 penalty_type = '%RENEW%';
8373 penalty_type = '%CIRC%';
8376 FOR standing_penalty IN
8377 SELECT DISTINCT csp.*
8378 FROM actor.usr_standing_penalty usp
8379 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8380 WHERE usr = match_user
8381 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8382 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8383 AND csp.block_list LIKE penalty_type LOOP
8385 result.fail_part := standing_penalty.name;
8386 result.success := FALSE;
8391 -- Fail if the test is set to hard non-circulating
8392 IF circ_matchpoint.circulate IS FALSE THEN
8393 result.fail_part := 'config.circ_matrix_test.circulate';
8394 result.success := FALSE;
8399 -- Fail if the total copy-hold ratio is too low
8400 IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
8401 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8402 IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
8403 result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8404 result.success := FALSE;
8410 -- Fail if the available copy-hold ratio is too low
8411 IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
8412 IF hold_ratio.hold_count IS NULL THEN
8413 SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8415 IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
8416 result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8417 result.success := FALSE;
8423 -- Fail if the user has too many items with specific circ_modifiers checked out
8424 FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_matchpoint.id LOOP
8425 SELECT INTO items_out COUNT(*)
8426 FROM action.circulation circ
8427 JOIN asset.copy cp ON (cp.id = circ.target_copy)
8428 WHERE circ.usr = match_user
8429 AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8430 AND circ.checkin_time IS NULL
8431 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8432 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);
8433 IF items_out >= out_by_circ_mod.items_out THEN
8434 result.fail_part := 'config.circ_matrix_circ_mod_test';
8435 result.success := FALSE;
8441 -- If we passed everything, return the successful matchpoint
8448 $func$ LANGUAGE plpgsql;
8452 INSERT INTO config.upgrade_log (version) VALUES ('0628');
8454 -- acq.fund_combined_balance and acq.fund_spent_balance are unchanged,
8455 -- however we need to drop them to recreate the other views.
8456 -- we need to drop all our views because we change the number of columns
8457 -- for example, debit_total does not need an encumberance column when we
8458 -- have a sepearate total for that.
8460 DROP VIEW acq.fund_spent_balance;
8461 DROP VIEW acq.fund_combined_balance;
8462 DROP VIEW acq.fund_encumbrance_total;
8463 DROP VIEW acq.fund_spent_total;
8464 DROP VIEW acq.fund_debit_total;
8466 CREATE OR REPLACE VIEW acq.fund_debit_total AS
8467 SELECT fund.id AS fund,
8468 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8470 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8473 CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS
8476 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8478 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8479 WHERE fund_debit.encumbrance GROUP BY fund.id;
8481 CREATE OR REPLACE VIEW acq.fund_spent_total AS
8482 SELECT fund.id AS fund,
8483 sum(COALESCE(fund_debit.amount, 0::numeric)) AS amount
8485 LEFT JOIN acq.fund_debit fund_debit ON fund.id = fund_debit.fund
8486 WHERE NOT fund_debit.encumbrance
8489 CREATE OR REPLACE VIEW acq.fund_combined_balance AS
8491 c.amount - COALESCE(d.amount, 0.0) AS amount
8492 FROM acq.fund_allocation_total c
8493 LEFT JOIN acq.fund_debit_total d USING (fund);
8495 CREATE OR REPLACE VIEW acq.fund_spent_balance AS
8497 c.amount - COALESCE(d.amount,0.0) AS amount
8498 FROM acq.fund_allocation_total c
8499 LEFT JOIN acq.fund_spent_total d USING (fund);
8503 INSERT INTO config.upgrade_log (version) VALUES ('0631');
8505 CREATE OR REPLACE FUNCTION search.query_parser_fts (
8507 param_search_ou INT,
8510 param_statuses INT[],
8511 param_locations INT[],
8518 ) RETURNS SETOF search.search_result AS $func$
8521 current_res search.search_result%ROWTYPE;
8522 search_org_list INT[];
8523 luri_org_list INT[];
8532 core_cursor REFCURSOR;
8533 core_rel_query TEXT;
8535 total_count INT := 0;
8536 check_count INT := 0;
8537 deleted_count INT := 0;
8538 visible_count INT := 0;
8539 excluded_count INT := 0;
8543 check_limit := COALESCE( param_check, 1000 );
8544 core_limit := COALESCE( param_limit, 25000 );
8545 core_offset := COALESCE( param_offset, 0 );
8547 -- core_skip_chk := COALESCE( param_skip_chk, 1 );
8549 IF param_search_ou > 0 THEN
8550 IF param_depth IS NOT NULL THEN
8551 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
8553 SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
8556 SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
8558 ELSIF param_search_ou < 0 THEN
8559 SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
8561 FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
8562 SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
8563 luri_org_list := luri_org_list || tmp_int_list;
8566 SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
8568 ELSIF param_search_ou = 0 THEN
8569 -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
8572 OPEN core_cursor FOR EXECUTE param_query;
8576 FETCH core_cursor INTO core_result;
8577 EXIT WHEN NOT FOUND;
8578 EXIT WHEN total_count >= core_limit;
8580 total_count := total_count + 1;
8582 CONTINUE WHEN total_count NOT BETWEEN core_offset + 1 AND check_limit + core_offset;
8584 check_count := check_count + 1;
8586 PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8588 -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
8589 deleted_count := deleted_count + 1;
8594 FROM biblio.record_entry b
8595 JOIN config.bib_source s ON (b.source = s.id)
8596 WHERE s.transcendant
8597 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
8600 -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
8601 visible_count := visible_count + 1;
8603 current_res.id = core_result.id;
8604 current_res.rel = core_result.rel;
8608 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8612 current_res.record = core_result.records[1];
8614 current_res.record = NULL;
8617 RETURN NEXT current_res;
8623 FROM asset.call_number cn
8624 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
8625 JOIN asset.uri uri ON (map.uri = uri.id)
8626 WHERE NOT cn.deleted
8627 AND cn.label = '##URI##'
8629 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
8630 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8631 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
8635 -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
8636 visible_count := visible_count + 1;
8638 current_res.id = core_result.id;
8639 current_res.rel = core_result.rel;
8643 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8647 current_res.record = core_result.records[1];
8649 current_res.record = NULL;
8652 RETURN NEXT current_res;
8657 IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
8660 FROM asset.call_number cn
8661 JOIN asset.copy cp ON (cp.call_number = cn.id)
8662 WHERE NOT cn.deleted
8664 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8665 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8666 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8671 FROM biblio.peer_bib_copy_map pr
8672 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8673 WHERE NOT cp.deleted
8674 AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
8675 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8676 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8680 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
8681 excluded_count := excluded_count + 1;
8688 IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
8691 FROM asset.call_number cn
8692 JOIN asset.copy cp ON (cp.call_number = cn.id)
8693 WHERE NOT cn.deleted
8695 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8696 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8697 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8702 FROM biblio.peer_bib_copy_map pr
8703 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8704 WHERE NOT cp.deleted
8705 AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
8706 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8707 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8711 -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
8712 excluded_count := excluded_count + 1;
8719 IF staff IS NULL OR NOT staff THEN
8722 FROM asset.opac_visible_copies
8723 WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8724 AND record IN ( SELECT * FROM unnest( core_result.records ) )
8729 FROM biblio.peer_bib_copy_map pr
8730 JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
8731 WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8732 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8737 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8738 excluded_count := excluded_count + 1;
8746 FROM asset.call_number cn
8747 JOIN asset.copy cp ON (cp.call_number = cn.id)
8748 WHERE NOT cn.deleted
8750 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8751 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8757 FROM biblio.peer_bib_copy_map pr
8758 JOIN asset.copy cp ON (cp.id = pr.target_copy)
8759 WHERE NOT cp.deleted
8760 AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
8761 AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
8767 FROM asset.call_number cn
8768 JOIN asset.copy cp ON (cp.call_number = cn.id)
8769 WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
8774 -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
8775 excluded_count := excluded_count + 1;
8784 visible_count := visible_count + 1;
8786 current_res.id = core_result.id;
8787 current_res.rel = core_result.rel;
8791 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
8795 current_res.record = core_result.records[1];
8797 current_res.record = NULL;
8800 RETURN NEXT current_res;
8802 IF visible_count % 1000 = 0 THEN
8803 -- RAISE NOTICE ' % visible so far ... ', visible_count;
8808 current_res.id = NULL;
8809 current_res.rel = NULL;
8810 current_res.record = NULL;
8811 current_res.total = total_count;
8812 current_res.checked = check_count;
8813 current_res.deleted = deleted_count;
8814 current_res.visible = visible_count;
8815 current_res.excluded = excluded_count;
8819 RETURN NEXT current_res;
8822 $func$ LANGUAGE PLPGSQL;
8825 INSERT INTO config.upgrade_log (version) VALUES ('0633');
8826 INSERT INTO config.upgrade_log (version) VALUES ('0634');
8831 INSERT into config.org_unit_setting_type
8832 ( name, grp, label, description, datatype ) VALUES
8834 'print.custom_js_file', 'circ',
8836 'print.custom_js_file',
8837 'Printing: Custom Javascript File',
8842 'print.custom_js_file',
8843 'Full URL path to a Javascript File to be loaded when printing. Should'
8844 || ' implement a print_custom function for DOM manipulation. Can change'
8845 || ' the value of the do_print variable to false to cancel printing.',
8854 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8855 ( 513, 'DEBUG_CLIENT', oils_i18n_gettext( 513,
8856 'Allows a user to use debug functions in the staff client', 'ppl', 'description' ));
8860 INSERT INTO config.org_unit_setting_type
8861 ( name, label, description, datatype ) VALUES
8862 ( 'circ.user_merge.delete_addresses',
8863 'Circ: Patron Merge Address Delete',
8864 'Delete address(es) of subordinate user(s) in a patron merge',
8868 INSERT INTO config.org_unit_setting_type
8869 ( name, label, description, datatype ) VALUES
8870 ( 'circ.user_merge.delete_cards',
8871 'Circ: Patron Merge Barcode Delete',
8872 'Delete barcode(s) of subordinate user(s) in a patron merge',
8876 INSERT INTO config.org_unit_setting_type
8877 ( name, label, description, datatype ) VALUES
8878 ( 'circ.user_merge.deactivate_cards',
8879 'Circ: Patron Merge Deactivate Card',
8880 'Mark barcode(s) of subordinate user(s) in a patron merge as inactive',
8884 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.cash_payment;
8885 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.cash_payment;
8886 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.cash_payment;
8888 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('cash_payment');
8889 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('cash_payment');
8890 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.cash_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('cash_payment');
8892 DROP TRIGGER IF EXISTS mat_summary_add_tgr ON money.check_payment;
8893 DROP TRIGGER IF EXISTS mat_summary_upd_tgr ON money.check_payment;
8894 DROP TRIGGER IF EXISTS mat_summary_del_tgr ON money.check_payment;
8896 CREATE TRIGGER mat_summary_add_tgr AFTER INSERT ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_add ('check_payment');
8897 CREATE TRIGGER mat_summary_upd_tgr AFTER UPDATE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_update ('check_payment');
8898 CREATE TRIGGER mat_summary_del_tgr BEFORE DELETE ON money.check_payment FOR EACH ROW EXECUTE PROCEDURE money.materialized_summary_payment_del ('check_payment');
8901 UPDATE metabib.record_attr
8902 SET attrs = attrs || asort
8903 FROM (SELECT record,
8904 HSTORE('authorsort',FIRST(value)) AS asort
8905 FROM metabib.full_rec
8908 WHERE x.record = metabib.record_attr.id;
8910 UPDATE metabib.record_attr
8911 SET attrs = attrs || tsort
8912 FROM (SELECT record,
8913 HSTORE('titlesort',FIRST(value)) AS tsort
8914 FROM metabib.full_rec
8917 WHERE x.record = metabib.record_attr.id;