]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
LP#1772028 Add some FK violation functions just in case they are missing
[Evergreen.git] / Open-ILS / src / sql / Pg / version-upgrade / 2.1-2.2-upgrade-db.sql
1 --Upgrade Script for 2.1 to 2.2.0
2
3 -- Don't require use of -vegversion=something
4 \set eg_version '''2.2.0'''
5
6 -- DROP objects that might have existed from a prior run of 0526
7 -- Yes this is ironic.
8 DROP TABLE IF EXISTS config.db_patch_dependencies;
9 ALTER TABLE config.upgrade_log DROP COLUMN IF EXISTS applied_to;
10 DROP FUNCTION IF EXISTS evergreen.upgrade_list_applied_deprecates(TEXT);
11 DROP FUNCTION IF EXISTS evergreen.upgrade_list_applied_supersedes(TEXT);
12
13 BEGIN;
14 INSERT INTO config.upgrade_log (version) VALUES ('2.2.0');
15
16 INSERT INTO config.upgrade_log (version) VALUES ('0526'); --miker
17
18 CREATE TABLE config.db_patch_dependencies (
19   db_patch      TEXT PRIMARY KEY,
20   supersedes    TEXT[],
21   deprecates    TEXT[]
22 );
23
24 CREATE OR REPLACE FUNCTION evergreen.array_overlap_check (/* field */) RETURNS TRIGGER AS $$
25 DECLARE
26     fld     TEXT;
27     cnt     INT;
28 BEGIN
29     fld := TG_ARGV[1];
30     EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
31     IF cnt > 0 THEN
32         RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
33     END IF;
34     RETURN NEW;
35 END;
36 $$ LANGUAGE PLPGSQL;
37
38 CREATE TRIGGER no_overlapping_sups
39     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
40     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('supersedes');
41
42 CREATE TRIGGER no_overlapping_deps
43     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
44     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
45
46 ALTER TABLE config.upgrade_log
47     ADD COLUMN applied_to TEXT;
48
49 -- Provide a named type for patching functions
50 CREATE TYPE evergreen.patch AS (patch TEXT);
51
52 -- List applied db patches that are deprecated by (and block the application of) my_db_patch
53 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
54     SELECT  DISTINCT l.version
55       FROM  config.upgrade_log l
56             JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.deprecates)
57       WHERE d.db_patch = $1
58 $$ LANGUAGE SQL;
59
60 -- List applied db patches that are superseded by (and block the application of) my_db_patch
61 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
62     SELECT  DISTINCT l.version
63       FROM  config.upgrade_log l
64             JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.supersedes)
65       WHERE d.db_patch = $1
66 $$ LANGUAGE SQL;
67
68 -- List applied db patches that deprecates (and block the application of) my_db_patch
69 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS TEXT AS $$
70     SELECT  db_patch
71       FROM  config.db_patch_dependencies
72       WHERE ARRAY[$1]::TEXT[] && deprecates
73 $$ LANGUAGE SQL;
74
75 -- List applied db patches that supersedes (and block the application of) my_db_patch
76 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS TEXT AS $$
77     SELECT  db_patch
78       FROM  config.db_patch_dependencies
79       WHERE ARRAY[$1]::TEXT[] && supersedes
80 $$ LANGUAGE SQL;
81
82 -- Make sure that no deprecated or superseded db patches are currently applied
83 CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
84     SELECT  COUNT(*) = 0
85       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
86                 UNION
87              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
88                 UNION
89              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
90                 UNION
91              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
92 $$ LANGUAGE SQL;
93
94 -- Raise an exception if there are, in fact, dep/sup confilct
95 CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
96 DECLARE 
97     deprecates TEXT;
98     supersedes TEXT;
99 BEGIN
100     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
101         SELECT  STRING_AGG(patch, ', ') INTO deprecates FROM evergreen.upgrade_list_applied_deprecates(my_db_patch);
102         SELECT  STRING_AGG(patch, ', ') INTO supersedes FROM evergreen.upgrade_list_applied_supersedes(my_db_patch);
103         RAISE EXCEPTION '
104 Upgrade script % can not be applied:
105   applied deprecated scripts %
106   applied superseded scripts %
107   deprecated by %
108   superseded by %',
109             my_db_patch,
110             ARRAY_AGG(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
111             ARRAY_AGG(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
112             evergreen.upgrade_list_applied_deprecated(my_db_patch),
113             evergreen.upgrade_list_applied_superseded(my_db_patch);
114     END IF;
115
116     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
117     RETURN TRUE;
118 END;
119 $$ LANGUAGE PLPGSQL;
120
121 -- Evergreen DB patch 0536.schema.lazy_circ-barcode_lookup.sql
122 --
123 -- FIXME: insert description of change, if needed
124 --
125
126 -- check whether patch can be applied
127 INSERT INTO config.upgrade_log (version) VALUES ('0536');
128
129 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype) VALUES ( 'circ.staff_client.actor_on_checkout', 'Load patron from Checkout', 'When scanning barcodes into Checkout auto-detect if a new patron barcode is scanned and auto-load the new patron.', 'bool');
130
131 CREATE TABLE config.barcode_completion (
132     id          SERIAL  PRIMARY KEY,
133     active      BOOL    NOT NULL DEFAULT true,
134     org_unit    INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
135     prefix      TEXT,
136     suffix      TEXT,
137     length      INT     NOT NULL DEFAULT 0,
138     padding     TEXT,
139     padding_end BOOL    NOT NULL DEFAULT false,
140     asset       BOOL    NOT NULL DEFAULT true,
141     actor       BOOL    NOT NULL DEFAULT true
142 );
143
144 CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT);
145
146 CREATE OR REPLACE FUNCTION evergreen.get_barcodes(select_ou INT, type TEXT, in_barcode TEXT) RETURNS SETOF evergreen.barcode_set AS $$
147 DECLARE
148     cur_barcode TEXT;
149     barcode_len INT;
150     completion_len  INT;
151     asset_barcodes  TEXT[];
152     actor_barcodes  TEXT[];
153     do_asset    BOOL = false;
154     do_serial   BOOL = false;
155     do_booking  BOOL = false;
156     do_actor    BOOL = false;
157     completion_set  config.barcode_completion%ROWTYPE;
158 BEGIN
159
160     IF position('asset' in type) > 0 THEN
161         do_asset = true;
162     END IF;
163     IF position('serial' in type) > 0 THEN
164         do_serial = true;
165     END IF;
166     IF position('booking' in type) > 0 THEN
167         do_booking = true;
168     END IF;
169     IF do_asset OR do_serial OR do_booking THEN
170         asset_barcodes = asset_barcodes || in_barcode;
171     END IF;
172     IF position('actor' in type) > 0 THEN
173         do_actor = true;
174         actor_barcodes = actor_barcodes || in_barcode;
175     END IF;
176
177     barcode_len := length(in_barcode);
178
179     FOR completion_set IN
180       SELECT * FROM config.barcode_completion
181         WHERE active
182         AND org_unit IN (SELECT aou.id FROM actor.org_unit_ancestors(select_ou) aou)
183         LOOP
184         IF completion_set.prefix IS NULL THEN
185             completion_set.prefix := '';
186         END IF;
187         IF completion_set.suffix IS NULL THEN
188             completion_set.suffix := '';
189         END IF;
190         IF completion_set.length = 0 OR completion_set.padding IS NULL OR length(completion_set.padding) = 0 THEN
191             cur_barcode = completion_set.prefix || in_barcode || completion_set.suffix;
192         ELSE
193             completion_len = completion_set.length - length(completion_set.prefix) - length(completion_set.suffix);
194             IF completion_len >= barcode_len THEN
195                 IF completion_set.padding_end THEN
196                     cur_barcode = rpad(in_barcode, completion_len, completion_set.padding);
197                 ELSE
198                     cur_barcode = lpad(in_barcode, completion_len, completion_set.padding);
199                 END IF;
200                 cur_barcode = completion_set.prefix || cur_barcode || completion_set.suffix;
201             END IF;
202         END IF;
203         IF completion_set.actor THEN
204             actor_barcodes = actor_barcodes || cur_barcode;
205         END IF;
206         IF completion_set.asset THEN
207             asset_barcodes = asset_barcodes || cur_barcode;
208         END IF;
209     END LOOP;
210
211     IF do_asset AND do_serial THEN
212         RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM ONLY asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
213         RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
214     ELSIF do_asset THEN
215         RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
216     ELSIF do_serial THEN
217         RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
218     END IF;
219     IF do_booking THEN
220         RETURN QUERY SELECT 'booking'::TEXT, id::BIGINT, barcode FROM booking.resource WHERE barcode = ANY(asset_barcodes);
221     END IF;
222     IF do_actor THEN
223         RETURN QUERY SELECT 'actor'::TEXT, c.usr::BIGINT, c.barcode FROM actor.card c JOIN actor.usr u ON c.usr = u.id WHERE c.barcode = ANY(actor_barcodes) AND c.active AND NOT u.deleted ORDER BY usr;
224     END IF;
225     RETURN;
226 END;
227 $$ LANGUAGE plpgsql;
228
229 COMMENT ON FUNCTION evergreen.get_barcodes(INT, TEXT, TEXT) IS $$
230 Given user input, find an appropriate barcode in the proper class.
231
232 Will add prefix/suffix information to do so, and return all results.
233 $$;
234
235
236
237 INSERT INTO config.upgrade_log (version) VALUES ('0537'); --miker
238
239 DROP FUNCTION evergreen.upgrade_deps_block_check(text,text);
240 DROP FUNCTION evergreen.upgrade_verify_no_dep_conflicts(text);
241 DROP FUNCTION evergreen.upgrade_list_applied_deprecated(text);
242 DROP FUNCTION evergreen.upgrade_list_applied_superseded(text);
243
244 -- List applied db patches that deprecates (and block the application of) my_db_patch
245 CREATE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
246     SELECT  db_patch
247       FROM  config.db_patch_dependencies
248       WHERE ARRAY[$1]::TEXT[] && deprecates
249 $$ LANGUAGE SQL;
250
251 -- List applied db patches that supersedes (and block the application of) my_db_patch
252 CREATE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
253     SELECT  db_patch
254       FROM  config.db_patch_dependencies
255       WHERE ARRAY[$1]::TEXT[] && supersedes
256 $$ LANGUAGE SQL;
257
258 -- Make sure that no deprecated or superseded db patches are currently applied
259 CREATE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
260     SELECT  COUNT(*) = 0
261       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
262                 UNION
263              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
264                 UNION
265              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
266                 UNION
267              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
268 $$ LANGUAGE SQL;
269
270 -- Raise an exception if there are, in fact, dep/sup confilct
271 CREATE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
272 BEGIN
273     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
274         RAISE EXCEPTION '
275 Upgrade script % can not be applied:
276   applied deprecated scripts %
277   applied superseded scripts %
278   deprecated by %
279   superseded by %',
280             my_db_patch,
281             ARRAY_ACCUM(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
282             ARRAY_ACCUM(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
283             evergreen.upgrade_list_applied_deprecated(my_db_patch),
284             evergreen.upgrade_list_applied_superseded(my_db_patch);
285     END IF;
286
287     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
288     RETURN TRUE;
289 END;
290 $$ LANGUAGE PLPGSQL;
291
292
293 INSERT INTO config.upgrade_log (version) VALUES ('0544');
294
295 INSERT INTO config.usr_setting_type 
296 ( name, opac_visible, label, description, datatype) VALUES 
297 ( 'circ.collections.exempt',
298   FALSE, 
299   oils_i18n_gettext('circ.collections.exempt', 'Collections: Exempt', 'cust', 'description'),
300   oils_i18n_gettext('circ.collections.exempt', 'User is exempt from collections tracking/processing', 'cust', 'description'),
301   'bool'
302 );
303
304
305
306 SELECT evergreen.upgrade_deps_block_check('0545', :eg_version);
307
308 INSERT INTO permission.perm_list VALUES
309  (507, 'ABORT_TRANSIT_ON_LOST', oils_i18n_gettext(507, 'Allows a user to abort a transit on a copy with status of LOST', 'ppl', 'description')),
310  (508, 'ABORT_TRANSIT_ON_MISSING', oils_i18n_gettext(508, 'Allows a user to abort a transit on a copy with status of MISSING', 'ppl', 'description'));
311
312 --- stock Circulation Administrator group
313
314 INSERT INTO permission.grp_perm_map ( grp, perm, depth, grantable )
315     SELECT
316         4,
317         id,
318         0,
319         't'
320     FROM permission.perm_list
321     WHERE code in ('ABORT_TRANSIT_ON_LOST', 'ABORT_TRANSIT_ON_MISSING');
322
323 -- Evergreen DB patch 0546.schema.sip_statcats.sql
324
325
326 -- check whether patch can be applied
327 SELECT evergreen.upgrade_deps_block_check('0546', :eg_version);
328
329 CREATE TABLE actor.stat_cat_sip_fields (
330     field   CHAR(2) PRIMARY KEY,
331     name    TEXT    NOT NULL,
332     one_only  BOOL    NOT NULL DEFAULT FALSE
333 );
334 COMMENT ON TABLE actor.stat_cat_sip_fields IS $$
335 Actor Statistical Category SIP Fields
336
337 Contains the list of valid SIP Field identifiers for
338 Statistical Categories.
339 $$;
340 ALTER TABLE actor.stat_cat
341     ADD COLUMN sip_field   CHAR(2) REFERENCES actor.stat_cat_sip_fields(field) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
342     ADD COLUMN sip_format  TEXT;
343
344 CREATE FUNCTION actor.stat_cat_check() RETURNS trigger AS $func$
345 DECLARE
346     sipfield actor.stat_cat_sip_fields%ROWTYPE;
347     use_count INT;
348 BEGIN
349     IF NEW.sip_field IS NOT NULL THEN
350         SELECT INTO sipfield * FROM actor.stat_cat_sip_fields WHERE field = NEW.sip_field;
351         IF sipfield.one_only THEN
352             SELECT INTO use_count count(id) FROM actor.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
353             IF use_count > 0 THEN
354                 RAISE EXCEPTION 'Sip field cannot be used twice';
355             END IF;
356         END IF;
357     END IF;
358     RETURN NEW;
359 END;
360 $func$ LANGUAGE PLPGSQL;
361
362 CREATE TRIGGER actor_stat_cat_sip_update_trigger
363     BEFORE INSERT OR UPDATE ON actor.stat_cat FOR EACH ROW
364     EXECUTE PROCEDURE actor.stat_cat_check();
365
366 CREATE TABLE asset.stat_cat_sip_fields (
367     field   CHAR(2) PRIMARY KEY,
368     name    TEXT    NOT NULL,
369     one_only BOOL    NOT NULL DEFAULT FALSE
370 );
371 COMMENT ON TABLE asset.stat_cat_sip_fields IS $$
372 Asset Statistical Category SIP Fields
373
374 Contains the list of valid SIP Field identifiers for
375 Statistical Categories.
376 $$;
377
378 ALTER TABLE asset.stat_cat
379     ADD COLUMN sip_field   CHAR(2) REFERENCES asset.stat_cat_sip_fields(field) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
380     ADD COLUMN sip_format  TEXT;
381
382 CREATE FUNCTION asset.stat_cat_check() RETURNS trigger AS $func$
383 DECLARE
384     sipfield asset.stat_cat_sip_fields%ROWTYPE;
385     use_count INT;
386 BEGIN
387     IF NEW.sip_field IS NOT NULL THEN
388         SELECT INTO sipfield * FROM asset.stat_cat_sip_fields WHERE field = NEW.sip_field;
389         IF sipfield.one_only THEN
390             SELECT INTO use_count count(id) FROM asset.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
391             IF use_count > 0 THEN
392                 RAISE EXCEPTION 'Sip field cannot be used twice';
393             END IF;
394         END IF;
395     END IF;
396     RETURN NEW;
397 END;
398 $func$ LANGUAGE PLPGSQL;
399
400 CREATE TRIGGER asset_stat_cat_sip_update_trigger
401     BEFORE INSERT OR UPDATE ON asset.stat_cat FOR EACH ROW
402     EXECUTE PROCEDURE asset.stat_cat_check();
403
404
405
406 SELECT evergreen.upgrade_deps_block_check('0548', :eg_version); -- dbwells
407
408 \qecho This redoes the original part 1 of 0547 which did not apply to rel_2_1,
409 \qecho and is being added for the sake of clarity
410
411 -- delete errant inserts from 0545 (group 4 is NOT the circulation admin group)
412 DELETE FROM permission.grp_perm_map WHERE grp = 4 AND perm IN (
413         SELECT id FROM permission.perm_list
414         WHERE code in ('ABORT_TRANSIT_ON_LOST', 'ABORT_TRANSIT_ON_MISSING')
415 );
416
417 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
418         SELECT
419                 pgt.id, perm.id, aout.depth, TRUE
420         FROM
421                 permission.grp_tree pgt,
422                 permission.perm_list perm,
423                 actor.org_unit_type aout
424         WHERE
425                 pgt.name = 'Circulation Administrator' AND
426                 aout.name = 'Consortium' AND
427                 perm.code IN (
428                         'ABORT_TRANSIT_ON_LOST',
429                         'ABORT_TRANSIT_ON_MISSING'
430                 ) AND NOT EXISTS (
431                         SELECT 1
432                         FROM permission.grp_perm_map AS map
433                         WHERE
434                                 map.grp = pgt.id
435                                 AND map.perm = perm.id
436                 );
437
438 -- Evergreen DB patch XXXX.data.transit-checkin-interval.sql
439 --
440 -- New org unit setting "circ.transit.min_checkin_interval"
441 -- New TRANSIT_CHECKIN_INTERVAL_BLOCK.override permission
442 --
443
444
445 -- check whether patch can be applied
446 SELECT evergreen.upgrade_deps_block_check('0549', :eg_version);
447
448 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
449     'circ.transit.min_checkin_interval',
450     oils_i18n_gettext( 
451         'circ.transit.min_checkin_interval', 
452         'Circ:  Minimum Transit Checkin Interval',
453         'coust',
454         'label'
455     ),
456     oils_i18n_gettext( 
457         'circ.transit.min_checkin_interval', 
458         'In-Transit items checked in this close to the transit start time will be prevented from checking in',
459         'coust',
460         'label'
461     ),
462     'interval'
463 );
464
465 INSERT INTO permission.perm_list ( id, code, description ) VALUES (  
466     509, 
467     'TRANSIT_CHECKIN_INTERVAL_BLOCK.override', 
468     oils_i18n_gettext(
469         509,
470         'Allows a user to override the TRANSIT_CHECKIN_INTERVAL_BLOCK event', 
471         'ppl', 
472         'description'
473     )
474 );
475
476 -- add the perm to the default circ admin group
477 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
478         SELECT
479                 pgt.id, perm.id, aout.depth, TRUE
480         FROM
481                 permission.grp_tree pgt,
482                 permission.perm_list perm,
483                 actor.org_unit_type aout
484         WHERE
485                 pgt.name = 'Circulation Administrator' AND
486                 aout.name = 'System' AND
487                 perm.code IN ( 'TRANSIT_CHECKIN_INTERVAL_BLOCK.override' );
488
489
490 -- check whether patch can be applied
491 SELECT evergreen.upgrade_deps_block_check('0550', :eg_version);
492
493 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
494     'org.patron_opt_boundary',
495     oils_i18n_gettext( 
496         'org.patron_opt_boundary',
497         'Circ: Patron Opt-In Boundary',
498         'coust',
499         'label'
500     ),
501     oils_i18n_gettext( 
502         'org.patron_opt_boundary',
503         'This determines at which depth above which patrons must be opted in, and below which patrons will be assumed to be opted in.',
504         'coust',
505         'label'
506     ),
507     'integer'
508 );
509
510 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
511     'org.patron_opt_default',
512     oils_i18n_gettext( 
513         'org.patron_opt_default',
514         'Circ: Patron Opt-In Default',
515         'coust',
516         'label'
517     ),
518     oils_i18n_gettext( 
519         'org.patron_opt_default',
520         'This is the default depth at which a patron is opted in; it is calculated as an org unit relative to the current workstation.',
521         'coust',
522         'label'
523     ),
524     'integer'
525 );
526
527 -- Evergreen DB patch 0562.schema.copy_active_date.sql
528 --
529 -- Active Date
530
531
532 -- check whether patch can be applied
533 SELECT evergreen.upgrade_deps_block_check('0562', :eg_version);
534
535 ALTER TABLE asset.copy
536     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
537
538 ALTER TABLE auditor.asset_copy_history
539     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
540
541 ALTER TABLE auditor.serial_unit_history
542     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
543
544 ALTER TABLE config.copy_status
545     ADD COLUMN copy_active BOOL NOT NULL DEFAULT FALSE;
546
547 ALTER TABLE config.circ_matrix_weights
548     ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
549
550 ALTER TABLE config.hold_matrix_weights
551     ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
552
553 -- The two defaults above were to stop erroring on NOT NULL
554 -- Remove them here
555 ALTER TABLE config.circ_matrix_weights
556     ALTER COLUMN item_age DROP DEFAULT;
557
558 ALTER TABLE config.hold_matrix_weights
559     ALTER COLUMN item_age DROP DEFAULT;
560
561 ALTER TABLE config.circ_matrix_matchpoint
562     ADD COLUMN item_age INTERVAL;
563
564 ALTER TABLE config.hold_matrix_matchpoint
565     ADD COLUMN item_age INTERVAL;
566
567 --Removed dupe asset.acp_status_changed
568
569 CREATE OR REPLACE FUNCTION asset.acp_created()
570 RETURNS TRIGGER AS $$
571 BEGIN
572     IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
573         NEW.active_date := now();
574     END IF;
575     IF NEW.status_changed_time IS NULL THEN
576         NEW.status_changed_time := now();
577     END IF;
578     RETURN NEW;
579 END;
580 $$ LANGUAGE plpgsql;
581
582 CREATE TRIGGER acp_created_trig
583     BEFORE INSERT ON asset.copy
584     FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
585
586 CREATE TRIGGER sunit_created_trig
587     BEFORE INSERT ON serial.unit
588     FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
589
590 --Removed dupe action.hold_request_permit_test
591
592 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$
593 DECLARE
594     cn_object       asset.call_number%ROWTYPE;
595     rec_descriptor  metabib.rec_descriptor%ROWTYPE;
596     cur_matchpoint  config.circ_matrix_matchpoint%ROWTYPE;
597     matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
598     weights         config.circ_matrix_weights%ROWTYPE;
599     user_age        INTERVAL;
600     my_item_age     INTERVAL;
601     denominator     NUMERIC(6,2);
602     row_list        INT[];
603     result          action.found_circ_matrix_matchpoint;
604 BEGIN
605     -- Assume failure
606     result.success = false;
607
608     -- Fetch useful data
609     SELECT INTO cn_object       * FROM asset.call_number        WHERE id = item_object.call_number;
610     SELECT INTO rec_descriptor  * FROM metabib.rec_descriptor   WHERE record = cn_object.record;
611
612     -- Pre-generate this so we only calc it once
613     IF user_object.dob IS NOT NULL THEN
614         SELECT INTO user_age age(user_object.dob);
615     END IF;
616
617     -- Ditto
618     SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
619
620     -- Grab the closest set circ weight setting.
621     SELECT INTO weights cw.*
622       FROM config.weight_assoc wa
623            JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
624            JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
625       WHERE active
626       ORDER BY d.distance
627       LIMIT 1;
628
629     -- No weights? Bad admin! Defaults to handle that anyway.
630     IF weights.id IS NULL THEN
631         weights.grp                 := 11.0;
632         weights.org_unit            := 10.0;
633         weights.circ_modifier       := 5.0;
634         weights.marc_type           := 4.0;
635         weights.marc_form           := 3.0;
636         weights.marc_bib_level      := 2.0;
637         weights.marc_vr_format      := 2.0;
638         weights.copy_circ_lib       := 8.0;
639         weights.copy_owning_lib     := 8.0;
640         weights.user_home_ou        := 8.0;
641         weights.ref_flag            := 1.0;
642         weights.juvenile_flag       := 6.0;
643         weights.is_renewal          := 7.0;
644         weights.usr_age_lower_bound := 0.0;
645         weights.usr_age_upper_bound := 0.0;
646         weights.item_age            := 0.0;
647     END IF;
648
649     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
650     -- If you break your org tree with funky parenting this may be wrong
651     -- 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
652     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
653     WITH all_distance(distance) AS (
654             SELECT depth AS distance FROM actor.org_unit_type
655         UNION
656             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
657         )
658     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
659
660     -- Loop over all the potential matchpoints
661     FOR cur_matchpoint IN
662         SELECT m.*
663           FROM  config.circ_matrix_matchpoint m
664                 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
665                 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
666                 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
667                 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
668                 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
669           WHERE m.active
670                 -- Permission Groups
671              -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
672                 -- Org Units
673              -- AND (m.org_unit                 IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
674                 AND (m.copy_owning_lib          IS NULL OR cnoua.id IS NOT NULL)
675                 AND (m.copy_circ_lib            IS NULL OR iooua.id IS NOT NULL)
676                 AND (m.user_home_ou             IS NULL OR uhoua.id IS NOT NULL)
677                 -- Circ Type
678                 AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
679                 -- Static User Checks
680                 AND (m.juvenile_flag            IS NULL OR m.juvenile_flag = user_object.juvenile)
681                 AND (m.usr_age_lower_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
682                 AND (m.usr_age_upper_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
683                 -- Static Item Checks
684                 AND (m.circ_modifier            IS NULL OR m.circ_modifier = item_object.circ_modifier)
685                 AND (m.marc_type                IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
686                 AND (m.marc_form                IS NULL OR m.marc_form = rec_descriptor.item_form)
687                 AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
688                 AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
689                 AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
690                 AND (m.item_age                 IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
691           ORDER BY
692                 -- Permission Groups
693                 CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
694                 -- Org Units
695                 CASE WHEN ctoua.distance        IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
696                 CASE WHEN cnoua.distance        IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
697                 CASE WHEN iooua.distance        IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
698                 CASE WHEN uhoua.distance        IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
699                 -- Circ Type                    -- Note: 4^x is equiv to 2^(2*x)
700                 CASE WHEN m.is_renewal          IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
701                 -- Static User Checks
702                 CASE WHEN m.juvenile_flag       IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
703                 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
704                 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
705                 -- Static Item Checks
706                 CASE WHEN m.circ_modifier       IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
707                 CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
708                 CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
709                 CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
710                 CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
711                 -- Item age has a slight adjustment to weight based on value.
712                 -- This should ensure that a shorter age limit comes first when all else is equal.
713                 -- NOTE: This assumes that intervals will normally be in days.
714                 CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 1 + 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
715                 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
716                 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
717                 m.id LOOP
718
719         -- Record the full matching row list
720         row_list := row_list || cur_matchpoint.id;
721
722         -- No matchpoint yet?
723         IF matchpoint.id IS NULL THEN
724             -- Take the entire matchpoint as a starting point
725             matchpoint := cur_matchpoint;
726             CONTINUE; -- No need to look at this row any more.
727         END IF;
728
729         -- Incomplete matchpoint?
730         IF matchpoint.circulate IS NULL THEN
731             matchpoint.circulate := cur_matchpoint.circulate;
732         END IF;
733         IF matchpoint.duration_rule IS NULL THEN
734             matchpoint.duration_rule := cur_matchpoint.duration_rule;
735         END IF;
736         IF matchpoint.recurring_fine_rule IS NULL THEN
737             matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
738         END IF;
739         IF matchpoint.max_fine_rule IS NULL THEN
740             matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
741         END IF;
742         IF matchpoint.hard_due_date IS NULL THEN
743             matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
744         END IF;
745         IF matchpoint.total_copy_hold_ratio IS NULL THEN
746             matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
747         END IF;
748         IF matchpoint.available_copy_hold_ratio IS NULL THEN
749             matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
750         END IF;
751         IF matchpoint.renewals IS NULL THEN
752             matchpoint.renewals := cur_matchpoint.renewals;
753         END IF;
754         IF matchpoint.grace_period IS NULL THEN
755             matchpoint.grace_period := cur_matchpoint.grace_period;
756         END IF;
757     END LOOP;
758
759     -- Check required fields
760     IF matchpoint.circulate             IS NOT NULL AND
761        matchpoint.duration_rule         IS NOT NULL AND
762        matchpoint.recurring_fine_rule   IS NOT NULL AND
763        matchpoint.max_fine_rule         IS NOT NULL THEN
764         -- All there? We have a completed match.
765         result.success := true;
766     END IF;
767
768     -- Include the assembled matchpoint, even if it isn't complete
769     result.matchpoint := matchpoint;
770
771     -- Include (for debugging) the full list of matching rows
772     result.buildrows := row_list;
773
774     -- Hand the result back to caller
775     RETURN result;
776 END;
777 $func$ LANGUAGE plpgsql;
778
779 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
780   RETURNS integer AS
781 $func$
782 DECLARE
783     requestor_object    actor.usr%ROWTYPE;
784     user_object         actor.usr%ROWTYPE;
785     item_object         asset.copy%ROWTYPE;
786     item_cn_object      asset.call_number%ROWTYPE;
787     my_item_age         INTERVAL;
788     rec_descriptor      metabib.rec_descriptor%ROWTYPE;
789     matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
790     weights             config.hold_matrix_weights%ROWTYPE;
791     denominator         NUMERIC(6,2);
792 BEGIN
793     SELECT INTO user_object         * FROM actor.usr                WHERE id = match_user;
794     SELECT INTO requestor_object    * FROM actor.usr                WHERE id = match_requestor;
795     SELECT INTO item_object         * FROM asset.copy               WHERE id = match_item;
796     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
797     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
798
799     SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
800
801     -- The item's owner should probably be the one determining if the item is holdable
802     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
803     -- This flag will allow for setting it to the owning library (where the call number "lives")
804     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
805
806     -- Grab the closest set circ weight setting.
807     IF NOT FOUND THEN
808         -- Default to circ library
809         SELECT INTO weights hw.*
810           FROM config.weight_assoc wa
811                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
812                JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
813           WHERE active
814           ORDER BY d.distance
815           LIMIT 1;
816     ELSE
817         -- Flag is set, use owning library
818         SELECT INTO weights hw.*
819           FROM config.weight_assoc wa
820                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
821                JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) d ON (wa.org_unit = d.id)
822           WHERE active
823           ORDER BY d.distance
824           LIMIT 1;
825     END IF;
826
827     -- No weights? Bad admin! Defaults to handle that anyway.
828     IF weights.id IS NULL THEN
829         weights.user_home_ou    := 5.0;
830         weights.request_ou      := 5.0;
831         weights.pickup_ou       := 5.0;
832         weights.item_owning_ou  := 5.0;
833         weights.item_circ_ou    := 5.0;
834         weights.usr_grp         := 7.0;
835         weights.requestor_grp   := 8.0;
836         weights.circ_modifier   := 4.0;
837         weights.marc_type       := 3.0;
838         weights.marc_form       := 2.0;
839         weights.marc_bib_level  := 1.0;
840         weights.marc_vr_format  := 1.0;
841         weights.juvenile_flag   := 4.0;
842         weights.ref_flag        := 0.0;
843         weights.item_age        := 0.0;
844     END IF;
845
846     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
847     -- If you break your org tree with funky parenting this may be wrong
848     -- 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
849     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
850     WITH all_distance(distance) AS (
851             SELECT depth AS distance FROM actor.org_unit_type
852         UNION
853             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
854         )
855     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
856
857     -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
858     -- This may be better implemented as part of the upgrade script?
859     -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
860     -- Then remove this flag, of course.
861     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
862
863     IF FOUND THEN
864         -- Note: This, to me, is REALLY hacky. I put it in anyway.
865         -- If you can't tell, this is a single call swap on two variables.
866         SELECT INTO user_object.profile, requestor_object.profile
867                     requestor_object.profile, user_object.profile;
868     END IF;
869
870     -- Select the winning matchpoint into the matchpoint variable for returning
871     SELECT INTO matchpoint m.*
872       FROM  config.hold_matrix_matchpoint m
873             /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
874             LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
875             LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
876             LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
877             LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
878             LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
879             LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
880       WHERE m.active
881             -- Permission Groups
882          -- AND (m.requestor_grp        IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
883             AND (m.usr_grp              IS NULL OR upgad.id IS NOT NULL)
884             -- Org Units
885             AND (m.pickup_ou            IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
886             AND (m.request_ou           IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
887             AND (m.item_owning_ou       IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
888             AND (m.item_circ_ou         IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
889             AND (m.user_home_ou         IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
890             -- Static User Checks
891             AND (m.juvenile_flag        IS NULL OR m.juvenile_flag = user_object.juvenile)
892             -- Static Item Checks
893             AND (m.circ_modifier        IS NULL OR m.circ_modifier = item_object.circ_modifier)
894             AND (m.marc_type            IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
895             AND (m.marc_form            IS NULL OR m.marc_form = rec_descriptor.item_form)
896             AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
897             AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
898             AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
899             AND (m.item_age             IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
900       ORDER BY
901             -- Permission Groups
902             CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
903             CASE WHEN upgad.distance    IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
904             -- Org Units
905             CASE WHEN puoua.distance    IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
906             CASE WHEN rqoua.distance    IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
907             CASE WHEN cnoua.distance    IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
908             CASE WHEN iooua.distance    IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
909             CASE WHEN uhoua.distance    IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
910             -- Static User Checks       -- Note: 4^x is equiv to 2^(2*x)
911             CASE WHEN m.juvenile_flag   IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
912             -- Static Item Checks
913             CASE WHEN m.circ_modifier   IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
914             CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
915             CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
916             CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
917             CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
918             -- Item age has a slight adjustment to weight based on value.
919             -- This should ensure that a shorter age limit comes first when all else is equal.
920             -- NOTE: This assumes that intervals will normally be in days.
921             CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
922             -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
923             -- This prevents "we changed the table order by updating a rule, and we started getting different results"
924             m.id;
925
926     -- Return just the ID for now
927     RETURN matchpoint.id;
928 END;
929 $func$ LANGUAGE 'plpgsql';
930
931 DROP INDEX IF EXISTS config.ccmm_once_per_paramset;
932
933 DROP INDEX IF EXISTS config.chmm_once_per_paramset;
934
935 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_bib_level,''), 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, ''), COALESCE(item_age::TEXT, '')) WHERE active;
936
937 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_bib_level, ''), COALESCE(marc_vr_format, ''), COALESCE(juvenile_flag::TEXT, ''), COALESCE(ref_flag::TEXT, ''), COALESCE(item_age::TEXT, '')) WHERE active;
938
939 UPDATE config.copy_status SET copy_active = true WHERE id IN (0, 1, 7, 8, 10, 12, 15);
940
941 INSERT into config.org_unit_setting_type
942 ( name, label, description, datatype ) VALUES
943 ( 'circ.holds.age_protect.active_date', 'Holds: Use Active Date for Age Protection', 'When calculating age protection rules use the active date instead of the creation date.', 'bool');
944
945 -- Assume create date when item is in status we would update active date for anyway
946 UPDATE asset.copy SET active_date = create_date WHERE status IN (SELECT id FROM config.copy_status WHERE copy_active = true);
947
948 -- Assume create date for any item with circs
949 UPDATE asset.copy SET active_date = create_date WHERE id IN (SELECT id FROM extend_reporter.full_circ_count WHERE circ_count > 0);
950
951 -- Assume create date for status change time while we are at it. Because being created WAS a change in status.
952 UPDATE asset.copy SET status_changed_time = create_date WHERE status_changed_time IS NULL;
953
954 -- Evergreen DB patch 0564.data.delete_empty_volume.sql
955 --
956 -- New org setting cat.volume.delete_on_empty
957 --
958
959 -- check whether patch can be applied
960 SELECT evergreen.upgrade_deps_block_check('0564', :eg_version);
961
962 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
963     VALUES ( 
964         'cat.volume.delete_on_empty',
965         oils_i18n_gettext('cat.volume.delete_on_empty', 'Cat: Delete volume with last copy', 'coust', 'label'),
966         oils_i18n_gettext('cat.volume.delete_on_empty', 'Automatically delete a volume when the last linked copy is deleted', 'coust', 'description'),
967         'bool'
968     );
969
970
971 -- Evergreen DB patch 0565.schema.action-trigger.event_definition.hold-cancel-no-target-notification.sql
972 --
973 -- New action trigger event definition: Hold Cancelled (No Target) Email Notification
974 --
975
976 -- check whether patch can be applied
977 SELECT evergreen.upgrade_deps_block_check('0565', :eg_version);
978
979 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template)
980     VALUES (38, FALSE, 1, 
981         'Hold Cancelled (No Target) Email Notification', 
982         'hold_request.cancel.expire_no_target', 
983         'HoldIsCancelled', 'SendEmail', '30 minutes', 'cancel_time', 'usr',
984 $$
985 [%- USE date -%]
986 [%- user = target.0.usr -%]
987 To: [%- params.recipient_email || user.email %]
988 From: [%- params.sender_email || default_sender %]
989 Subject: Hold Request Cancelled
990
991 Dear [% user.family_name %], [% user.first_given_name %]
992 The following holds were cancelled because no items were found to fullfil the hold.
993
994 [% FOR hold IN target %]
995     Title: [% hold.bib_rec.bib_record.simple_record.title %]
996     Author: [% hold.bib_rec.bib_record.simple_record.author %]
997     Library: [% hold.pickup_lib.name %]
998     Request Date: [% date.format(helpers.format_date(hold.rrequest_time), '%Y-%m-%d') %]
999 [% END %]
1000
1001 $$);
1002
1003 INSERT INTO action_trigger.environment (event_def, path) VALUES
1004     (38, 'usr'),
1005     (38, 'pickup_lib'),
1006     (38, 'bib_rec.bib_record.simple_record');
1007
1008 -- Evergreen DB patch XXXX.data.ou_setting_generate_overdue_on_lost.sql.sql
1009
1010 -- check whether patch can be applied
1011 SELECT evergreen.upgrade_deps_block_check('0567', :eg_version);
1012
1013 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1014     'circ.lost.generate_overdue_on_checkin',
1015     oils_i18n_gettext( 
1016         'circ.lost.generate_overdue_on_checkin',
1017         'Circ:  Lost Checkin Generates New Overdues',
1018         'coust',
1019         'label'
1020     ),
1021     oils_i18n_gettext( 
1022         'circ.lost.generate_overdue_on_checkin',
1023         'Enabling this setting causes retroactive creation of not-yet-existing overdue fines on lost item checkin, up to the point of checkin time (or max fines is reached).  This is different than "restore overdue on lost", because it only creates new overdue fines.  Use both settings together to get the full complement of overdue fines for a lost item',
1024         'coust',
1025         'label'
1026     ),
1027     'bool'
1028 );
1029
1030 -- Evergreen DB patch 0572.vandelay-record-matching-and-quality.sql
1031 --
1032
1033
1034 -- check whether patch can be applied
1035 SELECT evergreen.upgrade_deps_block_check('0572', :eg_version);
1036
1037 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;
1038
1039 CREATE TABLE vandelay.match_set (
1040     id      SERIAL  PRIMARY KEY,
1041     name    TEXT        NOT NULL,
1042     owner   INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE,
1043     mtype   TEXT        NOT NULL DEFAULT 'biblio', -- 'biblio','authority','mfhd'?, others?
1044     CONSTRAINT name_once_per_owner_mtype UNIQUE (name, owner, mtype)
1045 );
1046
1047 -- Table to define match points, either FF via SVF or tag+subfield
1048 CREATE TABLE vandelay.match_set_point (
1049     id          SERIAL  PRIMARY KEY,
1050     match_set   INT     REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
1051     parent      INT     REFERENCES vandelay.match_set_point (id),
1052     bool_op     TEXT    CHECK (bool_op IS NULL OR (bool_op IN ('AND','OR','NOT'))),
1053     svf         TEXT    REFERENCES config.record_attr_definition (name),
1054     tag         TEXT,
1055     subfield    TEXT,
1056     negate      BOOL    DEFAULT FALSE,
1057     quality     INT     NOT NULL DEFAULT 1, -- higher is better
1058     CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
1059     CONSTRAINT vmsp_need_a_tag_or_a_ff_or_a_bo CHECK (
1060         (tag IS NOT NULL AND svf IS NULL AND bool_op IS NULL) OR
1061         (tag IS NULL AND svf IS NOT NULL AND bool_op IS NULL) OR
1062         (tag IS NULL AND svf IS NULL AND bool_op IS NOT NULL)
1063     )
1064 );
1065
1066 CREATE TABLE vandelay.match_set_quality (
1067     id          SERIAL  PRIMARY KEY,
1068     match_set   INT     NOT NULL REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
1069     svf         TEXT    REFERENCES config.record_attr_definition,
1070     tag         TEXT,
1071     subfield    TEXT,
1072     value       TEXT    NOT NULL,
1073     quality     INT     NOT NULL DEFAULT 1, -- higher is better
1074     CONSTRAINT vmsq_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
1075     CONSTRAINT vmsq_need_a_tag_or_a_ff CHECK ((tag IS NOT NULL AND svf IS NULL) OR (tag IS NULL AND svf IS NOT NULL))
1076 );
1077 CREATE UNIQUE INDEX vmsq_def_once_per_set ON vandelay.match_set_quality (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(svf,''), value);
1078
1079
1080 -- ALTER TABLEs...
1081 ALTER TABLE vandelay.queue ADD COLUMN match_set INT REFERENCES vandelay.match_set (id) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
1082 ALTER TABLE vandelay.queued_record ADD COLUMN quality INT NOT NULL DEFAULT 0;
1083 ALTER TABLE vandelay.bib_attr_definition DROP COLUMN ident;
1084
1085 CREATE TABLE vandelay.import_error (
1086     code        TEXT    PRIMARY KEY,
1087     description TEXT    NOT NULL -- i18n
1088 );
1089
1090 ALTER TABLE vandelay.queued_bib_record
1091     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1092     ADD COLUMN error_detail TEXT;
1093
1094 ALTER TABLE vandelay.bib_match
1095     DROP COLUMN field_type,
1096     DROP COLUMN matched_attr,
1097     ADD COLUMN quality INT NOT NULL DEFAULT 1,
1098     ADD COLUMN match_score INT NOT NULL DEFAULT 0;
1099
1100 ALTER TABLE vandelay.import_item
1101     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1102     ADD COLUMN error_detail TEXT,
1103     ADD COLUMN imported_as BIGINT REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED,
1104     ADD COLUMN import_time TIMESTAMP WITH TIME ZONE;
1105
1106 ALTER TABLE vandelay.merge_profile ADD COLUMN lwm_ratio NUMERIC;
1107
1108 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
1109 DECLARE
1110     ldr         TEXT;
1111     tval        TEXT;
1112     tval_rec    RECORD;
1113     bval        TEXT;
1114     bval_rec    RECORD;
1115     retval      config.marc21_rec_type_map%ROWTYPE;
1116 BEGIN
1117     ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
1118
1119     IF ldr IS NULL OR ldr = '' THEN
1120         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1121         RETURN retval;
1122     END IF;
1123
1124     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
1125     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
1126
1127
1128     tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
1129     bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
1130
1131     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
1132
1133     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
1134
1135
1136     IF retval.code IS NULL THEN
1137         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1138     END IF;
1139
1140     RETURN retval;
1141 END;
1142 $func$ LANGUAGE PLPGSQL;
1143
1144 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
1145 DECLARE
1146     rtype       TEXT;
1147     ff_pos      RECORD;
1148     tag_data    RECORD;
1149     val         TEXT;
1150 BEGIN
1151     rtype := (vandelay.marc21_record_type( marc )).code;
1152     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
1153         IF ff_pos.tag = 'ldr' THEN
1154             val := oils_xpath_string('//*[local-name()="leader"]', marc);
1155             IF val IS NOT NULL THEN
1156                 val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
1157                 RETURN val;
1158             END IF;
1159         ELSE
1160             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1161                 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1162                 RETURN val;
1163             END LOOP;
1164         END IF;
1165         val := REPEAT( ff_pos.default_val, ff_pos.length );
1166         RETURN val;
1167     END LOOP;
1168
1169     RETURN NULL;
1170 END;
1171 $func$ LANGUAGE PLPGSQL;
1172
1173 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
1174 DECLARE
1175     tag_data    TEXT;
1176     rtype       TEXT;
1177     ff_pos      RECORD;
1178     output      biblio.record_ff_map%ROWTYPE;
1179 BEGIN
1180     rtype := (vandelay.marc21_record_type( marc )).code;
1181
1182     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
1183         output.ff_name  := ff_pos.fixed_field;
1184         output.ff_value := NULL;
1185
1186         IF ff_pos.tag = 'ldr' THEN
1187             output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
1188             IF output.ff_value IS NOT NULL THEN
1189                 output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
1190                 RETURN NEXT output;
1191                 output.ff_value := NULL;
1192             END IF;
1193         ELSE
1194             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1195                 output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
1196                 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
1197                 RETURN NEXT output;
1198                 output.ff_value := NULL;
1199             END LOOP;
1200         END IF;
1201     
1202     END LOOP;
1203
1204     RETURN;
1205 END;
1206 $func$ LANGUAGE PLPGSQL;
1207
1208 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1209 DECLARE
1210     rowid   INT := 0;
1211     _007    TEXT;
1212     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
1213     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
1214     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
1215     retval  biblio.marc21_physical_characteristics%ROWTYPE;
1216 BEGIN
1217
1218     _007 := oils_xpath_string( '//*[@tag="007"]', marc );
1219
1220     IF _007 IS NOT NULL AND _007 <> '' THEN
1221         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
1222
1223         IF ptype.ptype_key IS NOT NULL THEN
1224             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
1225                 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 );
1226
1227                 IF pval.id IS NOT NULL THEN
1228                     rowid := rowid + 1;
1229                     retval.id := rowid;
1230                     retval.ptype := ptype.ptype_key;
1231                     retval.subfield := psf.id;
1232                     retval.value := pval.id;
1233                     RETURN NEXT retval;
1234                 END IF;
1235
1236             END LOOP;
1237         END IF;
1238     END IF;
1239
1240     RETURN;
1241 END;
1242 $func$ LANGUAGE PLPGSQL;
1243
1244 CREATE TYPE vandelay.flat_marc AS ( tag CHAR(3), ind1 TEXT, ind2 TEXT, subfield TEXT, value TEXT );
1245 CREATE OR REPLACE FUNCTION vandelay.flay_marc ( TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
1246
1247 use MARC::Record;
1248 use MARC::File::XML (BinaryEncoding => 'UTF-8');
1249 use MARC::Charset;
1250 use strict;
1251
1252 MARC::Charset->assume_unicode(1);
1253
1254 my $xml = shift;
1255 my $r = MARC::Record->new_from_xml( $xml );
1256
1257 return_next( { tag => 'LDR', value => $r->leader } );
1258
1259 for my $f ( $r->fields ) {
1260     if ($f->is_control_field) {
1261         return_next({ tag => $f->tag, value => $f->data });
1262     } else {
1263         for my $s ($f->subfields) {
1264             return_next({
1265                 tag      => $f->tag,
1266                 ind1     => $f->indicator(1),
1267                 ind2     => $f->indicator(2),
1268                 subfield => $s->[0],
1269                 value    => $s->[1]
1270             });
1271
1272             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
1273                 my $trim = $f->indicator(2) || 0;
1274                 return_next({
1275                     tag      => 'tnf',
1276                     ind1     => $f->indicator(1),
1277                     ind2     => $f->indicator(2),
1278                     subfield => 'a',
1279                     value    => substr( $s->[1], $trim )
1280                 });
1281             }
1282         }
1283     }
1284 }
1285
1286 return undef;
1287
1288 $func$ LANGUAGE PLPERLU;
1289
1290 CREATE OR REPLACE FUNCTION vandelay.flatten_marc ( marc TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
1291 DECLARE
1292     output  vandelay.flat_marc%ROWTYPE;
1293     field   RECORD;
1294 BEGIN
1295     FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
1296         output.ind1 := field.ind1;
1297         output.ind2 := field.ind2;
1298         output.tag := field.tag;
1299         output.subfield := field.subfield;
1300         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
1301             output.value := naco_normalize(field.value, field.subfield);
1302         ELSE
1303             output.value := field.value;
1304         END IF;
1305
1306         CONTINUE WHEN output.value IS NULL;
1307
1308         RETURN NEXT output;
1309     END LOOP;
1310 END;
1311 $func$ LANGUAGE PLPGSQL;
1312
1313 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT, attr_defs TEXT[]) RETURNS hstore AS $_$
1314 DECLARE
1315     transformed_xml TEXT;
1316     prev_xfrm       TEXT;
1317     normalizer      RECORD;
1318     xfrm            config.xml_transform%ROWTYPE;
1319     attr_value      TEXT;
1320     new_attrs       HSTORE := ''::HSTORE;
1321     attr_def        config.record_attr_definition%ROWTYPE;
1322 BEGIN
1323
1324     FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE name IN (SELECT * FROM UNNEST(attr_defs)) ORDER BY format LOOP
1325
1326         IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
1327             SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(x.value), COALESCE(attr_def.joiner,' ')) INTO attr_value
1328               FROM  vandelay.flatten_marc(xml) AS x
1329               WHERE x.tag LIKE attr_def.tag
1330                     AND CASE
1331                         WHEN attr_def.sf_list IS NOT NULL
1332                             THEN POSITION(x.subfield IN attr_def.sf_list) > 0
1333                         ELSE TRUE
1334                         END
1335               GROUP BY x.tag
1336               ORDER BY x.tag
1337               LIMIT 1;
1338
1339         ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
1340             attr_value := vandelay.marc21_extract_fixed_field(xml, attr_def.fixed_field);
1341
1342         ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
1343
1344             SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
1345
1346             -- See if we can skip the XSLT ... it's expensive
1347             IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1348                 -- Can't skip the transform
1349                 IF xfrm.xslt <> '---' THEN
1350                     transformed_xml := oils_xslt_process(xml,xfrm.xslt);
1351                 ELSE
1352                     transformed_xml := xml;
1353                 END IF;
1354
1355                 prev_xfrm := xfrm.name;
1356             END IF;
1357
1358             IF xfrm.name IS NULL THEN
1359                 -- just grab the marcxml (empty) transform
1360                 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
1361                 prev_xfrm := xfrm.name;
1362             END IF;
1363
1364             attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
1365
1366         ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
1367             SELECT  m.value::TEXT INTO attr_value
1368               FROM  vandelay.marc21_physical_characteristics(xml) v
1369                     JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
1370               WHERE v.subfield = attr_def.phys_char_sf
1371               LIMIT 1; -- Just in case ...
1372
1373         END IF;
1374
1375         -- apply index normalizers to attr_value
1376         FOR normalizer IN
1377             SELECT  n.func AS func,
1378                     n.param_count AS param_count,
1379                     m.params AS params
1380               FROM  config.index_normalizer n
1381                     JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
1382               WHERE attr = attr_def.name
1383               ORDER BY m.pos LOOP
1384                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
1385                     quote_literal( attr_value ) ||
1386                     CASE
1387                         WHEN normalizer.param_count > 0
1388                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1389                             ELSE ''
1390                         END ||
1391                     ')' INTO attr_value;
1392
1393         END LOOP;
1394
1395         -- Add the new value to the hstore
1396         new_attrs := new_attrs || hstore( attr_def.name, attr_value );
1397
1398     END LOOP;
1399
1400     RETURN new_attrs;
1401 END;
1402 $_$ LANGUAGE PLPGSQL;
1403
1404 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT ) RETURNS hstore AS $_$
1405     SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_ACCUM(name) FROM config.record_attr_definition));
1406 $_$ LANGUAGE SQL;
1407
1408 -- Everything between this comment and the beginning of the definition of
1409 -- vandelay.match_bib_record() is strictly in service of that function.
1410 CREATE TYPE vandelay.match_set_test_result AS (record BIGINT, quality INTEGER);
1411
1412 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
1413     match_set_id INTEGER, record_xml TEXT
1414 ) RETURNS SETOF vandelay.match_set_test_result AS $$
1415 DECLARE
1416     tags_rstore HSTORE;
1417     svf_rstore  HSTORE;
1418     coal        TEXT;
1419     joins       TEXT;
1420     query_      TEXT;
1421     wq          TEXT;
1422     qvalue      INTEGER;
1423     rec         RECORD;
1424 BEGIN
1425     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
1426     svf_rstore := vandelay.extract_rec_attrs(record_xml);
1427
1428     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
1429     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
1430
1431     -- generate the where clause and return that directly (into wq), and as
1432     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
1433     wq := vandelay.get_expr_from_match_set(match_set_id);
1434
1435     query_ := 'SELECT bre.id AS record, ';
1436
1437     -- qrows table is for the quality bits we add to the SELECT clause
1438     SELECT ARRAY_TO_STRING(
1439         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
1440     ) INTO coal FROM _vandelay_tmp_qrows;
1441
1442     -- our query string so far is the SELECT clause and the inital FROM.
1443     -- no JOINs yet nor the WHERE clause
1444     query_ := query_ || coal || ' AS quality ' || E'\n' ||
1445         'FROM biblio.record_entry bre ';
1446
1447     -- jrows table is for the joins we must make (and the real text conditions)
1448     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
1449         FROM _vandelay_tmp_jrows;
1450
1451     -- add those joins and the where clause to our query.
1452     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
1453
1454     -- this will return rows of record,quality
1455     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
1456         RETURN NEXT rec;
1457     END LOOP;
1458
1459     DROP TABLE _vandelay_tmp_qrows;
1460     DROP TABLE _vandelay_tmp_jrows;
1461     RETURN;
1462 END;
1463
1464 $$ LANGUAGE PLPGSQL;
1465
1466 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
1467     record_xml TEXT
1468 ) RETURNS HSTORE AS $$
1469 BEGIN
1470     RETURN (SELECT
1471         HSTORE(
1472             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
1473             ARRAY_ACCUM(value)
1474         )
1475         FROM (
1476             SELECT tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
1477                 FROM vandelay.flatten_marc(record_xml)
1478                 GROUP BY tag, subfield ORDER BY tag, subfield
1479         ) subquery
1480     );
1481 END;
1482 $$ LANGUAGE PLPGSQL;
1483
1484 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set(
1485     match_set_id INTEGER
1486 ) RETURNS TEXT AS $$
1487 DECLARE
1488     root    vandelay.match_set_point;
1489 BEGIN
1490     SELECT * INTO root FROM vandelay.match_set_point
1491         WHERE parent IS NULL AND match_set = match_set_id;
1492
1493     RETURN vandelay.get_expr_from_match_set_point(root);
1494 END;
1495 $$  LANGUAGE PLPGSQL;
1496
1497 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set_point(
1498     node vandelay.match_set_point
1499 ) RETURNS TEXT AS $$
1500 DECLARE
1501     q           TEXT;
1502     i           INTEGER;
1503     this_op     TEXT;
1504     children    INTEGER[];
1505     child       vandelay.match_set_point;
1506 BEGIN
1507     SELECT ARRAY_ACCUM(id) INTO children FROM vandelay.match_set_point
1508         WHERE parent = node.id;
1509
1510     IF ARRAY_LENGTH(children, 1) > 0 THEN
1511         this_op := vandelay._get_expr_render_one(node);
1512         q := '(';
1513         i := 1;
1514         WHILE children[i] IS NOT NULL LOOP
1515             SELECT * INTO child FROM vandelay.match_set_point
1516                 WHERE id = children[i];
1517             IF i > 1 THEN
1518                 q := q || ' ' || this_op || ' ';
1519             END IF;
1520             i := i + 1;
1521             q := q || vandelay.get_expr_from_match_set_point(child);
1522         END LOOP;
1523         q := q || ')';
1524         RETURN q;
1525     ELSIF node.bool_op IS NULL THEN
1526         PERFORM vandelay._get_expr_push_qrow(node);
1527         PERFORM vandelay._get_expr_push_jrow(node);
1528         RETURN vandelay._get_expr_render_one(node);
1529     ELSE
1530         RETURN '';
1531     END IF;
1532 END;
1533 $$  LANGUAGE PLPGSQL;
1534
1535 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_qrow(
1536     node vandelay.match_set_point
1537 ) RETURNS VOID AS $$
1538 DECLARE
1539 BEGIN
1540     INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
1541 END;
1542 $$ LANGUAGE PLPGSQL;
1543
1544 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
1545     node vandelay.match_set_point
1546 ) RETURNS VOID AS $$
1547 DECLARE
1548     jrow        TEXT;
1549     my_alias    TEXT;
1550     op          TEXT;
1551     tagkey      TEXT;
1552 BEGIN
1553     IF node.negate THEN
1554         op := '<>';
1555     ELSE
1556         op := '=';
1557     END IF;
1558
1559     IF node.tag IS NOT NULL THEN
1560         tagkey := node.tag;
1561         IF node.subfield IS NOT NULL THEN
1562             tagkey := tagkey || node.subfield;
1563         END IF;
1564     END IF;
1565
1566     my_alias := 'n' || node.id::TEXT;
1567
1568     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
1569         ' AS quality FROM metabib.';
1570     IF node.tag IS NOT NULL THEN
1571         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
1572             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
1573             node.tag || '''';
1574         IF node.subfield IS NOT NULL THEN
1575             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
1576                 node.subfield || '''';
1577         END IF;
1578         jrow := jrow || ' AND (' || my_alias || '.value ' || op ||
1579             ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
1580     ELSE    -- svf
1581         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
1582             my_alias || '.id = bre.id AND (' ||
1583             my_alias || '.attrs->''' || node.svf ||
1584             ''' ' || op || ' $2->''' || node.svf || '''))';
1585     END IF;
1586     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
1587 END;
1588 $$ LANGUAGE PLPGSQL;
1589
1590 CREATE OR REPLACE FUNCTION vandelay._get_expr_render_one(
1591     node vandelay.match_set_point
1592 ) RETURNS TEXT AS $$
1593 DECLARE
1594     s           TEXT;
1595 BEGIN
1596     IF node.bool_op IS NOT NULL THEN
1597         RETURN node.bool_op;
1598     ELSE
1599         RETURN '(n' || node.id::TEXT || '.id IS NOT NULL)';
1600     END IF;
1601 END;
1602 $$ LANGUAGE PLPGSQL;
1603
1604 CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
1605 DECLARE
1606     incoming_existing_id    TEXT;
1607     test_result             vandelay.match_set_test_result%ROWTYPE;
1608     tmp_rec                 BIGINT;
1609     match_set               INT;
1610 BEGIN
1611     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1612         RETURN NEW;
1613     END IF;
1614
1615     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1616
1617     SELECT q.match_set INTO match_set FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
1618
1619     IF match_set IS NOT NULL THEN
1620         NEW.quality := vandelay.measure_record_quality( NEW.marc, match_set );
1621     END IF;
1622
1623     -- Perfect matches on 901$c exit early with a match with high quality.
1624     incoming_existing_id :=
1625         oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
1626
1627     IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
1628         SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
1629         IF tmp_rec IS NOT NULL THEN
1630             INSERT INTO vandelay.bib_match (queued_record, eg_record, match_score, quality) 
1631                 SELECT
1632                     NEW.id, 
1633                     b.id,
1634                     9999,
1635                     -- note: no match_set means quality==0
1636                     vandelay.measure_record_quality( b.marc, match_set )
1637                 FROM biblio.record_entry b
1638                 WHERE id = incoming_existing_id::bigint;
1639         END IF;
1640     END IF;
1641
1642     IF match_set IS NULL THEN
1643         RETURN NEW;
1644     END IF;
1645
1646     FOR test_result IN SELECT * FROM
1647         vandelay.match_set_test_marcxml(match_set, NEW.marc) LOOP
1648
1649         INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
1650             SELECT  
1651                 NEW.id,
1652                 test_result.record,
1653                 test_result.quality,
1654                 vandelay.measure_record_quality( b.marc, match_set )
1655                 FROM  biblio.record_entry b
1656                 WHERE id = test_result.record;
1657
1658     END LOOP;
1659
1660     RETURN NEW;
1661 END;
1662 $func$ LANGUAGE PLPGSQL;
1663
1664 CREATE OR REPLACE FUNCTION vandelay.measure_record_quality ( xml TEXT, match_set_id INT ) RETURNS INT AS $_$
1665 DECLARE
1666     out_q   INT := 0;
1667     rvalue  TEXT;
1668     test    vandelay.match_set_quality%ROWTYPE;
1669 BEGIN
1670
1671     FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
1672         IF test.tag IS NOT NULL THEN
1673             FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
1674                 IF test.value = rvalue THEN
1675                     out_q := out_q + test.quality;
1676                 END IF;
1677             END LOOP;
1678         ELSE
1679             IF test.value = vandelay.extract_rec_attrs(xml, ARRAY[test.svf]) -> test.svf THEN
1680                 out_q := out_q + test.quality;
1681             END IF;
1682         END IF;
1683     END LOOP;
1684
1685     RETURN out_q;
1686 END;
1687 $_$ LANGUAGE PLPGSQL;
1688
1689
1690 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1691 DECLARE
1692     merge_profile   vandelay.merge_profile%ROWTYPE;
1693     dyn_profile     vandelay.compile_profile%ROWTYPE;
1694     editor_string   TEXT;
1695     editor_id       INT;
1696     source_marc     TEXT;
1697     target_marc     TEXT;
1698     eg_marc         TEXT;
1699     v_marc          TEXT;
1700     replace_rule    TEXT;
1701 BEGIN
1702
1703     SELECT  q.marc INTO v_marc
1704       FROM  vandelay.queued_record q
1705             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
1706       LIMIT 1;
1707
1708     IF v_marc IS NULL THEN
1709         -- RAISE NOTICE 'no marc for vandelay or bib record';
1710         RETURN FALSE;
1711     END IF;
1712
1713     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
1714         UPDATE  vandelay.queued_bib_record
1715           SET   imported_as = eg_id,
1716                 import_time = NOW()
1717           WHERE id = import_id;
1718
1719         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
1720
1721         IF editor_string IS NOT NULL AND editor_string <> '' THEN
1722             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
1723
1724             IF editor_id IS NULL THEN
1725                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
1726             END IF;
1727
1728             IF editor_id IS NOT NULL THEN
1729                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
1730             END IF;
1731         END IF;
1732
1733         RETURN TRUE;
1734     END IF;
1735
1736     -- RAISE NOTICE 'update of biblio.record_entry failed';
1737
1738     RETURN FALSE;
1739
1740 END;
1741 $$ LANGUAGE PLPGSQL;
1742
1743
1744 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
1745 DECLARE
1746     eg_id           BIGINT;
1747     lwm_ratio_value NUMERIC;
1748 BEGIN
1749
1750     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
1751
1752     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
1753
1754     IF FOUND THEN
1755         -- RAISE NOTICE 'already imported, cannot auto-overlay'
1756         RETURN FALSE;
1757     END IF;
1758
1759     SELECT  m.eg_record INTO eg_id
1760       FROM  vandelay.bib_match m
1761             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
1762             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
1763             JOIN biblio.record_entry r ON (r.id = m.eg_record)
1764       WHERE m.queued_record = import_id
1765             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
1766       ORDER BY  m.match_score DESC, -- required match score
1767                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
1768                 m.id -- when in doubt, use the first match
1769       LIMIT 1;
1770
1771     IF eg_id IS NULL THEN
1772         -- RAISE NOTICE 'incoming record is not of high enough quality';
1773         RETURN FALSE;
1774     END IF;
1775
1776     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
1777 END;
1778 $$ LANGUAGE PLPGSQL;
1779
1780 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
1781 DECLARE
1782     eg_id           BIGINT;
1783     lwm_ratio_value NUMERIC;
1784 BEGIN
1785
1786     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
1787
1788     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
1789
1790     IF FOUND THEN
1791         -- RAISE NOTICE 'already imported, cannot auto-overlay'
1792         RETURN FALSE;
1793     END IF;
1794
1795     SELECT  m.eg_record INTO eg_id
1796       FROM  vandelay.bib_match m
1797             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
1798             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
1799             JOIN biblio.record_entry r ON (r.id = m.eg_record)
1800       WHERE m.queued_record = import_id
1801             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
1802       ORDER BY  m.match_score DESC, -- required match score
1803                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
1804                 m.id -- when in doubt, use the first match
1805       LIMIT 1;
1806
1807     IF eg_id IS NULL THEN
1808         -- RAISE NOTICE 'incoming record is not of high enough quality';
1809         RETURN FALSE;
1810     END IF;
1811
1812     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
1813 END;
1814 $$ LANGUAGE PLPGSQL;
1815
1816
1817 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( queue_id BIGINT, merge_profile_id INT, lwm_ratio_value NUMERIC ) RETURNS SETOF BIGINT AS $$
1818 DECLARE
1819     queued_record   vandelay.queued_bib_record%ROWTYPE;
1820 BEGIN
1821
1822     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
1823
1824         IF vandelay.auto_overlay_bib_record_with_best( queued_record.id, merge_profile_id, lwm_ratio_value ) THEN
1825             RETURN NEXT queued_record.id;
1826         END IF;
1827
1828     END LOOP;
1829
1830     RETURN;
1831     
1832 END;
1833 $$ LANGUAGE PLPGSQL;
1834
1835 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( import_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1836     SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
1837 $$ LANGUAGE SQL;
1838
1839 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
1840 DECLARE
1841     value   TEXT;
1842     atype   TEXT;
1843     adef    RECORD;
1844 BEGIN
1845     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1846         RETURN NEW;
1847     END IF;
1848
1849     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
1850
1851         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
1852         IF (value IS NOT NULL AND value <> '') THEN
1853             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1854         END IF;
1855
1856     END LOOP;
1857
1858     RETURN NULL;
1859 END;
1860 $$ LANGUAGE PLPGSQL;
1861
1862 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
1863 DECLARE
1864     attr_def    BIGINT;
1865     item_data   vandelay.import_item%ROWTYPE;
1866 BEGIN
1867
1868     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1869         RETURN NEW;
1870     END IF;
1871
1872     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
1873
1874     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
1875         INSERT INTO vandelay.import_item (
1876             record,
1877             definition,
1878             owning_lib,
1879             circ_lib,
1880             call_number,
1881             copy_number,
1882             status,
1883             location,
1884             circulate,
1885             deposit,
1886             deposit_amount,
1887             ref,
1888             holdable,
1889             price,
1890             barcode,
1891             circ_modifier,
1892             circ_as_type,
1893             alert_message,
1894             pub_note,
1895             priv_note,
1896             opac_visible
1897         ) VALUES (
1898             NEW.id,
1899             item_data.definition,
1900             item_data.owning_lib,
1901             item_data.circ_lib,
1902             item_data.call_number,
1903             item_data.copy_number,
1904             item_data.status,
1905             item_data.location,
1906             item_data.circulate,
1907             item_data.deposit,
1908             item_data.deposit_amount,
1909             item_data.ref,
1910             item_data.holdable,
1911             item_data.price,
1912             item_data.barcode,
1913             item_data.circ_modifier,
1914             item_data.circ_as_type,
1915             item_data.alert_message,
1916             item_data.pub_note,
1917             item_data.priv_note,
1918             item_data.opac_visible
1919         );
1920     END LOOP;
1921
1922     RETURN NULL;
1923 END;
1924 $func$ LANGUAGE PLPGSQL;
1925
1926 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1927 BEGIN
1928     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1929         RETURN NEW;
1930     END IF;
1931
1932     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1933     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1934
1935     IF TG_OP = 'UPDATE' THEN
1936         RETURN NEW;
1937     END IF;
1938     RETURN OLD;
1939 END;
1940 $$ LANGUAGE PLPGSQL;
1941
1942 -- ALTER TABLEs...
1943
1944 DROP TRIGGER zz_match_bibs_trigger ON vandelay.queued_bib_record;
1945 CREATE TRIGGER zz_match_bibs_trigger
1946     BEFORE INSERT OR UPDATE ON vandelay.queued_bib_record
1947     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1948
1949 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1950 DECLARE
1951     value   TEXT;
1952     atype   TEXT;
1953     adef    RECORD;
1954 BEGIN
1955     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1956         RETURN NEW;
1957     END IF;
1958
1959     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1960
1961         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1962         IF (value IS NOT NULL AND value <> '') THEN
1963             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1964         END IF;
1965
1966     END LOOP;
1967
1968     RETURN NULL;
1969 END;
1970 $$ LANGUAGE PLPGSQL;
1971
1972 ALTER TABLE vandelay.authority_attr_definition DROP COLUMN ident;
1973 ALTER TABLE vandelay.queued_authority_record
1974     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1975     ADD COLUMN error_detail TEXT;
1976
1977 ALTER TABLE vandelay.authority_match DROP COLUMN matched_attr;
1978 ALTER TABLE vandelay.authority_match ADD COLUMN quality INTEGER NOT NULL DEFAULT 0;
1979
1980 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1981 BEGIN
1982     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1983         RETURN NEW;
1984     END IF;
1985
1986     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1987     IF TG_OP = 'UPDATE' THEN
1988         RETURN NEW;
1989     END IF;
1990     RETURN OLD;
1991 END;
1992 $$ LANGUAGE PLPGSQL;
1993
1994 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
1995 DECLARE
1996         auth    authority.record_entry%ROWTYPE;
1997         output  authority.full_rec%ROWTYPE;
1998         field   RECORD;
1999 BEGIN
2000         SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
2001
2002         FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
2003                 output.record := rid;
2004                 output.ind1 := field.ind1;
2005                 output.ind2 := field.ind2;
2006                 output.tag := field.tag;
2007                 output.subfield := field.subfield;
2008                 output.value := field.value;
2009
2010                 RETURN NEXT output;
2011         END LOOP;
2012 END;
2013 $func$ LANGUAGE PLPGSQL;
2014
2015 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
2016 DECLARE
2017         bib     biblio.record_entry%ROWTYPE;
2018         output  metabib.full_rec%ROWTYPE;
2019         field   RECORD;
2020 BEGIN
2021         SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
2022
2023         FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
2024                 output.record := rid;
2025                 output.ind1 := field.ind1;
2026                 output.ind2 := field.ind2;
2027                 output.tag := field.tag;
2028                 output.subfield := field.subfield;
2029                 output.value := field.value;
2030
2031                 RETURN NEXT output;
2032         END LOOP;
2033 END;
2034 $func$ LANGUAGE PLPGSQL;
2035
2036 -----------------------------------------------
2037 -- Seed data for import errors
2038 -----------------------------------------------
2039
2040 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'general.unknown', oils_i18n_gettext('general.unknown', 'Import or Overlay failed', 'vie', 'description') );
2041 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.duplicate.barcode', oils_i18n_gettext('import.item.duplicate.barcode', 'Import failed due to barcode collision', 'vie', 'description') );
2042 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.circ_modifier', oils_i18n_gettext('import.item.invalid.circ_modifier', 'Import failed due to invalid circulation modifier', 'vie', 'description') );
2043 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.location', oils_i18n_gettext('import.item.invalid.location', 'Import failed due to invalid copy location', 'vie', 'description') );
2044 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.sysid', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
2045 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.tcn', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
2046 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.missing.sysid', oils_i18n_gettext('overlay.missing.sysid', 'Overlay failed due to missing system id', 'vie', 'description') );
2047 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.auth.duplicate.acn', oils_i18n_gettext('import.auth.duplicate.acn', 'Import failed due to Accession Number collision', 'vie', 'description') );
2048 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.xml.malformed', oils_i18n_gettext('import.xml.malformed', 'Malformed record cause Import failure', 'vie', 'description') );
2049 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.xml.malformed', oils_i18n_gettext('overlay.xml.malformed', 'Malformed record cause Overlay failure', 'vie', 'description') );
2050 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.record.quality', oils_i18n_gettext('overlay.record.quality', 'New record had insufficient quality', 'vie', 'description') );
2051
2052
2053 ----------------------------------------------------------------
2054 -- Seed data for queued record/item exports
2055 ----------------------------------------------------------------
2056
2057 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2058         'vandelay.queued_bib_record.print',
2059         'vqbr', 
2060         oils_i18n_gettext(
2061             'vandelay.queued_bib_record.print',
2062             'Print output has been requested for records in an Importer Bib Queue.',
2063             'ath',
2064             'description'
2065         ), 
2066         FALSE
2067     )
2068     ,(
2069         'vandelay.queued_bib_record.csv',
2070         'vqbr', 
2071         oils_i18n_gettext(
2072             'vandelay.queued_bib_record.csv',
2073             'CSV output has been requested for records in an Importer Bib Queue.',
2074             'ath',
2075             'description'
2076         ), 
2077         FALSE
2078     )
2079     ,(
2080         'vandelay.queued_bib_record.email',
2081         'vqbr', 
2082         oils_i18n_gettext(
2083             'vandelay.queued_bib_record.email',
2084             'An email has been requested for records in an Importer Bib Queue.',
2085             'ath',
2086             'description'
2087         ), 
2088         FALSE
2089     )
2090     ,(
2091         'vandelay.queued_auth_record.print',
2092         'vqar', 
2093         oils_i18n_gettext(
2094             'vandelay.queued_auth_record.print',
2095             'Print output has been requested for records in an Importer Authority Queue.',
2096             'ath',
2097             'description'
2098         ), 
2099         FALSE
2100     )
2101     ,(
2102         'vandelay.queued_auth_record.csv',
2103         'vqar', 
2104         oils_i18n_gettext(
2105             'vandelay.queued_auth_record.csv',
2106             'CSV output has been requested for records in an Importer Authority Queue.',
2107             'ath',
2108             'description'
2109         ), 
2110         FALSE
2111     )
2112     ,(
2113         'vandelay.queued_auth_record.email',
2114         'vqar', 
2115         oils_i18n_gettext(
2116             'vandelay.queued_auth_record.email',
2117             'An email has been requested for records in an Importer Authority Queue.',
2118             'ath',
2119             'description'
2120         ), 
2121         FALSE
2122     )
2123     ,(
2124         'vandelay.import_items.print',
2125         'vii', 
2126         oils_i18n_gettext(
2127             'vandelay.import_items.print',
2128             'Print output has been requested for Import Items from records in an Importer Bib Queue.',
2129             'ath',
2130             'description'
2131         ), 
2132         FALSE
2133     )
2134     ,(
2135         'vandelay.import_items.csv',
2136         'vii', 
2137         oils_i18n_gettext(
2138             'vandelay.import_items.csv',
2139             'CSV output has been requested for Import Items from records in an Importer Bib Queue.',
2140             'ath',
2141             'description'
2142         ), 
2143         FALSE
2144     )
2145     ,(
2146         'vandelay.import_items.email',
2147         'vii', 
2148         oils_i18n_gettext(
2149             'vandelay.import_items.email',
2150             'An email has been requested for Import Items from records in an Importer Bib Queue.',
2151             'ath',
2152             'description'
2153         ), 
2154         FALSE
2155     )
2156 ;
2157
2158 INSERT INTO action_trigger.event_definition (
2159         id,
2160         active,
2161         owner,
2162         name,
2163         hook,
2164         validator,
2165         reactor,
2166         group_field,
2167         granularity,
2168         template
2169     ) VALUES (
2170         39,
2171         TRUE,
2172         1,
2173         'Print Output for Queued Bib Records',
2174         'vandelay.queued_bib_record.print',
2175         'NOOP_True',
2176         'ProcessTemplate',
2177         'queue.owner',
2178         'print-on-demand',
2179 $$
2180 [%- USE date -%]
2181 <pre>
2182 Queue ID: [% target.0.queue.id %]
2183 Queue Name: [% target.0.queue.name %]
2184 Queue Type: [% target.0.queue.queue_type %]
2185 Complete? [% target.0.queue.complete %]
2186
2187     [% FOR vqbr IN target %]
2188 =-=-=
2189  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2190  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2191  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2192  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2193  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2194  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2195  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2196  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2197  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2198  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2199  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2200  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2201  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2202  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2203  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2204
2205     [% END %]
2206 </pre>
2207 $$
2208     )
2209 ;
2210
2211 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2212     39, 'attributes')
2213     ,( 39, 'queue')
2214 ;
2215
2216 INSERT INTO action_trigger.event_definition (
2217         id,
2218         active,
2219         owner,
2220         name,
2221         hook,
2222         validator,
2223         reactor,
2224         group_field,
2225         granularity,
2226         template
2227     ) VALUES (
2228         40,
2229         TRUE,
2230         1,
2231         'CSV Output for Queued Bib Records',
2232         'vandelay.queued_bib_record.csv',
2233         'NOOP_True',
2234         'ProcessTemplate',
2235         'queue.owner',
2236         'print-on-demand',
2237 $$
2238 [%- USE date -%]
2239 "Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode"
2240 [% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]"
2241 [% END %]
2242 $$
2243     )
2244 ;
2245
2246 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2247     40, 'attributes')
2248     ,( 40, 'queue')
2249 ;
2250
2251 INSERT INTO action_trigger.event_definition (
2252         id,
2253         active,
2254         owner,
2255         name,
2256         hook,
2257         validator,
2258         reactor,
2259         group_field,
2260         granularity,
2261         template
2262     ) VALUES (
2263         41,
2264         TRUE,
2265         1,
2266         'Email Output for Queued Bib Records',
2267         'vandelay.queued_bib_record.email',
2268         'NOOP_True',
2269         'SendEmail',
2270         'queue.owner',
2271         NULL,
2272 $$
2273 [%- USE date -%]
2274 [%- SET user = target.0.queue.owner -%]
2275 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2276 From: [%- params.sender_email || default_sender %]
2277 Subject: Bibs from Import Queue
2278
2279 Queue ID: [% target.0.queue.id %]
2280 Queue Name: [% target.0.queue.name %]
2281 Queue Type: [% target.0.queue.queue_type %]
2282 Complete? [% target.0.queue.complete %]
2283
2284     [% FOR vqbr IN target %]
2285 =-=-=
2286  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2287  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2288  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2289  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2290  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2291  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2292  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2293  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2294  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2295  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2296  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2297  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2298  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2299  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2300  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2301
2302     [% END %]
2303
2304 $$
2305     )
2306 ;
2307
2308 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2309     41, 'attributes')
2310     ,( 41, 'queue')
2311     ,( 41, 'queue.owner')
2312 ;
2313
2314 INSERT INTO action_trigger.event_definition (
2315         id,
2316         active,
2317         owner,
2318         name,
2319         hook,
2320         validator,
2321         reactor,
2322         group_field,
2323         granularity,
2324         template
2325     ) VALUES (
2326         42,
2327         TRUE,
2328         1,
2329         'Print Output for Queued Authority Records',
2330         'vandelay.queued_auth_record.print',
2331         'NOOP_True',
2332         'ProcessTemplate',
2333         'queue.owner',
2334         'print-on-demand',
2335 $$
2336 [%- USE date -%]
2337 <pre>
2338 Queue ID: [% target.0.queue.id %]
2339 Queue Name: [% target.0.queue.name %]
2340 Queue Type: [% target.0.queue.queue_type %]
2341 Complete? [% target.0.queue.complete %]
2342
2343     [% FOR vqar IN target %]
2344 =-=-=
2345  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2346
2347     [% END %]
2348 </pre>
2349 $$
2350     )
2351 ;
2352
2353 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2354     42, 'attributes')
2355     ,( 42, 'queue')
2356 ;
2357
2358 INSERT INTO action_trigger.event_definition (
2359         id,
2360         active,
2361         owner,
2362         name,
2363         hook,
2364         validator,
2365         reactor,
2366         group_field,
2367         granularity,
2368         template
2369     ) VALUES (
2370         43,
2371         TRUE,
2372         1,
2373         'CSV Output for Queued Authority Records',
2374         'vandelay.queued_auth_record.csv',
2375         'NOOP_True',
2376         'ProcessTemplate',
2377         'queue.owner',
2378         'print-on-demand',
2379 $$
2380 [%- USE date -%]
2381 "Record Identifier"
2382 [% FOR vqar IN target %]"[% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) | replace('"', '""') %]"
2383 [% END %]
2384 $$
2385     )
2386 ;
2387
2388 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2389     43, 'attributes')
2390     ,( 43, 'queue')
2391 ;
2392
2393 INSERT INTO action_trigger.event_definition (
2394         id,
2395         active,
2396         owner,
2397         name,
2398         hook,
2399         validator,
2400         reactor,
2401         group_field,
2402         granularity,
2403         template
2404     ) VALUES (
2405         44,
2406         TRUE,
2407         1,
2408         'Email Output for Queued Authority Records',
2409         'vandelay.queued_auth_record.email',
2410         'NOOP_True',
2411         'SendEmail',
2412         'queue.owner',
2413         NULL,
2414 $$
2415 [%- USE date -%]
2416 [%- SET user = target.0.queue.owner -%]
2417 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2418 From: [%- params.sender_email || default_sender %]
2419 Subject: Authorities from Import Queue
2420
2421 Queue ID: [% target.0.queue.id %]
2422 Queue Name: [% target.0.queue.name %]
2423 Queue Type: [% target.0.queue.queue_type %]
2424 Complete? [% target.0.queue.complete %]
2425
2426     [% FOR vqar IN target %]
2427 =-=-=
2428  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2429
2430     [% END %]
2431
2432 $$
2433     )
2434 ;
2435
2436 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2437     44, 'attributes')
2438     ,( 44, 'queue')
2439     ,( 44, 'queue.owner')
2440 ;
2441
2442 INSERT INTO action_trigger.event_definition (
2443         id,
2444         active,
2445         owner,
2446         name,
2447         hook,
2448         validator,
2449         reactor,
2450         group_field,
2451         granularity,
2452         template
2453     ) VALUES (
2454         45,
2455         TRUE,
2456         1,
2457         'Print Output for Import Items from Queued Bib Records',
2458         'vandelay.import_items.print',
2459         'NOOP_True',
2460         'ProcessTemplate',
2461         'record.queue.owner',
2462         'print-on-demand',
2463 $$
2464 [%- USE date -%]
2465 <pre>
2466 Queue ID: [% target.0.record.queue.id %]
2467 Queue Name: [% target.0.record.queue.name %]
2468 Queue Type: [% target.0.record.queue.queue_type %]
2469 Complete? [% target.0.record.queue.complete %]
2470
2471     [% FOR vii IN target %]
2472 =-=-=
2473  Import Item ID         | [% vii.id %]
2474  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2475  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2476  Attribute Definition   | [% vii.definition %]
2477  Import Error           | [% vii.import_error %]
2478  Import Error Detail    | [% vii.error_detail %]
2479  Owning Library         | [% vii.owning_lib %]
2480  Circulating Library    | [% vii.circ_lib %]
2481  Call Number            | [% vii.call_number %]
2482  Copy Number            | [% vii.copy_number %]
2483  Status                 | [% vii.status.name %]
2484  Shelving Location      | [% vii.location.name %]
2485  Circulate              | [% vii.circulate %]
2486  Deposit                | [% vii.deposit %]
2487  Deposit Amount         | [% vii.deposit_amount %]
2488  Reference              | [% vii.ref %]
2489  Holdable               | [% vii.holdable %]
2490  Price                  | [% vii.price %]
2491  Barcode                | [% vii.barcode %]
2492  Circulation Modifier   | [% vii.circ_modifier %]
2493  Circulate As MARC Type | [% vii.circ_as_type %]
2494  Alert Message          | [% vii.alert_message %]
2495  Public Note            | [% vii.pub_note %]
2496  Private Note           | [% vii.priv_note %]
2497  OPAC Visible           | [% vii.opac_visible %]
2498
2499     [% END %]
2500 </pre>
2501 $$
2502     )
2503 ;
2504
2505 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2506     45, 'record')
2507     ,( 45, 'record.attributes')
2508     ,( 45, 'record.queue')
2509     ,( 45, 'record.queue.owner')
2510 ;
2511
2512 INSERT INTO action_trigger.event_definition (
2513         id,
2514         active,
2515         owner,
2516         name,
2517         hook,
2518         validator,
2519         reactor,
2520         group_field,
2521         granularity,
2522         template
2523     ) VALUES (
2524         46,
2525         TRUE,
2526         1,
2527         'CSV Output for Import Items from Queued Bib Records',
2528         'vandelay.import_items.csv',
2529         'NOOP_True',
2530         'ProcessTemplate',
2531         'record.queue.owner',
2532         'print-on-demand',
2533 $$
2534 [%- USE date -%]
2535 "Import Item ID","Title of work","ISBN","Attribute Definition","Import Error","Import Error Detail","Owning Library","Circulating Library","Call Number","Copy Number","Status","Shelving Location","Circulate","Deposit","Deposit Amount","Reference","Holdable","Price","Barcode","Circulation Modifier","Circulate As MARC Type","Alert Message","Public Note","Private Note","OPAC Visible"
2536 [% FOR vii IN target %]"[% vii.id | replace('"', '""') %]","[% helpers.get_queued_bib_attr('title',vii.record.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vii.record.attributes) | replace('"', '""') %]","[% vii.definition | replace('"', '""') %]","[% vii.import_error | replace('"', '""') %]","[% vii.error_detail | replace('"', '""') %]","[% vii.owning_lib | replace('"', '""') %]","[% vii.circ_lib | replace('"', '""') %]","[% vii.call_number | replace('"', '""') %]","[% vii.copy_number | replace('"', '""') %]","[% vii.status.name | replace('"', '""') %]","[% vii.location.name | replace('"', '""') %]","[% vii.circulate | replace('"', '""') %]","[% vii.deposit | replace('"', '""') %]","[% vii.deposit_amount | replace('"', '""') %]","[% vii.ref | replace('"', '""') %]","[% vii.holdable | replace('"', '""') %]","[% vii.price | replace('"', '""') %]","[% vii.barcode | replace('"', '""') %]","[% vii.circ_modifier | replace('"', '""') %]","[% vii.circ_as_type | replace('"', '""') %]","[% vii.alert_message | replace('"', '""') %]","[% vii.pub_note | replace('"', '""') %]","[% vii.priv_note | replace('"', '""') %]","[% vii.opac_visible | replace('"', '""') %]"
2537 [% END %]
2538 $$
2539     )
2540 ;
2541
2542 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2543     46, 'record')
2544     ,( 46, 'record.attributes')
2545     ,( 46, 'record.queue')
2546     ,( 46, 'record.queue.owner')
2547 ;
2548
2549 INSERT INTO action_trigger.event_definition (
2550         id,
2551         active,
2552         owner,
2553         name,
2554         hook,
2555         validator,
2556         reactor,
2557         group_field,
2558         granularity,
2559         template
2560     ) VALUES (
2561         47,
2562         TRUE,
2563         1,
2564         'Email Output for Import Items from Queued Bib Records',
2565         'vandelay.import_items.email',
2566         'NOOP_True',
2567         'SendEmail',
2568         'record.queue.owner',
2569         NULL,
2570 $$
2571 [%- USE date -%]
2572 [%- SET user = target.0.record.queue.owner -%]
2573 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2574 From: [%- params.sender_email || default_sender %]
2575 Subject: Import Items from Import Queue
2576
2577 Queue ID: [% target.0.record.queue.id %]
2578 Queue Name: [% target.0.record.queue.name %]
2579 Queue Type: [% target.0.record.queue.queue_type %]
2580 Complete? [% target.0.record.queue.complete %]
2581
2582     [% FOR vii IN target %]
2583 =-=-=
2584  Import Item ID         | [% vii.id %]
2585  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2586  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2587  Attribute Definition   | [% vii.definition %]
2588  Import Error           | [% vii.import_error %]
2589  Import Error Detail    | [% vii.error_detail %]
2590  Owning Library         | [% vii.owning_lib %]
2591  Circulating Library    | [% vii.circ_lib %]
2592  Call Number            | [% vii.call_number %]
2593  Copy Number            | [% vii.copy_number %]
2594  Status                 | [% vii.status.name %]
2595  Shelving Location      | [% vii.location.name %]
2596  Circulate              | [% vii.circulate %]
2597  Deposit                | [% vii.deposit %]
2598  Deposit Amount         | [% vii.deposit_amount %]
2599  Reference              | [% vii.ref %]
2600  Holdable               | [% vii.holdable %]
2601  Price                  | [% vii.price %]
2602  Barcode                | [% vii.barcode %]
2603  Circulation Modifier   | [% vii.circ_modifier %]
2604  Circulate As MARC Type | [% vii.circ_as_type %]
2605  Alert Message          | [% vii.alert_message %]
2606  Public Note            | [% vii.pub_note %]
2607  Private Note           | [% vii.priv_note %]
2608  OPAC Visible           | [% vii.opac_visible %]
2609
2610     [% END %]
2611 $$
2612     )
2613 ;
2614
2615 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2616     47, 'record')
2617     ,( 47, 'record.attributes')
2618     ,( 47, 'record.queue')
2619     ,( 47, 'record.queue.owner')
2620 ;
2621
2622
2623
2624 SELECT evergreen.upgrade_deps_block_check('0574', :eg_version);
2625
2626 UPDATE action_trigger.event_definition SET template =
2627 $$
2628 [%- USE date -%]
2629 <style>
2630     table { border-collapse: collapse; }
2631     td { padding: 5px; border-bottom: 1px solid #888; }
2632     th { font-weight: bold; }
2633 </style>
2634 [%
2635     # Sort the holds into copy-location buckets
2636     # In the main print loop, sort each bucket by callnumber before printing
2637     SET holds_list = [];
2638     SET loc_data = [];
2639     SET current_location = target.0.current_copy.location.id;
2640     FOR hold IN target;
2641         IF current_location != hold.current_copy.location.id;
2642             SET current_location = hold.current_copy.location.id;
2643             holds_list.push(loc_data);
2644             SET loc_data = [];
2645         END;
2646         SET hold_data = {
2647             'hold' => hold,
2648             'callnumber' => hold.current_copy.call_number.label
2649         };
2650         loc_data.push(hold_data);
2651     END;
2652     holds_list.push(loc_data)
2653 %]
2654 <table>
2655     <thead>
2656         <tr>
2657             <th>Title</th>
2658             <th>Author</th>
2659             <th>Shelving Location</th>
2660             <th>Call Number</th>
2661             <th>Barcode/Part</th>
2662             <th>Patron</th>
2663         </tr>
2664     </thead>
2665     <tbody>
2666     [% FOR loc_data IN holds_list  %]
2667         [% FOR hold_data IN loc_data.sort('callnumber') %]
2668             [%
2669                 SET hold = hold_data.hold;
2670                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
2671             %]
2672             <tr>
2673                 <td>[% copy_data.title | truncate %]</td>
2674                 <td>[% copy_data.author | truncate %]</td>
2675                 <td>[% hold.current_copy.location.name %]</td>
2676                 <td>[% hold.current_copy.call_number.label %]</td>
2677                 <td>[% hold.current_copy.barcode %]
2678                     [% FOR part IN hold.current_copy.parts %]
2679                        [% part.part.label %]
2680                     [% END %]
2681                 </td>
2682                 <td>[% hold.usr.card.barcode %]</td>
2683             </tr>
2684         [% END %]
2685     [% END %]
2686     <tbody>
2687 </table>
2688 $$
2689     WHERE id = 35;
2690
2691 INSERT INTO action_trigger.environment (
2692         event_def,
2693         path
2694     ) VALUES
2695         (35, 'current_copy.parts'),
2696         (35, 'current_copy.parts.part')
2697 ;
2698
2699
2700 -- Evergreen DB patch XXXX.schema.authority-control-sets.sql
2701 --
2702 -- Schema upgrade to add Authority Control Set functionality
2703 --
2704
2705
2706 -- check whether patch can be applied
2707 SELECT evergreen.upgrade_deps_block_check('0575', :eg_version);
2708
2709 CREATE TABLE authority.control_set (
2710     id          SERIAL  PRIMARY KEY,
2711     name        TEXT    NOT NULL UNIQUE, -- i18n
2712     description TEXT                     -- i18n
2713 );
2714
2715 CREATE TABLE authority.control_set_authority_field (
2716     id          SERIAL  PRIMARY KEY,
2717     main_entry  INT     REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2718     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2719     tag         CHAR(3) NOT NULL,
2720     nfi CHAR(1),
2721     sf_list     TEXT    NOT NULL,
2722     name        TEXT    NOT NULL, -- i18n
2723     description TEXT              -- i18n
2724 );
2725
2726 CREATE TABLE authority.control_set_bib_field (
2727     id              SERIAL  PRIMARY KEY,
2728     authority_field INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2729     tag             CHAR(3) NOT NULL
2730 );
2731
2732 CREATE TABLE authority.thesaurus (
2733     code        TEXT    PRIMARY KEY,     -- MARC21 thesaurus code
2734     control_set INT     REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2735     name        TEXT    NOT NULL UNIQUE, -- i18n
2736     description TEXT                     -- i18n
2737 );
2738
2739 CREATE TABLE authority.browse_axis (
2740     code        TEXT    PRIMARY KEY,
2741     name        TEXT    UNIQUE NOT NULL, -- i18n
2742     sorter      TEXT    REFERENCES config.record_attr_definition (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2743     description TEXT
2744 );
2745
2746 CREATE TABLE authority.browse_axis_authority_field_map (
2747     id          SERIAL  PRIMARY KEY,
2748     axis        TEXT    NOT NULL REFERENCES authority.browse_axis (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2749     field       INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
2750 );
2751
2752 ALTER TABLE authority.record_entry ADD COLUMN control_set INT REFERENCES authority.control_set (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED;
2753 ALTER TABLE authority.rec_descriptor DROP COLUMN char_encoding, ADD COLUMN encoding_level TEXT, ADD COLUMN thesaurus TEXT;
2754
2755 CREATE INDEX authority_full_rec_value_index ON authority.full_rec (value);
2756 CREATE OR REPLACE RULE protect_authority_rec_delete AS ON DELETE TO authority.record_entry DO INSTEAD (UPDATE authority.record_entry SET deleted = TRUE WHERE OLD.id = authority.record_entry.id; DELETE FROM authority.full_rec WHERE record = OLD.id);
2757  
2758 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
2759 DECLARE
2760     acsaf           authority.control_set_authority_field%ROWTYPE;
2761     tag_used        TEXT;
2762     sf              TEXT;
2763     thes_code       TEXT;
2764     cset            INT;
2765     heading_text    TEXT;
2766     tmp_text        TEXT;
2767 BEGIN
2768     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
2769     IF thes_code IS NULL THEN
2770         thes_code := '|';
2771     END IF;
2772
2773     SELECT control_set INTO cset FROM authority.thesaurus WHERE code = thes_code;
2774     IF NOT FOUND THEN
2775         cset = 1;
2776     END IF;
2777
2778     heading_text := '';
2779     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
2780         tag_used := acsaf.tag;
2781         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
2782             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
2783             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
2784                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
2785             END IF;
2786         END LOOP;
2787         EXIT WHEN heading_text <> '';
2788     END LOOP;
2789  
2790     IF thes_code = 'z' THEN
2791         thes_code := oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml);
2792     END IF;
2793
2794     IF heading_text <> '' THEN
2795         IF no_thesaurus IS TRUE THEN
2796             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
2797         ELSE
2798             heading_text := tag_used || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
2799         END IF;
2800     ELSE
2801         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
2802     END IF;
2803
2804     RETURN heading_text;
2805 END;
2806 $func$ LANGUAGE PLPGSQL IMMUTABLE;
2807
2808 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2809     SELECT authority.normalize_heading($1, TRUE);
2810 $func$ LANGUAGE SQL IMMUTABLE;
2811
2812 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2813     SELECT authority.normalize_heading($1, FALSE);
2814 $func$ LANGUAGE SQL IMMUTABLE;
2815
2816 CREATE OR REPLACE VIEW authority.tracing_links AS
2817     SELECT  main.record AS record,
2818             main.id AS main_id,
2819             main.tag AS main_tag,
2820             oils_xpath_string('//*[@tag="'||main.tag||'"]/*[local-name()="subfield"]', are.marc) AS main_value,
2821             substr(link.value,1,1) AS relationship,
2822             substr(link.value,2,1) AS use_restriction,
2823             substr(link.value,3,1) AS deprecation,
2824             substr(link.value,4,1) AS display_restriction,
2825             link.id AS link_id,
2826             link.tag AS link_tag,
2827             oils_xpath_string('//*[@tag="'||link.tag||'"]/*[local-name()="subfield"]', are.marc) AS link_value,
2828             authority.normalize_heading(are.marc) AS normalized_main_value
2829       FROM  authority.full_rec main
2830             JOIN authority.record_entry are ON (main.record = are.id)
2831             JOIN authority.control_set_authority_field main_entry
2832                 ON (main_entry.tag = main.tag
2833                     AND main_entry.main_entry IS NULL
2834                     AND main.subfield = 'a' )
2835             JOIN authority.control_set_authority_field sub_entry
2836                 ON (main_entry.id = sub_entry.main_entry)
2837             JOIN authority.full_rec link
2838                 ON (link.record = main.record
2839                     AND link.tag = sub_entry.tag
2840                     AND link.subfield = 'w' );
2841  
2842 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
2843 DECLARE
2844     cset                INT;
2845     main_entry          authority.control_set_authority_field%ROWTYPE;
2846     bib_field           authority.control_set_bib_field%ROWTYPE;
2847     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
2848     replace_data        XML[] DEFAULT '{}'::XML[];
2849     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
2850     auth_field          XML[];
2851 BEGIN
2852     IF auth_id IS NULL THEN
2853         RETURN NULL;
2854     END IF;
2855
2856     -- Default to the LoC controll set
2857     SELECT COALESCE(control_set,1) INTO cset FROM authority.record_entry WHERE id = auth_id;
2858
2859     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
2860         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
2861         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
2862             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
2863                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
2864                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
2865             END LOOP;
2866             EXIT;
2867         END IF;
2868     END LOOP;
2869  
2870     RETURN XMLELEMENT(
2871         name record,
2872         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
2873         XMLELEMENT( name leader, '00881nam a2200193   4500'),
2874         replace_data,
2875         XMLELEMENT(
2876             name datafield,
2877             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
2878             XMLELEMENT(
2879                 name subfield,
2880                 XMLATTRIBUTES('r' AS code),
2881                 ARRAY_TO_STRING(replace_rules,',')
2882             )
2883         )
2884     )::TEXT;
2885 END;
2886 $f$ STABLE LANGUAGE PLPGSQL;
2887  
2888 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
2889     SELECT authority.generate_overlay_template( marc ) FROM authority.record_entry WHERE id = $1;
2890 $func$ LANGUAGE SQL;
2891  
2892 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
2893
2894     use MARC::Record;
2895     use MARC::File::XML (BinaryEncoding => 'UTF-8');
2896     use MARC::Charset;
2897     use strict;
2898
2899     MARC::Charset->assume_unicode(1);
2900
2901     my $target_xml = shift;
2902     my $source_xml = shift;
2903     my $field_spec = shift;
2904     my $force_add = shift || 0;
2905
2906     my $target_r = MARC::Record->new_from_xml( $target_xml );
2907     my $source_r = MARC::Record->new_from_xml( $source_xml );
2908
2909     return $target_xml unless ($target_r && $source_r);
2910
2911     my @field_list = split(',', $field_spec);
2912
2913     my %fields;
2914     for my $f (@field_list) {
2915         $f =~ s/^\s*//; $f =~ s/\s*$//;
2916         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
2917             my $field = $1;
2918             $field =~ s/\s+//;
2919             my $sf = $2;
2920             $sf =~ s/\s+//;
2921             my $match = $3;
2922             $match =~ s/^\s*//; $match =~ s/\s*$//;
2923             $fields{$field} = { sf => [ split('', $sf) ] };
2924             if ($match) {
2925                 my ($msf,$mre) = split('~', $match);
2926                 if (length($msf) > 0 and length($mre) > 0) {
2927                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
2928                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
2929                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
2930                 }
2931             }
2932         }
2933     }
2934
2935     for my $f ( keys %fields) {
2936         if ( @{$fields{$f}{sf}} ) {
2937             for my $from_field ($source_r->field( $f )) {
2938                 my @tos = $target_r->field( $f );
2939                 if (!@tos) {
2940                     next if (exists($fields{$f}{match}) and !$force_add);
2941                     my @new_fields = map { $_->clone } $source_r->field( $f );
2942                     $target_r->insert_fields_ordered( @new_fields );
2943                 } else {
2944                     for my $to_field (@tos) {
2945                         if (exists($fields{$f}{match})) {
2946                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
2947                         }
2948                         my @new_sf = map { ($_ => $from_field->subfield($_)) } grep { defined($from_field->subfield($_)) } @{$fields{$f}{sf}};
2949                         $to_field->add_subfields( @new_sf );
2950                     }
2951                 }
2952             }
2953         } else {
2954             my @new_fields = map { $_->clone } $source_r->field( $f );
2955             $target_r->insert_fields_ordered( @new_fields );
2956         }
2957     }
2958
2959     $target_xml = $target_r->as_xml_record;
2960     $target_xml =~ s/^<\?.+?\?>$//mo;
2961     $target_xml =~ s/\n//sgo;
2962     $target_xml =~ s/>\s+</></sgo;
2963
2964     return $target_xml;
2965
2966 $_$ LANGUAGE PLPERLU;
2967
2968
2969 CREATE INDEX by_heading ON authority.record_entry (authority.simple_normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE;
2970
2971 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, search_field, facet_field) VALUES
2972     (28, 'identifier', 'authority_id', oils_i18n_gettext(28, 'Authority Record ID', 'cmf', 'label'), 'marcxml', '//marc:datafield/marc:subfield[@code="0"]', FALSE, TRUE);
2973  
2974 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('AUT','z',' ');
2975 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MFHD','uvxy',' ');
2976  
2977 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'AUT', 17, 1, ' ');
2978 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Subj', '008', 'AUT', 11, 1, '|');
2979 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('RecStat', 'ldr', 'AUT', 5, 1, 'n');
2980  
2981 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
2982     SELECT  m.id,
2983             i.id,
2984             -1
2985       FROM  config.metabib_field m,
2986             config.index_normalizer i
2987       WHERE i.func = 'remove_paren_substring'
2988             AND m.id IN (28);
2989
2990 SELECT SETVAL('authority.control_set_id_seq'::TEXT, 100);
2991 SELECT SETVAL('authority.control_set_authority_field_id_seq'::TEXT, 1000);
2992 SELECT SETVAL('authority.control_set_bib_field_id_seq'::TEXT, 1000);
2993
2994 INSERT INTO authority.control_set (id, name, description) VALUES (
2995     1,
2996     oils_i18n_gettext('1','LoC','acs','name'),
2997     oils_i18n_gettext('1','Library of Congress standard authority record control semantics','acs','description')
2998 );
2999
3000 INSERT INTO authority.control_set_authority_field (id, control_set, main_entry, tag, sf_list, name) VALUES
3001
3002 -- Main entries
3003     (1, 1, NULL, '100', 'abcdefklmnopqrstvxyz', oils_i18n_gettext('1','Heading -- Personal Name','acsaf','name')),
3004     (2, 1, NULL, '110', 'abcdefgklmnoprstvxyz', oils_i18n_gettext('2','Heading -- Corporate Name','acsaf','name')),
3005     (3, 1, NULL, '111', 'acdefgklnpqstvxyz', oils_i18n_gettext('3','Heading -- Meeting Name','acsaf','name')),
3006     (4, 1, NULL, '130', 'adfgklmnoprstvxyz', oils_i18n_gettext('4','Heading -- Uniform Title','acsaf','name')),
3007     (5, 1, NULL, '150', 'abvxyz', oils_i18n_gettext('5','Heading -- Topical Term','acsaf','name')),
3008     (6, 1, NULL, '151', 'avxyz', oils_i18n_gettext('6','Heading -- Geographic Name','acsaf','name')),
3009     (7, 1, NULL, '155', 'avxyz', oils_i18n_gettext('7','Heading -- Genre/Form Term','acsaf','name')),
3010     (8, 1, NULL, '180', 'vxyz', oils_i18n_gettext('8','Heading -- General Subdivision','acsaf','name')),
3011     (9, 1, NULL, '181', 'vxyz', oils_i18n_gettext('9','Heading -- Geographic Subdivision','acsaf','name')),
3012     (10, 1, NULL, '182', 'vxyz', oils_i18n_gettext('10','Heading -- Chronological Subdivision','acsaf','name')),
3013     (11, 1, NULL, '185', 'vxyz', oils_i18n_gettext('11','Heading -- Form Subdivision','acsaf','name')),
3014     (12, 1, NULL, '148', 'avxyz', oils_i18n_gettext('12','Heading -- Chronological Term','acsaf','name')),
3015
3016 -- See Also From tracings
3017     (21, 1, 1, '500', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('21','See Also From Tracing -- Personal Name','acsaf','name')),
3018     (22, 1, 2, '510', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('22','See Also From Tracing -- Corporate Name','acsaf','name')),
3019     (23, 1, 3, '511', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('23','See Also From Tracing -- Meeting Name','acsaf','name')),
3020     (24, 1, 4, '530', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('24','See Also From Tracing -- Uniform Title','acsaf','name')),
3021     (25, 1, 5, '550', 'abivwxyz4', oils_i18n_gettext('25','See Also From Tracing -- Topical Term','acsaf','name')),
3022     (26, 1, 6, '551', 'aivwxyz4', oils_i18n_gettext('26','See Also From Tracing -- Geographic Name','acsaf','name')),
3023     (27, 1, 7, '555', 'aivwxyz4', oils_i18n_gettext('27','See Also From Tracing -- Genre/Form Term','acsaf','name')),
3024     (28, 1, 8, '580', 'ivwxyz4', oils_i18n_gettext('28','See Also From Tracing -- General Subdivision','acsaf','name')),
3025     (29, 1, 9, '581', 'ivwxyz4', oils_i18n_gettext('29','See Also From Tracing -- Geographic Subdivision','acsaf','name')),
3026     (30, 1, 10, '582', 'ivwxyz4', oils_i18n_gettext('30','See Also From Tracing -- Chronological Subdivision','acsaf','name')),
3027     (31, 1, 11, '585', 'ivwxyz4', oils_i18n_gettext('31','See Also From Tracing -- Form Subdivision','acsaf','name')),
3028     (32, 1, 12, '548', 'aivwxyz4', oils_i18n_gettext('32','See Also From Tracing -- Chronological Term','acsaf','name')),
3029
3030 -- Linking entries
3031     (41, 1, 1, '700', 'abcdefghjklmnopqrstvwxyz25', oils_i18n_gettext('41','Established Heading Linking Entry -- Personal Name','acsaf','name')),
3032     (42, 1, 2, '710', 'abcdefghklmnoprstvwxyz25', oils_i18n_gettext('42','Established Heading Linking Entry -- Corporate Name','acsaf','name')),
3033     (43, 1, 3, '711', 'acdefghklnpqstvwxyz25', oils_i18n_gettext('43','Established Heading Linking Entry -- Meeting Name','acsaf','name')),
3034     (44, 1, 4, '730', 'adfghklmnoprstvwxyz25', oils_i18n_gettext('44','Established Heading Linking Entry -- Uniform Title','acsaf','name')),
3035     (45, 1, 5, '750', 'abvwxyz25', oils_i18n_gettext('45','Established Heading Linking Entry -- Topical Term','acsaf','name')),
3036     (46, 1, 6, '751', 'avwxyz25', oils_i18n_gettext('46','Established Heading Linking Entry -- Geographic Name','acsaf','name')),
3037     (47, 1, 7, '755', 'avwxyz25', oils_i18n_gettext('47','Established Heading Linking Entry -- Genre/Form Term','acsaf','name')),
3038     (48, 1, 8, '780', 'vwxyz25', oils_i18n_gettext('48','Subdivision Linking Entry -- General Subdivision','acsaf','name')),
3039     (49, 1, 9, '781', 'vwxyz25', oils_i18n_gettext('49','Subdivision Linking Entry -- Geographic Subdivision','acsaf','name')),
3040     (50, 1, 10, '782', 'vwxyz25', oils_i18n_gettext('50','Subdivision Linking Entry -- Chronological Subdivision','acsaf','name')),
3041     (51, 1, 11, '785', 'vwxyz25', oils_i18n_gettext('51','Subdivision Linking Entry -- Form Subdivision','acsaf','name')),
3042     (52, 1, 12, '748', 'avwxyz25', oils_i18n_gettext('52','Established Heading Linking Entry -- Chronological Term','acsaf','name')),
3043
3044 -- See From tracings
3045     (61, 1, 1, '400', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('61','See Also Tracing -- Personal Name','acsaf','name')),
3046     (62, 1, 2, '410', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('62','See Also Tracing -- Corporate Name','acsaf','name')),
3047     (63, 1, 3, '411', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('63','See Also Tracing -- Meeting Name','acsaf','name')),
3048     (64, 1, 4, '430', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('64','See Also Tracing -- Uniform Title','acsaf','name')),
3049     (65, 1, 5, '450', 'abivwxyz4', oils_i18n_gettext('65','See Also Tracing -- Topical Term','acsaf','name')),
3050     (66, 1, 6, '451', 'aivwxyz4', oils_i18n_gettext('66','See Also Tracing -- Geographic Name','acsaf','name')),
3051     (67, 1, 7, '455', 'aivwxyz4', oils_i18n_gettext('67','See Also Tracing -- Genre/Form Term','acsaf','name')),
3052     (68, 1, 8, '480', 'ivwxyz4', oils_i18n_gettext('68','See Also Tracing -- General Subdivision','acsaf','name')),
3053     (69, 1, 9, '481', 'ivwxyz4', oils_i18n_gettext('69','See Also Tracing -- Geographic Subdivision','acsaf','name')),
3054     (70, 1, 10, '482', 'ivwxyz4', oils_i18n_gettext('70','See Also Tracing -- Chronological Subdivision','acsaf','name')),
3055     (71, 1, 11, '485', 'ivwxyz4', oils_i18n_gettext('71','See Also Tracing -- Form Subdivision','acsaf','name')),
3056     (72, 1, 12, '448', 'aivwxyz4', oils_i18n_gettext('72','See Also Tracing -- Chronological Term','acsaf','name'));
3057
3058 INSERT INTO authority.browse_axis (code,name,description,sorter) VALUES
3059     ('title','Title','Title axis','titlesort'),
3060     ('author','Author','Author axis','titlesort'),
3061     ('subject','Subject','Subject axis','titlesort'),
3062     ('topic','Topic','Topic Subject axis','titlesort');
3063
3064 INSERT INTO authority.browse_axis_authority_field_map (axis,field) VALUES
3065     ('author',  1 ),
3066     ('author',  2 ),
3067     ('author',  3 ),
3068     ('title',   4 ),
3069     ('topic',   5 ),
3070     ('subject', 5 ),
3071     ('subject', 6 ),
3072     ('subject', 7 ),
3073     ('subject', 12);
3074
3075 INSERT INTO authority.control_set_bib_field (tag, authority_field) 
3076     SELECT '100', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3077         UNION
3078     SELECT '600', id FROM authority.control_set_authority_field WHERE tag IN ('100','180','181','182','185')
3079         UNION
3080     SELECT '700', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3081         UNION
3082     SELECT '800', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3083         UNION
3084
3085     SELECT '110', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3086         UNION
3087     SELECT '610', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3088         UNION
3089     SELECT '710', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3090         UNION
3091     SELECT '810', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3092         UNION
3093
3094     SELECT '111', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3095         UNION
3096     SELECT '611', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3097         UNION
3098     SELECT '711', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3099         UNION
3100     SELECT '811', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3101         UNION
3102
3103     SELECT '130', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3104         UNION
3105     SELECT '240', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3106         UNION
3107     SELECT '630', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3108         UNION
3109     SELECT '730', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3110         UNION
3111     SELECT '830', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3112         UNION
3113
3114     SELECT '648', id FROM authority.control_set_authority_field WHERE tag IN ('148')
3115         UNION
3116
3117     SELECT '650', id FROM authority.control_set_authority_field WHERE tag IN ('150','180','181','182','185')
3118         UNION
3119     SELECT '651', id FROM authority.control_set_authority_field WHERE tag IN ('151','180','181','182','185')
3120         UNION
3121     SELECT '655', id FROM authority.control_set_authority_field WHERE tag IN ('155','180','181','182','185')
3122 ;
3123
3124 INSERT INTO authority.thesaurus (code, name, control_set) VALUES
3125     ('a', oils_i18n_gettext('a','Library of Congress Subject Headings','at','name'), 1),
3126     ('b', oils_i18n_gettext('b',$$LC subject headings for children's literature$$,'at','name'), 1), -- silly vim '
3127     ('c', oils_i18n_gettext('c','Medical Subject Headings','at','name'), 1),
3128     ('d', oils_i18n_gettext('d','National Agricultural Library subject authority file','at','name'), 1),
3129     ('k', oils_i18n_gettext('k','Canadian Subject Headings','at','name'), 1),
3130     ('n', oils_i18n_gettext('n','Not applicable','at','name'), 1),
3131     ('r', oils_i18n_gettext('r','Art and Architecture Thesaurus','at','name'), 1),
3132     ('s', oils_i18n_gettext('s','Sears List of Subject Headings','at','name'), 1),
3133     ('v', oils_i18n_gettext('v','Repertoire de vedettes-matiere','at','name'), 1),
3134     ('z', oils_i18n_gettext('z','Other','at','name'), 1),
3135     ('|', oils_i18n_gettext('|','No attempt to code','at','name'), 1);
3136  
3137 CREATE OR REPLACE FUNCTION authority.map_thesaurus_to_control_set () RETURNS TRIGGER AS $func$
3138 BEGIN
3139     IF NEW.control_set IS NULL THEN
3140         SELECT  control_set INTO NEW.control_set
3141           FROM  authority.thesaurus
3142           WHERE vandelay.marc21_extract_fixed_field(NEW.marc,'Subj') = code;
3143     END IF;
3144
3145     RETURN NEW;
3146 END;
3147 $func$ LANGUAGE PLPGSQL;
3148
3149 CREATE TRIGGER map_thesaurus_to_control_set BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE authority.map_thesaurus_to_control_set ();
3150
3151 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
3152 BEGIN
3153     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
3154     INSERT INTO authority.rec_descriptor (record, record_status, encoding_level, thesaurus)
3155         SELECT  auth_id,
3156                 vandelay.marc21_extract_fixed_field(marc,'RecStat'),
3157                 vandelay.marc21_extract_fixed_field(marc,'ELvl'),
3158                 vandelay.marc21_extract_fixed_field(marc,'Subj')
3159           FROM  authority.record_entry
3160           WHERE id = auth_id;
3161      RETURN;
3162  END;
3163  $func$ LANGUAGE PLPGSQL;
3164
3165 --Removed dupe authority.indexing_ingest_or_delete
3166
3167 -- Evergreen DB patch 0577.schema.vandelay-item-import-copy-loc-ancestors.sql
3168 --
3169 -- Ingest items copy location inheritance
3170 --
3171
3172 -- check whether patch can be applied
3173 SELECT evergreen.upgrade_deps_block_check('0577', :eg_version); -- berick
3174
3175 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3176 DECLARE
3177
3178     owning_lib      TEXT;
3179     circ_lib        TEXT;
3180     call_number     TEXT;
3181     copy_number     TEXT;
3182     status          TEXT;
3183     location        TEXT;
3184     circulate       TEXT;
3185     deposit         TEXT;
3186     deposit_amount  TEXT;
3187     ref             TEXT;
3188     holdable        TEXT;
3189     price           TEXT;
3190     barcode         TEXT;
3191     circ_modifier   TEXT;
3192     circ_as_type    TEXT;
3193     alert_message   TEXT;
3194     opac_visible    TEXT;
3195     pub_note        TEXT;
3196     priv_note       TEXT;
3197
3198     attr_def        RECORD;
3199     tmp_attr_set    RECORD;
3200     attr_set        vandelay.import_item%ROWTYPE;
3201
3202     xpath           TEXT;
3203
3204 BEGIN
3205
3206     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3207
3208     IF FOUND THEN
3209
3210         attr_set.definition := attr_def.id;
3211
3212         -- Build the combined XPath
3213
3214         owning_lib :=
3215             CASE
3216                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3217                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3218                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3219             END;
3220
3221         circ_lib :=
3222             CASE
3223                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3224                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3225                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3226             END;
3227
3228         call_number :=
3229             CASE
3230                 WHEN attr_def.call_number IS NULL THEN 'null()'
3231                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3232                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3233             END;
3234
3235         copy_number :=
3236             CASE
3237                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3238                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3239                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3240             END;
3241
3242         status :=
3243             CASE
3244                 WHEN attr_def.status IS NULL THEN 'null()'
3245                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3246                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3247             END;
3248
3249         location :=
3250             CASE
3251                 WHEN attr_def.location IS NULL THEN 'null()'
3252                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3253                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3254             END;
3255
3256         circulate :=
3257             CASE
3258                 WHEN attr_def.circulate IS NULL THEN 'null()'
3259                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3260                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3261             END;
3262
3263         deposit :=
3264             CASE
3265                 WHEN attr_def.deposit IS NULL THEN 'null()'
3266                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3267                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3268             END;
3269
3270         deposit_amount :=
3271             CASE
3272                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3273                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3274                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3275             END;
3276
3277         ref :=
3278             CASE
3279                 WHEN attr_def.ref IS NULL THEN 'null()'
3280                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3281                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3282             END;
3283
3284         holdable :=
3285             CASE
3286                 WHEN attr_def.holdable IS NULL THEN 'null()'
3287                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3288                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3289             END;
3290
3291         price :=
3292             CASE
3293                 WHEN attr_def.price IS NULL THEN 'null()'
3294                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3295                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3296             END;
3297
3298         barcode :=
3299             CASE
3300                 WHEN attr_def.barcode IS NULL THEN 'null()'
3301                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3302                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3303             END;
3304
3305         circ_modifier :=
3306             CASE
3307                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3308                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3309                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3310             END;
3311
3312         circ_as_type :=
3313             CASE
3314                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3315                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3316                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3317             END;
3318
3319         alert_message :=
3320             CASE
3321                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3322                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3323                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3324             END;
3325
3326         opac_visible :=
3327             CASE
3328                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3329                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3330                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3331             END;
3332
3333         pub_note :=
3334             CASE
3335                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3336                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3337                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3338             END;
3339         priv_note :=
3340             CASE
3341                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3342                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3343                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3344             END;
3345
3346
3347         xpath :=
3348             owning_lib      || '|' ||
3349             circ_lib        || '|' ||
3350             call_number     || '|' ||
3351             copy_number     || '|' ||
3352             status          || '|' ||
3353             location        || '|' ||
3354             circulate       || '|' ||
3355             deposit         || '|' ||
3356             deposit_amount  || '|' ||
3357             ref             || '|' ||
3358             holdable        || '|' ||
3359             price           || '|' ||
3360             barcode         || '|' ||
3361             circ_modifier   || '|' ||
3362             circ_as_type    || '|' ||
3363             alert_message   || '|' ||
3364             pub_note        || '|' ||
3365             priv_note       || '|' ||
3366             opac_visible;
3367
3368         -- RAISE NOTICE 'XPath: %', xpath;
3369
3370         FOR tmp_attr_set IN
3371                 SELECT  *
3372                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3373                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3374                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3375                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3376         LOOP
3377
3378             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3379             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3380
3381             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
3382             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
3383
3384             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3385             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3386             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3387
3388
3389             -- search up the org unit tree for a matching copy location
3390
3391             WITH RECURSIVE anscestor_depth AS (
3392                 SELECT  ou.id,
3393                     out.depth AS depth,
3394                     ou.parent_ou
3395                 FROM  actor.org_unit ou
3396                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3397                 WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3398                     UNION ALL
3399                 SELECT  ou.id,
3400                     out.depth,
3401                     ou.parent_ou
3402                 FROM  actor.org_unit ou
3403                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3404                     JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3405             ) SELECT  cpl.id INTO attr_set.location
3406                 FROM  anscestor_depth a
3407                     JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3408                 WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3409                 ORDER BY a.depth DESC
3410                 LIMIT 1; 
3411
3412             attr_set.circulate      :=
3413                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3414                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3415
3416             attr_set.deposit        :=
3417                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3418                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3419
3420             attr_set.holdable       :=
3421                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3422                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3423
3424             attr_set.opac_visible   :=
3425                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3426                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
3427
3428             attr_set.ref            :=
3429                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
3430                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
3431
3432             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
3433             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
3434             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
3435
3436             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
3437             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
3438             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
3439             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
3440             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3441             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
3442             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
3443             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3444
3445             RETURN NEXT attr_set;
3446
3447         END LOOP;
3448
3449     END IF;
3450
3451     RETURN;
3452
3453 END;
3454 $$ LANGUAGE PLPGSQL;
3455
3456
3457 -- Evergreen DB patch XXXX.data.org-setting-ui.circ.billing.uncheck_bills_and_unfocus_payment_box.sql
3458 --
3459 -- New org setting ui.circ.billing.uncheck_bills_and_unfocus_payment_box
3460 --
3461
3462 -- check whether patch can be applied
3463 SELECT evergreen.upgrade_deps_block_check('0584', :eg_version);
3464
3465 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3466     VALUES ( 
3467         'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3468         oils_i18n_gettext(
3469             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3470             'GUI: Uncheck bills by default in the patron billing interface',
3471             'coust',
3472             'label'
3473         ),
3474         oils_i18n_gettext(
3475             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3476             'Uncheck bills by default in the patron billing interface,'
3477             || ' and focus on the Uncheck All button instead of the'
3478             || ' Payment Received field.',
3479             'coust',
3480             'description'
3481         ),
3482         'bool'
3483     );
3484
3485
3486 -- check whether patch can be applied
3487 SELECT evergreen.upgrade_deps_block_check('0585', :eg_version);
3488
3489 INSERT into config.org_unit_setting_type
3490 ( name, label, description, datatype ) VALUES
3491 ( 'circ.checkout_fills_related_hold_exact_match_only',
3492     'Checkout Fills Related Hold On Valid Copy Only',
3493     'When filling related holds on checkout only match on items that are valid for opportunistic capture for the hold. Without this set a Title or Volume hold could match when the item is not holdable. With this set only holdable items will match.',
3494     'bool');
3495
3496
3497 -- check whether patch can be applied
3498 SELECT evergreen.upgrade_deps_block_check('0586', :eg_version);
3499
3500 INSERT INTO permission.perm_list (id, code, description) VALUES (
3501     511,
3502     'PERSISTENT_LOGIN',
3503     oils_i18n_gettext(
3504         511,
3505         'Allows a user to authenticate and get a long-lived session (length configured in opensrf.xml)',
3506         'ppl',
3507         'description'
3508     )
3509 );
3510
3511 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
3512     SELECT
3513         pgt.id, perm.id, aout.depth, FALSE
3514     FROM
3515         permission.grp_tree pgt,
3516         permission.perm_list perm,
3517         actor.org_unit_type aout
3518     WHERE
3519         pgt.name = 'Users' AND
3520         aout.name = 'Consortium' AND
3521         perm.code = 'PERSISTENT_LOGIN';
3522
3523 \qecho 
3524 \qecho If this transaction succeeded, your users (staff and patrons) now have
3525 \qecho the PERSISTENT_LOGIN permission by default.
3526 \qecho 
3527
3528
3529 -- Evergreen DB patch XXXX.data.org-setting-circ.offline.skip_foo_if_newer_status_changed_time.sql
3530 --
3531 -- New org setting circ.offline.skip_checkout_if_newer_status_changed_time
3532 -- New org setting circ.offline.skip_renew_if_newer_status_changed_time
3533 -- New org setting circ.offline.skip_checkin_if_newer_status_changed_time
3534 --
3535
3536 -- check whether patch can be applied
3537 SELECT evergreen.upgrade_deps_block_check('0593', :eg_version);
3538
3539 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3540     VALUES ( 
3541         'circ.offline.skip_checkout_if_newer_status_changed_time',
3542         oils_i18n_gettext(
3543             'circ.offline.skip_checkout_if_newer_status_changed_time',
3544             'Offline: Skip offline checkout if newer item Status Changed Time.',
3545             'coust',
3546             'label'
3547         ),
3548         oils_i18n_gettext(
3549             'circ.offline.skip_checkout_if_newer_status_changed_time',
3550             'Skip offline checkout transaction (raise exception when'
3551             || ' processing) if item Status Changed Time is newer than the'
3552             || ' recorded transaction time.  WARNING: The Reshelving to'
3553             || ' Available status rollover will trigger this.',
3554             'coust',
3555             'description'
3556         ),
3557         'bool'
3558     ),( 
3559         'circ.offline.skip_renew_if_newer_status_changed_time',
3560         oils_i18n_gettext(
3561             'circ.offline.skip_renew_if_newer_status_changed_time',
3562             'Offline: Skip offline renewal if newer item Status Changed Time.',
3563             'coust',
3564             'label'
3565         ),
3566         oils_i18n_gettext(
3567             'circ.offline.skip_renew_if_newer_status_changed_time',
3568             'Skip offline renewal transaction (raise exception when'
3569             || ' processing) if item Status Changed Time is newer than the'
3570             || ' recorded transaction time.  WARNING: The Reshelving to'
3571             || ' Available status rollover will trigger this.',
3572             'coust',
3573             'description'
3574         ),
3575         'bool'
3576     ),( 
3577         'circ.offline.skip_checkin_if_newer_status_changed_time',
3578         oils_i18n_gettext(
3579             'circ.offline.skip_checkin_if_newer_status_changed_time',
3580             'Offline: Skip offline checkin if newer item Status Changed Time.',
3581             'coust',
3582             'label'
3583         ),
3584         oils_i18n_gettext(
3585             'circ.offline.skip_checkin_if_newer_status_changed_time',
3586             'Skip offline checkin transaction (raise exception when'
3587             || ' processing) if item Status Changed Time is newer than the'
3588             || ' recorded transaction time.  WARNING: The Reshelving to'
3589             || ' Available status rollover will trigger this.',
3590             'coust',
3591             'description'
3592         ),
3593         'bool'
3594     );
3595
3596 -- Evergreen DB patch YYYY.schema.acp_status_date_changed.sql
3597 --
3598 -- Change trigger which updates copy status_changed_time to ignore the
3599 -- Reshelving->Available status rollover
3600
3601 -- FIXME: 0039.schema.acp_status_date_changed.sql defines this the first time
3602 -- around, but along with the column itself, etc.  And it gets modified with
3603 -- 0562.schema.copy_active_date.sql.  Not sure how to use the supercedes /
3604 -- deprecate stuff for upgrade scripts, if it's even applicable when a given
3605 -- upgrade script is doing so much.
3606
3607 -- check whether patch can be applied
3608 SELECT evergreen.upgrade_deps_block_check('0594', :eg_version);
3609
3610 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
3611 RETURNS TRIGGER AS $$
3612 BEGIN
3613         IF NEW.status <> OLD.status AND NOT (NEW.status = 0 AND OLD.status = 7) THEN
3614         NEW.status_changed_time := now();
3615         IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
3616             NEW.active_date := now();
3617         END IF;
3618     END IF;
3619     RETURN NEW;
3620 END;
3621 $$ LANGUAGE plpgsql;
3622
3623 -- Evergreen DB patch 0595.data.org-setting-ui.patron_search.result_cap.sql
3624 --
3625 -- New org setting ui.patron_search.result_cap
3626 --
3627
3628 -- check whether patch can be applied
3629 SELECT evergreen.upgrade_deps_block_check('0595', :eg_version);
3630
3631 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
3632     VALUES (
3633         'ui.patron_search.result_cap',
3634         oils_i18n_gettext(
3635             'ui.patron_search.result_cap',
3636             'GUI: Cap results in Patron Search at this number.',
3637             'coust',
3638             'label'
3639         ),
3640         oils_i18n_gettext(
3641             'ui.patron_search.result_cap',
3642             'So for example, if you search for John Doe, normally you would get'
3643             || ' at most 50 results.  This setting allows you to raise or lower'
3644             || ' that limit.',
3645             'coust',
3646             'description'
3647         ),
3648         'integer'
3649     );
3650
3651 -- Evergreen DB patch 0596.schema.vandelay-item-import-error-detail.sql
3652
3653 -- check whether patch can be applied
3654 SELECT evergreen.upgrade_deps_block_check('0596', :eg_version);
3655
3656 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3657     'import.item.invalid.status', oils_i18n_gettext('import.item.invalid.status', 'Invalid value for "status"', 'vie', 'description') );
3658 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3659     'import.item.invalid.price', oils_i18n_gettext('import.item.invalid.price', 'Invalid value for "price"', 'vie', 'description') );
3660 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3661     'import.item.invalid.deposit_amount', oils_i18n_gettext('import.item.invalid.deposit_amount', 'Invalid value for "deposit_amount"', 'vie', 'description') );
3662 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3663     'import.item.invalid.owning_lib', oils_i18n_gettext('import.item.invalid.owning_lib', 'Invalid value for "owning_lib"', 'vie', 'description') );
3664 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3665     'import.item.invalid.circ_lib', oils_i18n_gettext('import.item.invalid.circ_lib', 'Invalid value for "circ_lib"', 'vie', 'description') );
3666 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3667     'import.item.invalid.copy_number', oils_i18n_gettext('import.item.invalid.copy_number', 'Invalid value for "copy_number"', 'vie', 'description') );
3668 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3669     'import.item.invalid.circ_as_type', oils_i18n_gettext('import.item.invalid.circ_as_type', 'Invalid value for "circ_as_type"', 'vie', 'description') );
3670
3671 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3672 DECLARE
3673
3674     owning_lib      TEXT;
3675     circ_lib        TEXT;
3676     call_number     TEXT;
3677     copy_number     TEXT;
3678     status          TEXT;
3679     location        TEXT;
3680     circulate       TEXT;
3681     deposit         TEXT;
3682     deposit_amount  TEXT;
3683     ref             TEXT;
3684     holdable        TEXT;
3685     price           TEXT;
3686     barcode         TEXT;
3687     circ_modifier   TEXT;
3688     circ_as_type    TEXT;
3689     alert_message   TEXT;
3690     opac_visible    TEXT;
3691     pub_note        TEXT;
3692     priv_note       TEXT;
3693
3694     attr_def        RECORD;
3695     tmp_attr_set    RECORD;
3696     attr_set        vandelay.import_item%ROWTYPE;
3697
3698     xpath           TEXT;
3699     tmp_str         TEXT;
3700
3701 BEGIN
3702
3703     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3704
3705     IF FOUND THEN
3706
3707         attr_set.definition := attr_def.id;
3708
3709         -- Build the combined XPath
3710
3711         owning_lib :=
3712             CASE
3713                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3714                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3715                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3716             END;
3717
3718         circ_lib :=
3719             CASE
3720                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3721                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3722                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3723             END;
3724
3725         call_number :=
3726             CASE
3727                 WHEN attr_def.call_number IS NULL THEN 'null()'
3728                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3729                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3730             END;
3731
3732         copy_number :=
3733             CASE
3734                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3735                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3736                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3737             END;
3738
3739         status :=
3740             CASE
3741                 WHEN attr_def.status IS NULL THEN 'null()'
3742                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3743                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3744             END;
3745
3746         location :=
3747             CASE
3748                 WHEN attr_def.location IS NULL THEN 'null()'
3749                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3750                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3751             END;
3752
3753         circulate :=
3754             CASE
3755                 WHEN attr_def.circulate IS NULL THEN 'null()'
3756                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3757                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3758             END;
3759
3760         deposit :=
3761             CASE
3762                 WHEN attr_def.deposit IS NULL THEN 'null()'
3763                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3764                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3765             END;
3766
3767         deposit_amount :=
3768             CASE
3769                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3770                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3771                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3772             END;
3773
3774         ref :=
3775             CASE
3776                 WHEN attr_def.ref IS NULL THEN 'null()'
3777                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3778                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3779             END;
3780
3781         holdable :=
3782             CASE
3783                 WHEN attr_def.holdable IS NULL THEN 'null()'
3784                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3785                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3786             END;
3787
3788         price :=
3789             CASE
3790                 WHEN attr_def.price IS NULL THEN 'null()'
3791                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3792                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3793             END;
3794
3795         barcode :=
3796             CASE
3797                 WHEN attr_def.barcode IS NULL THEN 'null()'
3798                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3799                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3800             END;
3801
3802         circ_modifier :=
3803             CASE
3804                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3805                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3806                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3807             END;
3808
3809         circ_as_type :=
3810             CASE
3811                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3812                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3813                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3814             END;
3815
3816         alert_message :=
3817             CASE
3818                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3819                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3820                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3821             END;
3822
3823         opac_visible :=
3824             CASE
3825                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3826                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3827                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3828             END;
3829
3830         pub_note :=
3831             CASE
3832                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3833                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3834                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3835             END;
3836         priv_note :=
3837             CASE
3838                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3839                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3840                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3841             END;
3842
3843
3844         xpath :=
3845             owning_lib      || '|' ||
3846             circ_lib        || '|' ||
3847             call_number     || '|' ||
3848             copy_number     || '|' ||
3849             status          || '|' ||
3850             location        || '|' ||
3851             circulate       || '|' ||
3852             deposit         || '|' ||
3853             deposit_amount  || '|' ||
3854             ref             || '|' ||
3855             holdable        || '|' ||
3856             price           || '|' ||
3857             barcode         || '|' ||
3858             circ_modifier   || '|' ||
3859             circ_as_type    || '|' ||
3860             alert_message   || '|' ||
3861             pub_note        || '|' ||
3862             priv_note       || '|' ||
3863             opac_visible;
3864
3865         FOR tmp_attr_set IN
3866                 SELECT  *
3867                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3868                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3869                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3870                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3871         LOOP
3872
3873             attr_set.import_error := NULL;
3874             attr_set.error_detail := NULL;
3875             attr_set.deposit_amount := NULL;
3876             attr_set.copy_number := NULL;
3877             attr_set.price := NULL;
3878
3879             IF tmp_attr_set.pr != '' THEN
3880                 tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3881                 IF tmp_str = '' THEN 
3882                     attr_set.import_error := 'import.item.invalid.price';
3883                     attr_set.error_detail := tmp_attr_set.pr; -- original value
3884                     RETURN NEXT attr_set; CONTINUE; 
3885                 END IF;
3886                 attr_set.price := tmp_str::NUMERIC(8,2); 
3887             END IF;
3888
3889             IF tmp_attr_set.dep_amount != '' THEN
3890                 tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3891                 IF tmp_str = '' THEN 
3892                     attr_set.import_error := 'import.item.invalid.deposit_amount';
3893                     attr_set.error_detail := tmp_attr_set.dep_amount; 
3894                     RETURN NEXT attr_set; CONTINUE; 
3895                 END IF;
3896                 attr_set.deposit_amount := tmp_str::NUMERIC(8,2); 
3897             END IF;
3898
3899             IF tmp_attr_set.cnum != '' THEN
3900                 tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
3901                 IF tmp_str = '' THEN 
3902                     attr_set.import_error := 'import.item.invalid.copy_number';
3903                     attr_set.error_detail := tmp_attr_set.cnum; 
3904                     RETURN NEXT attr_set; CONTINUE; 
3905                 END IF;
3906                 attr_set.copy_number := tmp_str::INT; 
3907             END IF;
3908
3909             IF tmp_attr_set.ol != '' THEN
3910                 SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3911                 IF NOT FOUND THEN
3912                     attr_set.import_error := 'import.item.invalid.owning_lib';
3913                     attr_set.error_detail := tmp_attr_set.ol;
3914                     RETURN NEXT attr_set; CONTINUE; 
3915                 END IF;
3916             END IF;
3917
3918             IF tmp_attr_set.clib != '' THEN
3919                 SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3920                 IF NOT FOUND THEN
3921                     attr_set.import_error := 'import.item.invalid.circ_lib';
3922                     attr_set.error_detail := tmp_attr_set.clib;
3923                     RETURN NEXT attr_set; CONTINUE; 
3924                 END IF;
3925             END IF;
3926
3927             IF tmp_attr_set.cs != '' THEN
3928                 SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3929                 IF NOT FOUND THEN
3930                     attr_set.import_error := 'import.item.invalid.status';
3931                     attr_set.error_detail := tmp_attr_set.cs;
3932                     RETURN NEXT attr_set; CONTINUE; 
3933                 END IF;
3934             END IF;
3935
3936             IF tmp_attr_set.circ_mod != '' THEN
3937                 SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
3938                 IF NOT FOUND THEN
3939                     attr_set.import_error := 'import.item.invalid.circ_modifier';
3940                     attr_set.error_detail := tmp_attr_set.circ_mod;
3941                     RETURN NEXT attr_set; CONTINUE; 
3942                 END IF;
3943             END IF;
3944
3945             IF tmp_attr_set.circ_as != '' THEN
3946                 SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
3947                 IF NOT FOUND THEN
3948                     attr_set.import_error := 'import.item.invalid.circ_as_type';
3949                     attr_set.error_detail := tmp_attr_set.circ_as;
3950                     RETURN NEXT attr_set; CONTINUE; 
3951                 END IF;
3952             END IF;
3953
3954             IF tmp_attr_set.cl != '' THEN
3955
3956                 -- search up the org unit tree for a matching copy location
3957                 WITH RECURSIVE anscestor_depth AS (
3958                     SELECT  ou.id,
3959                         out.depth AS depth,
3960                         ou.parent_ou
3961                     FROM  actor.org_unit ou
3962                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3963                     WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3964                         UNION ALL
3965                     SELECT  ou.id,
3966                         out.depth,
3967                         ou.parent_ou
3968                     FROM  actor.org_unit ou
3969                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3970                         JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3971                 ) SELECT  cpl.id INTO attr_set.location
3972                     FROM  anscestor_depth a
3973                         JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3974                     WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3975                     ORDER BY a.depth DESC
3976                     LIMIT 1; 
3977
3978                 IF NOT FOUND THEN
3979                     attr_set.import_error := 'import.item.invalid.location';
3980                     attr_set.error_detail := tmp_attr_set.cs;
3981                     RETURN NEXT attr_set; CONTINUE; 
3982                 END IF;
3983             END IF;
3984
3985             attr_set.circulate      :=
3986                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3987                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3988
3989             attr_set.deposit        :=
3990                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3991                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3992
3993             attr_set.holdable       :=
3994                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3995                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3996
3997             attr_set.opac_visible   :=
3998                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3999                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
4000
4001             attr_set.ref            :=
4002                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
4003                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
4004
4005             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
4006             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
4007             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4008             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
4009             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
4010             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4011
4012             RETURN NEXT attr_set;
4013
4014         END LOOP;
4015
4016     END IF;
4017
4018     RETURN;
4019
4020 END;
4021 $$ LANGUAGE PLPGSQL;
4022
4023 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
4024 DECLARE
4025     attr_def    BIGINT;
4026     item_data   vandelay.import_item%ROWTYPE;
4027 BEGIN
4028
4029     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
4030         RETURN NEW;
4031     END IF;
4032
4033     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
4034
4035     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
4036         INSERT INTO vandelay.import_item (
4037             record,
4038             definition,
4039             owning_lib,
4040             circ_lib,
4041             call_number,
4042             copy_number,
4043             status,
4044             location,
4045             circulate,
4046             deposit,
4047             deposit_amount,
4048             ref,
4049             holdable,
4050             price,
4051             barcode,
4052             circ_modifier,
4053             circ_as_type,
4054             alert_message,
4055             pub_note,
4056             priv_note,
4057             opac_visible,
4058             import_error,
4059             error_detail
4060         ) VALUES (
4061             NEW.id,
4062             item_data.definition,
4063             item_data.owning_lib,
4064             item_data.circ_lib,
4065             item_data.call_number,
4066             item_data.copy_number,
4067             item_data.status,
4068             item_data.location,
4069             item_data.circulate,
4070             item_data.deposit,
4071             item_data.deposit_amount,
4072             item_data.ref,
4073             item_data.holdable,
4074             item_data.price,
4075             item_data.barcode,
4076             item_data.circ_modifier,
4077             item_data.circ_as_type,
4078             item_data.alert_message,
4079             item_data.pub_note,
4080             item_data.priv_note,
4081             item_data.opac_visible,
4082             item_data.import_error,
4083             item_data.error_detail
4084         );
4085     END LOOP;
4086
4087     RETURN NULL;
4088 END;
4089 $func$ LANGUAGE PLPGSQL;
4090
4091 -- Evergreen DB patch XXXX.schema.vandelay.bib_match_isxn_caseless.sql
4092
4093
4094 -- check whether patch can be applied
4095 SELECT evergreen.upgrade_deps_block_check('0597', :eg_version);
4096
4097 CREATE INDEX metabib_full_rec_isxn_caseless_idx
4098     ON metabib.real_full_rec (LOWER(value))
4099     WHERE tag IN ('020', '022', '024');
4100
4101
4102 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4103     record_xml TEXT
4104 ) RETURNS HSTORE AS $$
4105 BEGIN
4106     RETURN (SELECT
4107         HSTORE(
4108             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4109             ARRAY_ACCUM(value)
4110         )
4111         FROM (
4112             SELECT
4113                 tag, subfield,
4114                 CASE WHEN tag IN ('020', '022', '024') THEN  -- caseless
4115                     ARRAY_ACCUM(LOWER(value))::TEXT
4116                 ELSE
4117                     ARRAY_ACCUM(value)::TEXT
4118                 END AS value
4119                 FROM vandelay.flatten_marc(record_xml)
4120                 GROUP BY tag, subfield ORDER BY tag, subfield
4121         ) subquery
4122     );
4123 END;
4124 $$ LANGUAGE PLPGSQL;
4125
4126 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4127     node vandelay.match_set_point
4128 ) RETURNS VOID AS $$
4129 DECLARE
4130     jrow        TEXT;
4131     my_alias    TEXT;
4132     op          TEXT;
4133     tagkey      TEXT;
4134     caseless    BOOL;
4135 BEGIN
4136     -- remember $1 is tags_rstore, and $2 is svf_rstore
4137
4138     IF node.negate THEN
4139         op := '<>';
4140     ELSE
4141         op := '=';
4142     END IF;
4143
4144     caseless := FALSE;
4145
4146     IF node.tag IS NOT NULL THEN
4147         caseless := (node.tag IN ('020', '022', '024'));
4148         tagkey := node.tag;
4149         IF node.subfield IS NOT NULL THEN
4150             tagkey := tagkey || node.subfield;
4151         END IF;
4152     END IF;
4153
4154     my_alias := 'n' || node.id::TEXT;
4155
4156     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4157         ' AS quality FROM metabib.';
4158     IF node.tag IS NOT NULL THEN
4159         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4160             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4161             node.tag || '''';
4162         IF node.subfield IS NOT NULL THEN
4163             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4164                 node.subfield || '''';
4165         END IF;
4166         jrow := jrow || ' AND (';
4167
4168         IF caseless THEN
4169             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4170         ELSE
4171             jrow := jrow || my_alias || '.value ' || op;
4172         END IF;
4173
4174         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4175     ELSE    -- svf
4176         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4177             my_alias || '.id = bre.id AND (' ||
4178             my_alias || '.attrs->''' || node.svf ||
4179             ''' ' || op || ' $2->''' || node.svf || '''))';
4180     END IF;
4181     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4182 END;
4183 $$ LANGUAGE PLPGSQL;
4184
4185 -- Evergreen DB patch 0598.schema.vandelay_one_match_per.sql
4186 --
4187
4188
4189 -- check whether patch can be applied
4190 SELECT evergreen.upgrade_deps_block_check('0598', :eg_version);
4191
4192 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
4193     match_set_id INTEGER, record_xml TEXT
4194 ) RETURNS SETOF vandelay.match_set_test_result AS $$
4195 DECLARE
4196     tags_rstore HSTORE;
4197     svf_rstore  HSTORE;
4198     coal        TEXT;
4199     joins       TEXT;
4200     query_      TEXT;
4201     wq          TEXT;
4202     qvalue      INTEGER;
4203     rec         RECORD;
4204 BEGIN
4205     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
4206     svf_rstore := vandelay.extract_rec_attrs(record_xml);
4207
4208     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
4209     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
4210
4211     -- generate the where clause and return that directly (into wq), and as
4212     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
4213     wq := vandelay.get_expr_from_match_set(match_set_id);
4214
4215     query_ := 'SELECT DISTINCT(bre.id) AS record, ';
4216
4217     -- qrows table is for the quality bits we add to the SELECT clause
4218     SELECT ARRAY_TO_STRING(
4219         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
4220     ) INTO coal FROM _vandelay_tmp_qrows;
4221
4222     -- our query string so far is the SELECT clause and the inital FROM.
4223     -- no JOINs yet nor the WHERE clause
4224     query_ := query_ || coal || ' AS quality ' || E'\n' ||
4225         'FROM biblio.record_entry bre ';
4226
4227     -- jrows table is for the joins we must make (and the real text conditions)
4228     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
4229         FROM _vandelay_tmp_jrows;
4230
4231     -- add those joins and the where clause to our query.
4232     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
4233
4234     -- this will return rows of record,quality
4235     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
4236         RETURN NEXT rec;
4237     END LOOP;
4238
4239     DROP TABLE _vandelay_tmp_qrows;
4240     DROP TABLE _vandelay_tmp_jrows;
4241     RETURN;
4242 END;
4243
4244 $$ LANGUAGE PLPGSQL;
4245
4246 -- Evergreen DB patch 0606.schema.czs_use_perm_column.sql
4247 --
4248 -- This adds a column to config.z3950_source called use_perm.
4249 -- The idea is that if a permission is set for a given source,
4250 -- then staff will need the referenced permission to use that
4251 -- source.
4252 --
4253
4254 -- check whether patch can be applied
4255 SELECT evergreen.upgrade_deps_block_check('0606', :eg_version);
4256
4257 ALTER TABLE config.z3950_source 
4258     ADD COLUMN use_perm INT REFERENCES permission.perm_list (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
4259
4260 COMMENT ON COLUMN config.z3950_source.use_perm IS $$
4261 If set, this permission is required for the source to be listed in the staff
4262 client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
4263 $$;
4264
4265 -- Evergreen DB patch 0608.data.vandelay-export-error-match-info.sql
4266 --
4267 --
4268
4269
4270 -- check whether patch can be applied
4271 SELECT evergreen.upgrade_deps_block_check('0608', :eg_version);
4272
4273 -- Add vqbr.import_error, vqbr.error_detail, and vqbr.matches.size to queue print output
4274
4275 UPDATE action_trigger.event_definition SET template = $$
4276 [%- USE date -%]
4277 <pre>
4278 Queue ID: [% target.0.queue.id %]
4279 Queue Name: [% target.0.queue.name %]
4280 Queue Type: [% target.0.queue.queue_type %]
4281 Complete? [% target.0.queue.complete %]
4282
4283     [% FOR vqbr IN target %]
4284 =-=-=
4285  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
4286  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
4287  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
4288  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
4289  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
4290  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
4291  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
4292  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
4293  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
4294  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
4295  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
4296  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
4297  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
4298  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
4299  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
4300  Import Error     | [% vqbr.import_error %]
4301  Error Detail     | [% vqbr.error_detail %]
4302  Match Count      | [% vqbr.matches.size %]
4303
4304     [% END %]
4305 </pre>
4306 $$
4307 WHERE id = 39;
4308
4309
4310 -- Do the same for the CVS version
4311
4312 UPDATE action_trigger.event_definition SET template = $$
4313 [%- USE date -%]
4314 "Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode","Import Error","Error Detail","Match Count"
4315 [% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]","[% vqbr.import_error | replace('"', '""') %]","[% vqbr.error_detail | replace('"', '""') %]","[% vqbr.matches.size %]"
4316 [% END %]
4317 $$
4318 WHERE id = 40;
4319
4320 -- Add matches to the env for both
4321 INSERT INTO action_trigger.environment (event_def, path) VALUES (39, 'matches');
4322 INSERT INTO action_trigger.environment (event_def, path) VALUES (40, 'matches');
4323
4324
4325 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4326
4327 -- check whether patch can be applied
4328 SELECT evergreen.upgrade_deps_block_check('0609', :eg_version);
4329
4330 ALTER TABLE acq.lineitem_detail 
4331     ADD COLUMN receiver INT REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
4332
4333
4334 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4335
4336 -- check whether patch can be applied
4337 SELECT evergreen.upgrade_deps_block_check('0610', :eg_version);
4338
4339 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
4340     'acq.copy_creator_uses_receiver',
4341     oils_i18n_gettext( 
4342         'acq.copy_creator_uses_receiver',
4343         'Acq: Set copy creator as receiver',
4344         'coust',
4345         'label'
4346     ),
4347     oils_i18n_gettext( 
4348         'acq.copy_creator_uses_receiver',
4349         'When receiving a copy in acquisitions, set the copy "creator" to be the staff that received the copy',
4350         'coust',
4351         'label'
4352     ),
4353     'bool'
4354 );
4355
4356 -- Evergreen DB patch 0611.data.magic_macros.sql
4357
4358 -- check whether patch can be applied
4359 SELECT evergreen.upgrade_deps_block_check('0611', :eg_version);
4360
4361 INSERT into config.org_unit_setting_type
4362 ( name, label, description, datatype ) VALUES
4363 (
4364         'circ.staff_client.receipt.header_text',
4365         oils_i18n_gettext(
4366             'circ.staff_client.receipt.header_text',
4367             'Receipt Template: Content of header_text include',
4368             'coust',
4369             'label'
4370         ),
4371         oils_i18n_gettext(
4372             'circ.staff_client.receipt.header_text',
4373             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(header_text)%',
4374             'coust',
4375             'description'
4376         ),
4377         'string'
4378     )
4379 ,(
4380         'circ.staff_client.receipt.footer_text',
4381         oils_i18n_gettext(
4382             'circ.staff_client.receipt.footer_text',
4383             'Receipt Template: Content of footer_text include',
4384             'coust',
4385             'label'
4386         ),
4387         oils_i18n_gettext(
4388             'circ.staff_client.receipt.footer_text',
4389             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(footer_text)%',
4390             'coust',
4391             'description'
4392         ),
4393         'string'
4394     )
4395 ,(
4396         'circ.staff_client.receipt.notice_text',
4397         oils_i18n_gettext(
4398             'circ.staff_client.receipt.notice_text',
4399             'Receipt Template: Content of notice_text include',
4400             'coust',
4401             'label'
4402         ),
4403         oils_i18n_gettext(
4404             'circ.staff_client.receipt.notice_text',
4405             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(notice_text)%',
4406             'coust',
4407             'description'
4408         ),
4409         'string'
4410     )
4411 ,(
4412         'circ.staff_client.receipt.alert_text',
4413         oils_i18n_gettext(
4414             'circ.staff_client.receipt.alert_text',
4415             'Receipt Template: Content of alert_text include',
4416             'coust',
4417             'label'
4418         ),
4419         oils_i18n_gettext(
4420             'circ.staff_client.receipt.alert_text',
4421             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(alert_text)%',
4422             'coust',
4423             'description'
4424         ),
4425         'string'
4426     )
4427 ,(
4428         'circ.staff_client.receipt.event_text',
4429         oils_i18n_gettext(
4430             'circ.staff_client.receipt.event_text',
4431             'Receipt Template: Content of event_text include',
4432             'coust',
4433             'label'
4434         ),
4435         oils_i18n_gettext(
4436             'circ.staff_client.receipt.event_text',
4437             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(event_text)%',
4438             'coust',
4439             'description'
4440         ),
4441         'string'
4442     );
4443
4444 -- Evergreen DB patch 0612.schema.authority_overlay_protection.sql
4445 --
4446
4447
4448 -- check whether patch can be applied
4449 SELECT evergreen.upgrade_deps_block_check('0612', :eg_version);
4450
4451 -- FIXME: add/check SQL statements to perform the upgrade
4452
4453 -- Function to generate an ephemeral overlay template from an authority record
4454 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
4455 DECLARE
4456     cset                INT;
4457     main_entry          authority.control_set_authority_field%ROWTYPE;
4458     bib_field           authority.control_set_bib_field%ROWTYPE;
4459     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
4460     replace_data        XML[] DEFAULT '{}'::XML[];
4461     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
4462     auth_field          XML[];
4463 BEGIN
4464     IF auth_id IS NULL THEN
4465         RETURN NULL;
4466     END IF;
4467
4468     -- Default to the LoC controll set
4469     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
4470
4471     -- if none, make a best guess
4472     IF cset IS NULL THEN
4473         SELECT  control_set INTO cset
4474           FROM  authority.control_set_authority_field
4475           WHERE tag IN (
4476                     SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marc::XML)::TEXT[])
4477                       FROM  authority.record_entry
4478                       WHERE id = auth_id
4479                 )
4480           LIMIT 1;
4481     END IF;
4482
4483     -- if STILL none, no-op change
4484     IF cset IS NULL THEN
4485         RETURN XMLELEMENT(
4486             name record,
4487             XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4488             XMLELEMENT( name leader, '00881nam a2200193   4500'),
4489             XMLELEMENT(
4490                 name datafield,
4491                 XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4492                 XMLELEMENT(
4493                     name subfield,
4494                     XMLATTRIBUTES('d' AS code),
4495                     '901c'
4496                 )
4497             )
4498         )::TEXT;
4499     END IF;
4500
4501     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
4502         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
4503         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
4504             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
4505                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
4506                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
4507             END LOOP;
4508             EXIT;
4509         END IF;
4510     END LOOP;
4511
4512     RETURN XMLELEMENT(
4513         name record,
4514         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4515         XMLELEMENT( name leader, '00881nam a2200193   4500'),
4516         replace_data,
4517         XMLELEMENT(
4518             name datafield,
4519             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4520             XMLELEMENT(
4521                 name subfield,
4522                 XMLATTRIBUTES('r' AS code),
4523                 ARRAY_TO_STRING(replace_rules,',')
4524             )
4525         )
4526     )::TEXT;
4527 END;
4528 $f$ STABLE LANGUAGE PLPGSQL;
4529
4530
4531
4532 -- Evergreen DB patch 0613.schema.vandelay_isxn_normalization.sql
4533 --
4534
4535
4536 -- check whether patch can be applied
4537 SELECT evergreen.upgrade_deps_block_check('0613', :eg_version);
4538
4539 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4540     record_xml TEXT
4541 ) RETURNS HSTORE AS $func$
4542 BEGIN
4543     RETURN (SELECT
4544         HSTORE(
4545             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4546             ARRAY_ACCUM(value)
4547         )
4548         FROM (
4549             SELECT  tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
4550               FROM  (SELECT tag,
4551                             subfield,
4552                             CASE WHEN tag = '020' THEN -- caseless -- isbn
4553                                 LOWER((REGEXP_MATCHES(value,$$^(\S{10,17})$$))[1] || '%')
4554                             WHEN tag = '022' THEN -- caseless -- issn
4555                                 LOWER((REGEXP_MATCHES(value,$$^(\S{4}[- ]?\S{4})$$))[1] || '%')
4556                             WHEN tag = '024' THEN -- caseless -- upc (other)
4557                                 LOWER(value || '%')
4558                             ELSE
4559                                 value
4560                             END AS value
4561                       FROM  vandelay.flatten_marc(record_xml)) x
4562                 GROUP BY tag, subfield ORDER BY tag, subfield
4563         ) subquery
4564     );
4565 END;
4566 $func$ LANGUAGE PLPGSQL;
4567
4568 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4569     node vandelay.match_set_point
4570 ) RETURNS VOID AS $$
4571 DECLARE
4572     jrow        TEXT;
4573     my_alias    TEXT;
4574     op          TEXT;
4575     tagkey      TEXT;
4576     caseless    BOOL;
4577 BEGIN
4578     -- remember $1 is tags_rstore, and $2 is svf_rstore
4579
4580     caseless := FALSE;
4581
4582     IF node.tag IS NOT NULL THEN
4583         caseless := (node.tag IN ('020', '022', '024'));
4584         tagkey := node.tag;
4585         IF node.subfield IS NOT NULL THEN
4586             tagkey := tagkey || node.subfield;
4587         END IF;
4588     END IF;
4589
4590     IF node.negate THEN
4591         IF caseless THEN
4592             op := 'NOT LIKE';
4593         ELSE
4594             op := '<>';
4595         END IF;
4596     ELSE
4597         IF caseless THEN
4598             op := 'LIKE';
4599         ELSE
4600             op := '=';
4601         END IF;
4602     END IF;
4603
4604     my_alias := 'n' || node.id::TEXT;
4605
4606     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4607         ' AS quality FROM metabib.';
4608     IF node.tag IS NOT NULL THEN
4609         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4610             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4611             node.tag || '''';
4612         IF node.subfield IS NOT NULL THEN
4613             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4614                 node.subfield || '''';
4615         END IF;
4616         jrow := jrow || ' AND (';
4617
4618         IF caseless THEN
4619             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4620         ELSE
4621             jrow := jrow || my_alias || '.value ' || op;
4622         END IF;
4623
4624         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4625     ELSE    -- svf
4626         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4627             my_alias || '.id = bre.id AND (' ||
4628             my_alias || '.attrs->''' || node.svf ||
4629             ''' ' || op || ' $2->''' || node.svf || '''))';
4630     END IF;
4631     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4632 END;
4633 $$ LANGUAGE PLPGSQL;
4634
4635
4636
4637 -- Evergreen DB patch XXXX.schema.generic-mapping-index-normalizer.sql
4638 --
4639
4640 -- check whether patch can be applied
4641 SELECT evergreen.upgrade_deps_block_check('0615', :eg_version);
4642
4643 -- evergreen.generic_map_normalizer 
4644
4645 CREATE OR REPLACE FUNCTION evergreen.generic_map_normalizer ( TEXT, TEXT ) RETURNS TEXT AS $f$
4646 my $string = shift;
4647 my %map;
4648
4649 my $default = $string;
4650
4651 $_ = shift;
4652 while (/^\s*?(.*?)\s*?=>\s*?(\S+)\s*/) {
4653     if ($1 eq '') {
4654         $default = $2;
4655     } else {
4656         $map{$2} = [split(/\s*,\s*/, $1)];
4657     }
4658     $_ = $';
4659 }
4660
4661 for my $key ( keys %map ) {
4662     return $key if (grep { $_ eq $string } @{ $map{$key} });
4663 }
4664
4665 return $default;
4666
4667 $f$ LANGUAGE PLPERLU;
4668
4669 -- evergreen.generic_map_normalizer 
4670
4671 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
4672     'Generic Mapping Normalizer', 
4673     'Map values or sets of values to new values',
4674     'generic_map_normalizer', 
4675     1
4676 );
4677
4678
4679 SELECT evergreen.upgrade_deps_block_check('0616', :eg_version);
4680
4681 CREATE OR REPLACE FUNCTION actor.org_unit_prox_update () RETURNS TRIGGER as $$
4682 BEGIN
4683
4684
4685 IF TG_OP = 'DELETE' THEN
4686
4687     DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4688
4689 END IF;
4690
4691 IF TG_OP = 'UPDATE' THEN
4692
4693     IF NEW.parent_ou <> OLD.parent_ou THEN
4694
4695         DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4696             INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4697             SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4698                 FROM  actor.org_unit l, actor.org_unit r
4699                 WHERE (l.id = NEW.id or r.id = NEW.id);
4700
4701     END IF;
4702
4703 END IF;
4704
4705 IF TG_OP = 'INSERT' THEN
4706
4707      INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4708      SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4709          FROM  actor.org_unit l, actor.org_unit r
4710          WHERE (l.id = NEW.id or r.id = NEW.id);
4711
4712 END IF;
4713
4714 RETURN null;
4715
4716 END;
4717 $$ LANGUAGE plpgsql;
4718
4719
4720 CREATE TRIGGER proximity_update_tgr AFTER INSERT OR UPDATE OR DELETE ON actor.org_unit FOR EACH ROW EXECUTE PROCEDURE actor.org_unit_prox_update ();
4721
4722
4723 SELECT evergreen.upgrade_deps_block_check('0617', :eg_version);
4724
4725 -- add notify columns to booking.reservation
4726 ALTER TABLE booking.reservation
4727   ADD COLUMN email_notify BOOLEAN NOT NULL DEFAULT FALSE;
4728
4729 -- create the hook and validator
4730 INSERT INTO action_trigger.hook (key, core_type, description, passive)
4731   VALUES ('reservation.available', 'bresv', 'A reservation is available for pickup', false);
4732 INSERT INTO action_trigger.validator (module, description)
4733   VALUES ('ReservationIsAvailable','Checked that a reserved resource is available for checkout');
4734
4735 -- create org unit setting to toggle checkbox display
4736 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
4737   VALUES ('booking.allow_email_notify', 'booking.allow_email_notify', 'Permit email notification when a reservation is ready for pickup.', 'bool');
4738
4739
4740 SELECT evergreen.upgrade_deps_block_check('0618', :eg_version);
4741
4742 UPDATE config.org_unit_setting_type SET description = E'The Regular Expression for validation on the day_phone field in patron registration. Note: The first capture group will be used for the "last 4 digits of phone number" feature, if enabled. Ex: "[2-9]\\d{2}-\\d{3}-(\\d{4})( x\\d+)?" will ignore the extension on a NANP number.' WHERE name = 'ui.patron.edit.au.day_phone.regex';
4743
4744 UPDATE config.org_unit_setting_type SET description = 'The Regular Expression for validation on phone fields in patron registration. Applies to all phone fields without their own setting. NOTE: See description of the day_phone regex for important information about capture groups with it.' WHERE name = 'ui.patron.edit.phone.regex';
4745
4746 UPDATE config.org_unit_setting_type SET description = oils_i18n_gettext('patron.password.use_phone', 'By default, use the last 4 alphanumeric characters of the patrons phone number as the default password when creating new users.  The exact characters used may be configured via the "GUI: Regex for day_phone field on patron registration" setting.', 'coust', 'description') WHERE name = 'patron.password.use_phone';
4747
4748 -- Evergreen DB patch 0619.schema.au_last_update_time.sql
4749
4750 -- check whether patch can be applied
4751 SELECT evergreen.upgrade_deps_block_check('0619', :eg_version);
4752
4753 -- Add new column last_update_time to actor.usr, with trigger to maintain it
4754 -- Add corresponding new column to auditor.actor_usr_history
4755
4756 ALTER TABLE actor.usr
4757         ADD COLUMN last_update_time TIMESTAMPTZ;
4758
4759 ALTER TABLE auditor.actor_usr_history
4760         ADD COLUMN last_update_time TIMESTAMPTZ;
4761
4762 CREATE OR REPLACE FUNCTION actor.au_updated()
4763 RETURNS TRIGGER AS $$
4764 BEGIN
4765     NEW.last_update_time := now();
4766         RETURN NEW;
4767 END;
4768 $$ LANGUAGE plpgsql;
4769
4770 CREATE TRIGGER au_update_trig
4771         BEFORE INSERT OR UPDATE ON actor.usr
4772         FOR EACH ROW EXECUTE PROCEDURE actor.au_updated();
4773
4774 -- Evergreen DB patch XXXX.data.opac_payment_history_age_limit.sql
4775
4776
4777 SELECT evergreen.upgrade_deps_block_check('0621', :eg_version);
4778
4779 INSERT into config.org_unit_setting_type (name, label, description, datatype)
4780 VALUES (
4781     'opac.payment_history_age_limit',
4782     oils_i18n_gettext('opac.payment_history_age_limit',
4783         'OPAC: Payment History Age Limit', 'coust', 'label'),
4784     oils_i18n_gettext('opac.payment_history_age_limit',
4785         'The OPAC should not display payments by patrons that are older than any interval defined here.', 'coust', 'label'),
4786     'interval'
4787 );
4788
4789 -- Updates config.org_unit_setting_type to remove the old tag prefixes for once 
4790 -- groups have been added.
4791 --
4792
4793 SELECT evergreen.upgrade_deps_block_check('0622', :eg_version);
4794
4795 INSERT INTO config.settings_group (name, label) VALUES
4796 ('sys', oils_i18n_gettext('config.settings_group.system', 'System', 'coust', 'label')),
4797 ('gui', oils_i18n_gettext('config.settings_group.gui', 'GUI', 'coust', 'label')),
4798 ('lib', oils_i18n_gettext('config.settings_group.lib', 'Library', 'coust', 'label')),
4799 ('sec', oils_i18n_gettext('config.settings_group.sec', 'Security', 'coust', 'label')),
4800 ('cat', oils_i18n_gettext('config.settings_group.cat', 'Cataloging', 'coust', 'label')),
4801 ('holds', oils_i18n_gettext('config.settings_group.holds', 'Holds', 'coust', 'label')),
4802 ('circ', oils_i18n_gettext('config.settings_group.circulation', 'Circulation', 'coust', 'label')),
4803 ('self', oils_i18n_gettext('config.settings_group.self', 'Self Check', 'coust', 'label')),
4804 ('opac', oils_i18n_gettext('config.settings_group.opac', 'OPAC', 'coust', 'label')),
4805 ('prog', oils_i18n_gettext('config.settings_group.program', 'Program', 'coust', 'label')),
4806 ('glob', oils_i18n_gettext('config.settings_group.global', 'Global', 'coust', 'label')),
4807 ('finance', oils_i18n_gettext('config.settings_group.finances', 'Finances', 'coust', 'label')),
4808 ('credit', oils_i18n_gettext('config.settings_group.ccp', 'Credit Card Processing', 'coust', 'label')),
4809 ('serial', oils_i18n_gettext('config.settings_group.serial', 'Serials', 'coust', 'label')),
4810 ('recall', oils_i18n_gettext('config.settings_group.recall', 'Recalls', 'coust', 'label')),
4811 ('booking', oils_i18n_gettext('config.settings_group.booking', 'Booking', 'coust', 'label')),
4812 ('offline', oils_i18n_gettext('config.settings_group.offline', 'Offline', 'coust', 'label')),
4813 ('receipt_template', oils_i18n_gettext('config.settings_group.receipt_template', 'Receipt Template', 'coust', 'label'));
4814
4815 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Set copy creator as receiver' WHERE name = 'acq.copy_creator_uses_receiver';
4816 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_circ_modifier';
4817 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_copy_location';
4818 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.block';
4819 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.warn';
4820 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.holds.allow_holds_from_purchase_request';
4821 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_barcode_prefix';
4822 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_callnumber_prefix';
4823 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.opac_timeout';
4824 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.persistent_login_interval';
4825 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.staff_timeout';
4826 UPDATE config.org_unit_setting_type SET grp = 'booking' WHERE name = 'booking.allow_email_notify';
4827 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'cat.bib.alert_on_empty';
4828 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Delete bib if all copies are deleted via Acquisitions lineitem cancellation.' WHERE name = 'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel';
4829 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'cat.bib.keep_on_empty';
4830 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default Classification Scheme' WHERE name = 'cat.default_classification_scheme';
4831 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (fast add)' WHERE name = 'cat.default_copy_status_fast';
4832 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (normal)' WHERE name = 'cat.default_copy_status_normal';
4833 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'cat.default_item_price';
4834 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font family' WHERE name = 'cat.label.font.family';
4835 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font size' WHERE name = 'cat.label.font.size';
4836 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font weight' WHERE name = 'cat.label.font.weight';
4837 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Defines the control number identifier used in 003 and 035 fields.' WHERE name = 'cat.marc_control_number_identifier';
4838 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label maximum lines' WHERE name = 'cat.spine.line.height';
4839 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label left margin' WHERE name = 'cat.spine.line.margin';
4840 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label line width' WHERE name = 'cat.spine.line.width';
4841 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Delete volume with last copy' WHERE name = 'cat.volume.delete_on_empty';
4842 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Toggle off the patron summary sidebar after first view.' WHERE name = 'circ.auto_hide_patron_summary';
4843 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Block Renewal of Items Needed for Holds' WHERE name = 'circ.block_renews_for_holds';
4844 UPDATE config.org_unit_setting_type SET grp = 'booking', label='Elbow room' WHERE name = 'circ.booking_reservation.default_elbow_room';
4845 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_lost_on_zero';
4846 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_on_damaged';
4847 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_auto_renew_age';
4848 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold';
4849 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold_exact_match_only';
4850 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_never_checked_out.mark_missing';
4851 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_return.copy_status';
4852 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.damaged.void_ovedue';
4853 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.damaged_item_processing_fee';
4854 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.' WHERE name = 'circ.do_not_tally_claims_returned';
4855 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard boundary' WHERE name = 'circ.hold_boundary.hard';
4856 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft boundary' WHERE name = 'circ.hold_boundary.soft';
4857 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Alert Interval' WHERE name = 'circ.hold_expire_alert_interval';
4858 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Interval' WHERE name = 'circ.hold_expire_interval';
4859 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.hold_shelf_status_delay';
4860 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft stalling interval' WHERE name = 'circ.hold_stalling.soft';
4861 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard stalling interval' WHERE name = 'circ.hold_stalling_hard';
4862 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Use Active Date for Age Protection' WHERE name = 'circ.holds.age_protect.active_date';
4863 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Behind Desk Pickup Supported' WHERE name = 'circ.holds.behind_desk_pickup_supported';
4864 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display age' WHERE name = 'circ.holds.canceled.display_age';
4865 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display count' WHERE name = 'circ.holds.canceled.display_count';
4866 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Clear shelf copy status' WHERE name = 'circ.holds.clear_shelf.copy_status';
4867 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Bypass hold capture during clear shelf process' WHERE name = 'circ.holds.clear_shelf.no_capture_holds';
4868 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Default Estimated Wait' WHERE name = 'circ.holds.default_estimated_wait_interval';
4869 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.default_shelf_expire_interval';
4870 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Block hold request if hold recipient privileges have expired' WHERE name = 'circ.holds.expired_patron_block';
4871 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Alert' WHERE name = 'circ.holds.hold_has_copy_at.alert';
4872 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Block' WHERE name = 'circ.holds.hold_has_copy_at.block';
4873 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Maximum library target attempts' WHERE name = 'circ.holds.max_org_unit_target_loops';
4874 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Minimum Estimated Wait' WHERE name = 'circ.holds.min_estimated_wait_interval';
4875 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Org Unit Target Weight' WHERE name = 'circ.holds.org_unit_target_weight';
4876 UPDATE config.org_unit_setting_type SET grp = 'recall', label='An array of fine amount, fine interval, and maximum fine.' WHERE name = 'circ.holds.recall_fine_rules';
4877 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Truncated loan period.' WHERE name = 'circ.holds.recall_return_interval';
4878 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Circulation duration that triggers a recall.' WHERE name = 'circ.holds.recall_threshold';
4879 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Use weight-based hold targeting' WHERE name = 'circ.holds.target_holds_by_org_unit_weight';
4880 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.target_skip_me';
4881 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Reset request time on un-cancel' WHERE name = 'circ.holds.uncancel.reset_request_time';
4882 UPDATE config.org_unit_setting_type SET grp = 'holds', label='FIFO' WHERE name = 'circ.holds_fifo';
4883 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'circ.item_checkout_history.max';
4884 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost Checkin Generates New Overdues' WHERE name = 'circ.lost.generate_overdue_on_checkin';
4885 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost items usable on checkin' WHERE name = 'circ.lost_immediately_available';
4886 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.lost_materials_processing_fee';
4887 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost max interval' WHERE name = 'circ.max_accept_return_of_lost';
4888 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Cap Max Fine at Item Price' WHERE name = 'circ.max_fine.cap_at_price';
4889 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.max_patron_claim_return_count';
4890 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Item Status for Missing Pieces' WHERE name = 'circ.missing_pieces.copy_status';
4891 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'circ.obscure_dob';
4892 UPDATE config.org_unit_setting_type SET grp = 'offline', label='Skip offline checkin if newer item Status Changed Time.' WHERE name = 'circ.offline.skip_checkin_if_newer_status_changed_time';
4893 UPDATE config.org_unit_setting_type SET grp = 'offline', label='Skip offline checkout if newer item Status Changed Time.' WHERE name = 'circ.offline.skip_checkout_if_newer_status_changed_time';
4894 UPDATE config.org_unit_setting_type SET grp = 'offline', label='Skip offline renewal if newer item Status Changed Time.' WHERE name = 'circ.offline.skip_renew_if_newer_status_changed_time';
4895 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Offline: Patron Usernames Allowed' WHERE name = 'circ.offline.username_allowed';
4896 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Maximum concurrently active self-serve password reset requests per user' WHERE name = 'circ.password_reset_request_per_user_limit';
4897 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Require matching email address for password reset requests' WHERE name = 'circ.password_reset_request_requires_matching_email';
4898 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Maximum concurrently active self-serve password reset requests' WHERE name = 'circ.password_reset_request_throttle';
4899 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Self-serve password reset request time-to-live' WHERE name = 'circ.password_reset_request_time_to_live';
4900 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Registration: Cloned patrons get address copy' WHERE name = 'circ.patron_edit.clone.copy_address';
4901 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.patron_invalid_address_apply_penalty';
4902 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.pre_cat_copy_circ_lib';
4903 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.reshelving_complete.interval';
4904 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Restore overdues on lost item return' WHERE name = 'circ.restore_overdue_on_lost_return';
4905 UPDATE config.org_unit_setting_type SET grp = 'self', label='Pop-up alert for errors' WHERE name = 'circ.selfcheck.alert.popup';
4906 UPDATE config.org_unit_setting_type SET grp = 'self', label='Audio Alerts' WHERE name = 'circ.selfcheck.alert.sound';
4907 UPDATE config.org_unit_setting_type SET grp = 'self' WHERE name = 'circ.selfcheck.auto_override_checkout_events';
4908 UPDATE config.org_unit_setting_type SET grp = 'self', label='Block copy checkout status' WHERE name = 'circ.selfcheck.block_checkout_on_copy_status';
4909 UPDATE config.org_unit_setting_type SET grp = 'self', label='Patron Login Timeout (in seconds)' WHERE name = 'circ.selfcheck.patron_login_timeout';
4910 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require Patron Password' WHERE name = 'circ.selfcheck.patron_password_required';
4911 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require patron password' WHERE name = 'circ.selfcheck.require_patron_password';
4912 UPDATE config.org_unit_setting_type SET grp = 'self', label='Workstation Required' WHERE name = 'circ.selfcheck.workstation_required';
4913 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.staff_client.actor_on_checkout';
4914 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'circ.staff_client.do_not_auto_attempt_print';
4915 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of alert_text include' WHERE name = 'circ.staff_client.receipt.alert_text';
4916 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of event_text include' WHERE name = 'circ.staff_client.receipt.event_text';
4917 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of footer_text include' WHERE name = 'circ.staff_client.receipt.footer_text';
4918 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of header_text include' WHERE name = 'circ.staff_client.receipt.header_text';
4919 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of notice_text include' WHERE name = 'circ.staff_client.receipt.notice_text';
4920 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Minimum Transit Checkin Interval' WHERE name = 'circ.transit.min_checkin_interval';
4921 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Deactivate Card' WHERE name = 'circ.user_merge.deactivate_cards';
4922 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Address Delete' WHERE name = 'circ.user_merge.delete_addresses';
4923 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Barcode Delete' WHERE name = 'circ.user_merge.delete_cards';
4924 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost item billing when returned' WHERE name = 'circ.void_lost_on_checkin';
4925 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void processing fee on lost item return' WHERE name = 'circ.void_lost_proc_fee_on_checkin';
4926 UPDATE config.org_unit_setting_type SET grp = 'finance', label='Void overdue fines when items are marked lost' WHERE name = 'circ.void_overdue_on_lost';
4927 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'credit.payments.allow';
4928 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable AuthorizeNet payments' WHERE name = 'credit.processor.authorizenet.enabled';
4929 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet login' WHERE name = 'credit.processor.authorizenet.login';
4930 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet password' WHERE name = 'credit.processor.authorizenet.password';
4931 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet server' WHERE name = 'credit.processor.authorizenet.server';
4932 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet test mode' WHERE name = 'credit.processor.authorizenet.testmode';
4933 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Name default credit processor' WHERE name = 'credit.processor.default';
4934 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayflowPro payments' WHERE name = 'credit.processor.payflowpro.enabled';
4935 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro login/merchant ID' WHERE name = 'credit.processor.payflowpro.login';
4936 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro partner' WHERE name = 'credit.processor.payflowpro.partner';
4937 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro password' WHERE name = 'credit.processor.payflowpro.password';
4938 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro test mode' WHERE name = 'credit.processor.payflowpro.testmode';
4939 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro vendor' WHERE name = 'credit.processor.payflowpro.vendor';
4940 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayPal payments' WHERE name = 'credit.processor.paypal.enabled';
4941 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal login' WHERE name = 'credit.processor.paypal.login';
4942 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal password' WHERE name = 'credit.processor.paypal.password';
4943 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal signature' WHERE name = 'credit.processor.paypal.signature';
4944 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal test mode' WHERE name = 'credit.processor.paypal.testmode';
4945 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Dates with this pattern.' WHERE name = 'format.date';
4946 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Times with this pattern.' WHERE name = 'format.time';
4947 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.default_locale';
4948 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'global.juvenile_age_threshold';
4949 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.password_regex';
4950 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Disable the ability to save list column configurations locally.' WHERE name = 'gui.disable_local_save_columns';
4951 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Courier Code' WHERE name = 'lib.courier_code';
4952 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'notice.telephony.callfile_lines';
4953 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Allow pending addresses' WHERE name = 'opac.allow_pending_address';
4954 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'opac.barcode_regex';
4955 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Use fully compressed serial holdings' WHERE name = 'opac.fully_compressed_serial_holdings';
4956 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Org Unit Hiding Depth' WHERE name = 'opac.org_unit_hiding.depth';
4957 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Payment History Age Limit' WHERE name = 'opac.payment_history_age_limit';
4958 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'org.bounced_emails';
4959 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Boundary' WHERE name = 'org.patron_opt_boundary';
4960 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Default' WHERE name = 'org.patron_opt_default';
4961 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'patron.password.use_phone';
4962 UPDATE config.org_unit_setting_type SET grp = 'serial', label='Previous Issuance Copy Location' WHERE name = 'serial.prev_issuance_copy_location';
4963 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Patrons Logged' WHERE name = 'ui.admin.patron_log.max_entries';
4964 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Actions Logged' WHERE name = 'ui.admin.work_log.max_entries';
4965 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Horizontal layout for Volume/Copy Creator/Editor.' WHERE name = 'ui.cat.volume_copy_editor.horizontal';
4966 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Uncheck bills by default in the patron billing interface' WHERE name = 'ui.circ.billing.uncheck_bills_and_unfocus_payment_box';
4967 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Record In-House Use: Maximum # of uses allowed per entry.' WHERE name = 'ui.circ.in_house_use.entry_cap';
4968 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Record In-House Use: # of uses threshold for Are You Sure? dialog.' WHERE name = 'ui.circ.in_house_use.entry_warn';
4969 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.patron_summary.horizontal';
4970 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.show_billing_tab_on_bills';
4971 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Suppress popup-dialogs during check-in.' WHERE name = 'ui.circ.suppress_checkin_popups';
4972 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Button bar' WHERE name = 'ui.general.button_bar';
4973 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Hotkeyset' WHERE name = 'ui.general.hotkeyset';
4974 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Idle timeout' WHERE name = 'ui.general.idle_timeout';
4975 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Country for New Addresses in Patron Editor' WHERE name = 'ui.patron.default_country';
4976 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Ident Type for Patron Registration' WHERE name = 'ui.patron.default_ident_type';
4977 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Default level of patrons'' internet access' WHERE name = 'ui.patron.default_inet_access_level';
4978 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show active field on patron registration' WHERE name = 'ui.patron.edit.au.active.show';
4979 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest active field on patron registration' WHERE name = 'ui.patron.edit.au.active.suggest';
4980 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show alert_message field on patron registration' WHERE name = 'ui.patron.edit.au.alert_message.show';
4981 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest alert_message field on patron registration' WHERE name = 'ui.patron.edit.au.alert_message.suggest';
4982 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.show';
4983 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.suggest';
4984 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.show';
4985 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.suggest';
4986 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show claims_never_checked_out_count field on patron registration' WHERE name = 'ui.patron.edit.au.claims_never_checked_out_count.show';
4987 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest claims_never_checked_out_count field on patron registration' WHERE name = 'ui.patron.edit.au.claims_never_checked_out_count.suggest';
4988 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show claims_returned_count field on patron registration' WHERE name = 'ui.patron.edit.au.claims_returned_count.show';
4989 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest claims_returned_count field on patron registration' WHERE name = 'ui.patron.edit.au.claims_returned_count.suggest';
4990 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for day_phone field on patron registration' WHERE name = 'ui.patron.edit.au.day_phone.example';
4991 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for day_phone field on patron registration' WHERE name = 'ui.patron.edit.au.day_phone.regex';
4992 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require day_phone field on patron registration' WHERE name = 'ui.patron.edit.au.day_phone.require';
4993 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show day_phone field on patron registration' WHERE name = 'ui.patron.edit.au.day_phone.show';
4994 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest day_phone field on patron registration' WHERE name = 'ui.patron.edit.au.day_phone.suggest';
4995 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show calendar widget for dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.calendar';
4996 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.require';
4997 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.show';
4998 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.suggest';
4999 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for email field on patron registration' WHERE name = 'ui.patron.edit.au.email.example';
5000 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for email field on patron registration' WHERE name = 'ui.patron.edit.au.email.regex';
5001 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require email field on patron registration' WHERE name = 'ui.patron.edit.au.email.require';
5002 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show email field on patron registration' WHERE name = 'ui.patron.edit.au.email.show';
5003 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest email field on patron registration' WHERE name = 'ui.patron.edit.au.email.suggest';
5004 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for evening_phone field on patron registration' WHERE name = 'ui.patron.edit.au.evening_phone.example';
5005 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for evening_phone field on patron registration' WHERE name = 'ui.patron.edit.au.evening_phone.regex';
5006 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require evening_phone field on patron registration' WHERE name = 'ui.patron.edit.au.evening_phone.require';
5007 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show evening_phone field on patron registration' WHERE name = 'ui.patron.edit.au.evening_phone.show';
5008 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest evening_phone field on patron registration' WHERE name = 'ui.patron.edit.au.evening_phone.suggest';
5009 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show ident_value field on patron registration' WHERE name = 'ui.patron.edit.au.ident_value.show';
5010 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest ident_value field on patron registration' WHERE name = 'ui.patron.edit.au.ident_value.suggest';
5011 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show ident_value2 field on patron registration' WHERE name = 'ui.patron.edit.au.ident_value2.show';
5012 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest ident_value2 field on patron registration' WHERE name = 'ui.patron.edit.au.ident_value2.suggest';
5013 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.show';
5014 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.suggest';
5015 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show master_account field on patron registration' WHERE name = 'ui.patron.edit.au.master_account.show';
5016 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest master_account field on patron registration' WHERE name = 'ui.patron.edit.au.master_account.suggest';
5017 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for other_phone field on patron registration' WHERE name = 'ui.patron.edit.au.other_phone.example';
5018 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for other_phone field on patron registration' WHERE name = 'ui.patron.edit.au.other_phone.regex';
5019 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require other_phone field on patron registration' WHERE name = 'ui.patron.edit.au.other_phone.require';
5020 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show other_phone field on patron registration' WHERE name = 'ui.patron.edit.au.other_phone.show';
5021 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest other_phone field on patron registration' WHERE name = 'ui.patron.edit.au.other_phone.suggest';
5022 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show second_given_name field on patron registration' WHERE name = 'ui.patron.edit.au.second_given_name.show';
5023 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest second_given_name field on patron registration' WHERE name = 'ui.patron.edit.au.second_given_name.suggest';
5024 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.show';
5025 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.suggest';
5026 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require county field on patron registration' WHERE name = 'ui.patron.edit.aua.county.require';
5027 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for post_code field on patron registration' WHERE name = 'ui.patron.edit.aua.post_code.example';
5028 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for post_code field on patron registration' WHERE name = 'ui.patron.edit.aua.post_code.regex';
5029 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default showing suggested patron registration fields' WHERE name = 'ui.patron.edit.default_suggested';
5030 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.example';
5031 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.regex';
5032 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require at least one address for Patron Registration' WHERE name = 'ui.patron.registration.require_address';
5033 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Cap results in Patron Search at this number.' WHERE name = 'ui.patron_search.result_cap';
5034 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require staff initials for entry/edit of item/patron/penalty notes/messages.' WHERE name = 'ui.staff.require_initials';
5035 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Unified Volume/Item Creator/Editor' WHERE name = 'ui.unified_volume_copy_editor';
5036 UPDATE config.org_unit_setting_type SET grp = 'gui', label='URL for remote directory containing list column settings.' WHERE name = 'url.remote_column_settings';
5037
5038
5039
5040
5041 SELECT evergreen.upgrade_deps_block_check('0623', :eg_version);
5042
5043
5044 CREATE TABLE config.org_unit_setting_type_log (
5045     id              BIGSERIAL   PRIMARY KEY,
5046     date_applied    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
5047     org             INT         REFERENCES actor.org_unit (id),
5048     original_value  TEXT,
5049     new_value       TEXT,
5050     field_name      TEXT      REFERENCES config.org_unit_setting_type (name)
5051 );
5052
5053 -- Log each change in oust to oustl, so admins can see what they messed up if someting stops working.
5054 CREATE OR REPLACE FUNCTION ous_change_log() RETURNS TRIGGER AS $ous_change_log$
5055     DECLARE
5056     original TEXT;
5057     BEGIN
5058         -- Check for which setting is being updated, and log it.
5059         SELECT INTO original value FROM actor.org_unit_setting WHERE name = NEW.name AND org_unit = NEW.org_unit;
5060                 
5061         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (NEW.org_unit, original, NEW.value, NEW.name);
5062         
5063         RETURN NEW;
5064     END;
5065 $ous_change_log$ LANGUAGE plpgsql;    
5066
5067 CREATE TRIGGER log_ous_change
5068     BEFORE INSERT OR UPDATE ON actor.org_unit_setting
5069     FOR EACH ROW EXECUTE PROCEDURE ous_change_log();
5070
5071 CREATE OR REPLACE FUNCTION ous_delete_log() RETURNS TRIGGER AS $ous_delete_log$
5072     DECLARE
5073     original TEXT;
5074     BEGIN
5075         -- Check for which setting is being updated, and log it.
5076         SELECT INTO original value FROM actor.org_unit_setting WHERE name = OLD.name AND org_unit = OLD.org_unit;
5077                 
5078         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (OLD.org_unit, original, 'null', OLD.name);
5079         
5080         RETURN OLD;
5081     END;
5082 $ous_delete_log$ LANGUAGE plpgsql;    
5083
5084 CREATE TRIGGER log_ous_del
5085     BEFORE DELETE ON actor.org_unit_setting
5086     FOR EACH ROW EXECUTE PROCEDURE ous_delete_log();
5087
5088 -- Evergreen DB patch 0625.data.opac_staff_saved_search_size.sql
5089
5090
5091 SELECT evergreen.upgrade_deps_block_check('0625', :eg_version);
5092
5093 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype)
5094 VALUES (
5095     'opac.staff_saved_search.size', 'opac',
5096     oils_i18n_gettext('opac.staff_saved_search.size',
5097         'OPAC: Number of staff client saved searches to display on left side of results and record details pages', 'coust', 'label'),
5098     oils_i18n_gettext('opac.staff_saved_search.size',
5099         'If unset, the OPAC (only when wrapped in the staff client!) will default to showing you your ten most recent searches on the left side of the results and record details pages.  If you actually don''t want to see this feature at all, set this value to zero at the top of your organizational tree.', 'coust', 'description'),
5100     'integer'
5101 );
5102
5103 -- Evergreen DB patch 0626.schema.bookbag-goodies.sql
5104
5105
5106 SELECT evergreen.upgrade_deps_block_check('0626', :eg_version);
5107
5108 ALTER TABLE container.biblio_record_entry_bucket
5109     ADD COLUMN description TEXT;
5110
5111 ALTER TABLE container.call_number_bucket
5112     ADD COLUMN description TEXT;
5113
5114 ALTER TABLE container.copy_bucket
5115     ADD COLUMN description TEXT;
5116
5117 ALTER TABLE container.user_bucket
5118     ADD COLUMN description TEXT;
5119
5120 INSERT INTO action_trigger.hook (key, core_type, description, passive)
5121 VALUES (
5122     'container.biblio_record_entry_bucket.csv',
5123     'cbreb',
5124     oils_i18n_gettext(
5125         'container.biblio_record_entry_bucket.csv',
5126         'Produce a CSV file representing a bookbag',
5127         'ath',
5128         'description'
5129     ),
5130     FALSE
5131 );
5132
5133 INSERT INTO action_trigger.reactor (module, description)
5134 VALUES (
5135     'ContainerCSV',
5136     oils_i18n_gettext(
5137         'ContainerCSV',
5138         'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
5139         'atr',
5140         'description'
5141     )
5142 );
5143
5144 INSERT INTO action_trigger.event_definition (
5145     id, active, owner,
5146     name, hook, reactor,
5147     validator, template
5148 ) VALUES (
5149     48, TRUE, 1,
5150     'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
5151     'NOOP_True',
5152 $$
5153 [%-
5154 # target is the bookbag itself. The 'items' variable does not need to be in
5155 # the environment because a special reactor will take care of filling it in.
5156
5157 FOR item IN items;
5158     bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
5159     title = "";
5160     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
5161         title = title _ part.textContent;
5162     END;
5163     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
5164
5165     helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
5166 END -%]
5167 $$
5168 );
5169
5170 -- Evergreen DB patch 0627.data.patron-password-reset-msg.sql
5171 --
5172 -- Updates password reset template to match TPAC reset form
5173 --
5174
5175 -- check whether patch can be applied
5176 SELECT evergreen.upgrade_deps_block_check('0627', :eg_version);
5177
5178 UPDATE action_trigger.event_definition SET template = 
5179 $$
5180 [%- USE date -%]
5181 [%- user = target.usr -%]
5182 To: [%- params.recipient_email || user.email %]
5183 From: [%- params.sender_email || user.home_ou.email || default_sender %]
5184 Subject: [% user.home_ou.name %]: library account password reset request
5185
5186 You have received this message because you, or somebody else, requested a reset
5187 of your library system password. If you did not request a reset of your library
5188 system password, just ignore this message and your current password will
5189 continue to work.
5190
5191 If you did request a reset of your library system password, please perform
5192 the following steps to continue the process of resetting your password:
5193
5194 1. Open the following link in a web browser: https://[% params.hostname %]/eg/opac/password_reset/[% target.uuid %]
5195 The browser displays a password reset form.
5196
5197 2. Enter your new password in the password reset form in the browser. You must
5198 enter the password twice to ensure that you do not make a mistake. If the
5199 passwords match, you will then be able to log in to your library system account
5200 with the new password.
5201
5202 $$
5203 WHERE id = 20; -- Password reset request notification
5204
5205
5206 SELECT evergreen.upgrade_deps_block_check('0630', :eg_version);
5207
5208 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5209 ( 'circ.transit.suppress_hold', 'circ',
5210     oils_i18n_gettext('circ.transit.suppress_hold',
5211         'Suppress Hold Transits Group',
5212         'coust', 'label'),
5213     oils_i18n_gettext('circ.transit.suppress_hold',
5214         'If set to a non-empty value, Hold Transits will be suppressed between this OU and others with the same value. If set to an empty value, transits will not be suppressed.',
5215         'coust', 'description'),
5216     'string')
5217 ,( 'circ.transit.suppress_non_hold', 'circ',
5218     oils_i18n_gettext('circ.transit.suppress_non_hold',
5219         'Suppress Non-Hold Transits Group',
5220         'coust', 'label'),
5221     oils_i18n_gettext('circ.transit.suppress_non_hold',
5222         'If set to a non-empty value, Non-Hold Transits will be suppressed between this OU and others with the same value. If set to an empty value, transits will not be suppressed.',
5223         'coust', 'description'),
5224     'string');
5225
5226
5227 -- check whether patch can be applied
5228 SELECT evergreen.upgrade_deps_block_check('0632', :eg_version);
5229
5230 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5231 ( 'opac.username_regex', 'glob',
5232     oils_i18n_gettext('opac.username_regex',
5233         'Patron username format',
5234         'coust', 'label'),
5235     oils_i18n_gettext('opac.username_regex',
5236         'Regular expression defining the patron username format, used for patron registration and self-service username changing only',
5237         'coust', 'description'),
5238     'string')
5239 ,( 'opac.lock_usernames', 'glob',
5240     oils_i18n_gettext('opac.lock_usernames',
5241         'Lock Usernames',
5242         'coust', 'label'),
5243     oils_i18n_gettext('opac.lock_usernames',
5244         'If enabled username changing via the OPAC will be disabled',
5245         'coust', 'description'),
5246     'bool')
5247 ,( 'opac.unlimit_usernames', 'glob',
5248     oils_i18n_gettext('opac.unlimit_usernames',
5249         'Allow multiple username changes',
5250         'coust', 'label'),
5251     oils_i18n_gettext('opac.unlimit_usernames',
5252         'If enabled (and Lock Usernames is not set) patrons will be allowed to change their username when it does not look like a barcode. Otherwise username changing in the OPAC will only be allowed when the patron''s username looks like a barcode.',
5253         'coust', 'description'),
5254     'bool')
5255 ;
5256
5257 -- Evergreen DB patch 0635.data.opac.jump-to-details-setting.sql
5258 --
5259
5260
5261 -- check whether patch can be applied
5262 SELECT evergreen.upgrade_deps_block_check('0635', :eg_version);
5263
5264 INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype )
5265     VALUES (
5266         'opac.staff.jump_to_details_on_single_hit', 
5267         'opac',
5268         oils_i18n_gettext(
5269             'opac.staff.jump_to_details_on_single_hit',
5270             'Jump to details on 1 hit (staff client)',
5271             'coust', 
5272             'label'
5273         ),
5274         oils_i18n_gettext(
5275             'opac.staff.jump_to_details_on_single_hit',
5276             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the OPAC within the staff client',
5277             'coust', 
5278             'description'
5279         ),
5280         'bool'
5281     ), (
5282         'opac.patron.jump_to_details_on_single_hit', 
5283         'opac',
5284         oils_i18n_gettext(
5285             'opac.patron.jump_to_details_on_single_hit',
5286             'Jump to details on 1 hit (public)',
5287             'coust', 
5288             'label'
5289         ),
5290         oils_i18n_gettext(
5291             'opac.patron.jump_to_details_on_single_hit',
5292             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the public OPAC',
5293             'coust', 
5294             'description'
5295         ),
5296         'bool'
5297     );
5298
5299 -- Evergreen DB patch 0636.data.grace_period_extend.sql
5300 --
5301 -- OU setting turns on grace period auto extension. By default they only do so
5302 -- when the grace period ends on a closed date, but there are two modifiers to
5303 -- change that.
5304 -- 
5305 -- The first modifier causes grace periods to extend for all closed dates that
5306 -- they intersect. This is "grace periods are only consumed by open days."
5307 -- 
5308 -- The second modifier causes a grace period that ends just before a closed
5309 -- day, with or without extension having happened, to include the closed day
5310 -- (and any following it) as well. This is mainly so that a backdate into the
5311 -- closed period following the grace period will assume the "best case" of the
5312 -- item having been returned after hours on the last day of the closed date.
5313 --
5314
5315
5316 -- check whether patch can be applied
5317 SELECT evergreen.upgrade_deps_block_check('0636', :eg_version);
5318
5319 INSERT INTO config.org_unit_setting_type(name, grp, label, description, datatype) VALUES
5320
5321 ( 'circ.grace.extend', 'circ',
5322     oils_i18n_gettext('circ.grace.extend',
5323         'Auto-Extend Grace Periods',
5324         'coust', 'label'),
5325     oils_i18n_gettext('circ.grace.extend',
5326         'When enabled grace periods will auto-extend. By default this will be only when they are a full day or more and end on a closed date, though other options can alter this.',
5327         'coust', 'description'),
5328     'bool')
5329
5330 ,( 'circ.grace.extend.all', 'circ',
5331     oils_i18n_gettext('circ.grace.extend.all',
5332         'Auto-Extending Grace Periods extend for all closed dates',
5333         'coust', 'label'),
5334     oils_i18n_gettext('circ.grace.extend.all',
5335         'If enabled and Grace Periods auto-extending is turned on grace periods will extend past all closed dates they intersect, within hard-coded limits. This basically becomes "grace periods can only be consumed by closed dates".',
5336         'coust', 'description'),
5337     'bool')
5338
5339 ,( 'circ.grace.extend.into_closed', 'circ',
5340     oils_i18n_gettext('circ.grace.extend.into_closed',
5341         'Auto-Extending Grace Periods include trailing closed dates',
5342         'coust', 'label'),
5343     oils_i18n_gettext('circ.grace.extend.into_closed',
5344          'If enabled and Grace Periods auto-extending is turned on grace periods will include closed dates that directly follow the last day of the grace period, to allow a backdate into the closed dates to assume "returned after hours on the last day of the grace period, and thus still within it" automatically.',
5345         'coust', 'description'),
5346     'bool');
5347
5348
5349 -- XXXX.schema-acs-nfi.sql
5350
5351 SELECT evergreen.upgrade_deps_block_check('0640', :eg_version);
5352
5353 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
5354 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
5355 BEGIN
5356
5357     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
5358         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
5359         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
5360         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5361           -- Should remove matching $0 from controlled fields at the same time?
5362         RETURN NEW; -- and we're done
5363     END IF;
5364
5365     IF TG_OP = 'UPDATE' THEN -- re-ingest?
5366         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
5367
5368         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
5369             RETURN NEW;
5370         END IF;
5371
5372         -- Propagate these updates to any linked bib records
5373         PERFORM authority.propagate_changes(NEW.id) FROM authority.record_entry WHERE id = NEW.id;
5374
5375         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5376     END IF;
5377
5378     INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5379         SELECT record, atag, value, sort_value FROM authority.simple_heading_set(NEW.marc);
5380
5381     -- Flatten and insert the afr data
5382     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
5383     IF NOT FOUND THEN
5384         PERFORM authority.reingest_authority_full_rec(NEW.id);
5385         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
5386         IF NOT FOUND THEN
5387             PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
5388         END IF;
5389     END IF;
5390
5391     RETURN NEW;
5392 END;
5393 $func$ LANGUAGE PLPGSQL;
5394
5395 -- Entries that need to respect an NFI
5396 UPDATE authority.control_set_authority_field SET nfi = '2'
5397     WHERE id IN (4,24,44,64);
5398
5399 DROP TRIGGER authority_full_rec_fti_trigger ON authority.full_rec;
5400 CREATE TRIGGER authority_full_rec_fti_trigger
5401     BEFORE UPDATE OR INSERT ON authority.full_rec
5402     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5403
5404 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
5405 DECLARE
5406     acsaf           authority.control_set_authority_field%ROWTYPE;
5407     tag_used        TEXT;
5408     nfi_used        TEXT;
5409     sf              TEXT;
5410     thes_code       TEXT;
5411     cset            INT;
5412     heading_text    TEXT;
5413     tmp_text        TEXT;
5414     first_sf        BOOL;
5415     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5416 BEGIN
5417     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
5418
5419     IF cset IS NULL THEN
5420         SELECT  control_set INTO cset
5421           FROM  authority.control_set_authority_field
5422           WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
5423           LIMIT 1;
5424     END IF;
5425
5426     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
5427     IF thes_code IS NULL THEN
5428         thes_code := '|';
5429     ELSIF thes_code = 'z' THEN
5430         thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
5431     END IF;
5432
5433     heading_text := '';
5434     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
5435         tag_used := acsaf.tag;
5436         nfi_used := acsaf.nfi;
5437         first_sf := TRUE;
5438         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5439             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
5440
5441             IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
5442
5443                 tmp_text := SUBSTRING(
5444                     tmp_text FROM
5445                     COALESCE(
5446                         NULLIF(
5447                             REGEXP_REPLACE(
5448                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5449                                 $$\D+$$,
5450                                 '',
5451                                 'g'
5452                             ),
5453                             ''
5454                         )::INT,
5455                         0
5456                     ) + 1
5457                 );
5458
5459             END IF;
5460
5461             first_sf := FALSE;
5462
5463             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
5464                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
5465             END IF;
5466         END LOOP;
5467         EXIT WHEN heading_text <> '';
5468     END LOOP;
5469
5470     IF heading_text <> '' THEN
5471         IF no_thesaurus IS TRUE THEN
5472             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
5473         ELSE
5474             heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
5475         END IF;
5476     ELSE
5477         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
5478     END IF;
5479
5480     RETURN heading_text;
5481 END;
5482 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5483
5484 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5485     SELECT authority.normalize_heading($1, TRUE);
5486 $func$ LANGUAGE SQL IMMUTABLE;
5487
5488 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5489     SELECT authority.normalize_heading($1, FALSE);
5490 $func$ LANGUAGE SQL IMMUTABLE;
5491
5492
5493 CREATE TABLE authority.simple_heading (
5494     id              BIGSERIAL   PRIMARY KEY,
5495     record          BIGINT      NOT NULL REFERENCES authority.record_entry (id),
5496     atag            INT         NOT NULL REFERENCES authority.control_set_authority_field (id),
5497     value           TEXT        NOT NULL,
5498     sort_value      TEXT        NOT NULL,
5499     index_vector    tsvector    NOT NULL
5500 );
5501 CREATE TRIGGER authority_simple_heading_fti_trigger
5502     BEFORE UPDATE OR INSERT ON authority.simple_heading
5503     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5504
5505 CREATE INDEX authority_simple_heading_index_vector_idx ON authority.simple_heading USING GIST (index_vector);
5506 CREATE INDEX authority_simple_heading_value_idx ON authority.simple_heading (value);
5507 CREATE INDEX authority_simple_heading_sort_value_idx ON authority.simple_heading (sort_value);
5508
5509 CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
5510 DECLARE
5511     res             authority.simple_heading%ROWTYPE;
5512     acsaf           authority.control_set_authority_field%ROWTYPE;
5513     tag_used        TEXT;
5514     nfi_used        TEXT;
5515     sf              TEXT;
5516     cset            INT;
5517     heading_text    TEXT;
5518     sort_text       TEXT;
5519     tmp_text        TEXT;
5520     tmp_xml         TEXT;
5521     first_sf        BOOL;
5522     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5523 BEGIN
5524
5525     res.record := auth_id;
5526
5527     SELECT  control_set INTO cset
5528       FROM  authority.control_set_authority_field
5529       WHERE tag IN ( SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]) )
5530       LIMIT 1;
5531
5532     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
5533
5534         res.atag := acsaf.id;
5535         tag_used := acsaf.tag;
5536         nfi_used := acsaf.nfi;
5537
5538         FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)) LOOP
5539             heading_text := '';
5540
5541             FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5542                 heading_text := heading_text || COALESCE( ' ' || oils_xpath_string('//*[@code="'||sf||'"]',tmp_xml::TEXT), '');
5543             END LOOP;
5544
5545             heading_text := public.naco_normalize(heading_text);
5546             
5547             IF nfi_used IS NOT NULL THEN
5548
5549                 sort_text := SUBSTRING(
5550                     heading_text FROM
5551                     COALESCE(
5552                         NULLIF(
5553                             REGEXP_REPLACE(
5554                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5555                                 $$\D+$$,
5556                                 '',
5557                                 'g'
5558                             ),
5559                             ''
5560                         )::INT,
5561                         0
5562                     ) + 1
5563                 );
5564
5565             ELSE
5566                 sort_text := heading_text;
5567             END IF;
5568
5569             IF heading_text IS NOT NULL AND heading_text <> '' THEN
5570                 res.value := heading_text;
5571                 res.sort_value := sort_text;
5572                 RETURN NEXT res;
5573             END IF;
5574
5575         END LOOP;
5576
5577     END LOOP;
5578
5579     RETURN;
5580 END;
5581 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5582
5583 -- Support function used to find the pivot for alpha-heading-browse style searching
5584 CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT ) RETURNS TEXT AS $$
5585 DECLARE
5586     sort_value_row  RECORD;
5587     value_row       RECORD;
5588     t_term          TEXT;
5589 BEGIN
5590
5591     t_term := public.naco_normalize(q);
5592
5593     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5594                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5595             ash.sort_value
5596       INTO  sort_value_row
5597       FROM  authority.simple_heading ash
5598       WHERE ash.atag = ANY (a)
5599             AND ash.sort_value >= t_term
5600       ORDER BY rank DESC, ash.sort_value
5601       LIMIT 1;
5602
5603     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5604                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5605             ash.sort_value
5606       INTO  value_row
5607       FROM  authority.simple_heading ash
5608       WHERE ash.atag = ANY (a)
5609             AND ash.value >= t_term
5610       ORDER BY rank DESC, ash.sort_value
5611       LIMIT 1;
5612
5613     IF value_row.rank > sort_value_row.rank THEN
5614         RETURN value_row.sort_value;
5615     ELSE
5616         RETURN sort_value_row.sort_value;
5617     END IF;
5618 END;
5619 $$ LANGUAGE PLPGSQL;
5620
5621
5622 CREATE OR REPLACE FUNCTION authority.simple_heading_browse_center( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5623 DECLARE
5624     pivot_sort_value    TEXT;
5625     boffset             INT DEFAULT 0;
5626     aoffset             INT DEFAULT 0;
5627     blimit              INT DEFAULT 0;
5628     alimit              INT DEFAULT 0;
5629 BEGIN
5630
5631     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5632
5633     IF page = 0 THEN
5634         blimit := pagesize / 2;
5635         alimit := blimit;
5636
5637         IF pagesize % 2 <> 0 THEN
5638             alimit := alimit + 1;
5639         END IF;
5640     ELSE
5641         blimit := pagesize;
5642         alimit := blimit;
5643
5644         boffset := pagesize / 2;
5645         aoffset := boffset;
5646
5647         IF pagesize % 2 <> 0 THEN
5648             boffset := boffset + 1;
5649         END IF;
5650     END IF;
5651
5652     IF page <= 0 THEN
5653         RETURN QUERY
5654             -- "bottom" half of the browse results
5655             SELECT id FROM (
5656                 SELECT  ash.id,
5657                         row_number() over ()
5658                   FROM  authority.simple_heading ash
5659                   WHERE ash.atag = ANY (atag_list)
5660                         AND ash.sort_value < pivot_sort_value
5661                   ORDER BY ash.sort_value DESC
5662                   LIMIT blimit
5663                   OFFSET ABS(page) * pagesize - boffset
5664             ) x ORDER BY row_number DESC;
5665     END IF;
5666
5667     IF page >= 0 THEN
5668         RETURN QUERY
5669             -- "bottom" half of the browse results
5670             SELECT  ash.id
5671               FROM  authority.simple_heading ash
5672               WHERE ash.atag = ANY (atag_list)
5673                     AND ash.sort_value >= pivot_sort_value
5674               ORDER BY ash.sort_value
5675               LIMIT alimit
5676               OFFSET ABS(page) * pagesize - aoffset;
5677     END IF;
5678 END;
5679 $$ LANGUAGE PLPGSQL ROWS 10;
5680
5681 CREATE OR REPLACE FUNCTION authority.simple_heading_browse_top( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5682 DECLARE
5683     pivot_sort_value    TEXT;
5684 BEGIN
5685
5686     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5687
5688     IF page < 0 THEN
5689         RETURN QUERY
5690             -- "bottom" half of the browse results
5691             SELECT id FROM (
5692                 SELECT  ash.id,
5693                         row_number() over ()
5694                   FROM  authority.simple_heading ash
5695                   WHERE ash.atag = ANY (atag_list)
5696                         AND ash.sort_value < pivot_sort_value
5697                   ORDER BY ash.sort_value DESC
5698                   LIMIT pagesize
5699                   OFFSET (ABS(page) - 1) * pagesize
5700             ) x ORDER BY row_number DESC;
5701     END IF;
5702
5703     IF page >= 0 THEN
5704         RETURN QUERY
5705             -- "bottom" half of the browse results
5706             SELECT  ash.id
5707               FROM  authority.simple_heading ash
5708               WHERE ash.atag = ANY (atag_list)
5709                     AND ash.sort_value >= pivot_sort_value
5710               ORDER BY ash.sort_value
5711               LIMIT pagesize
5712               OFFSET ABS(page) * pagesize ;
5713     END IF;
5714 END;
5715 $$ LANGUAGE PLPGSQL ROWS 10;
5716
5717 CREATE OR REPLACE FUNCTION authority.simple_heading_search_rank( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5718     SELECT  ash.id
5719       FROM  authority.simple_heading ash,
5720             public.naco_normalize($2) t(term),
5721             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5722       WHERE ash.atag = ANY ($1)
5723             AND ash.index_vector @@ ptsq.term
5724       ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
5725                     + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
5726                     + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
5727       LIMIT $4
5728       OFFSET $4 * $3;
5729 $$ LANGUAGE SQL ROWS 10;
5730
5731 CREATE OR REPLACE FUNCTION authority.simple_heading_search_heading( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5732     SELECT  ash.id
5733       FROM  authority.simple_heading ash,
5734             public.naco_normalize($2) t(term),
5735             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5736       WHERE ash.atag = ANY ($1)
5737             AND ash.index_vector @@ ptsq.term
5738       ORDER BY ash.sort_value
5739       LIMIT $4
5740       OFFSET $4 * $3;
5741 $$ LANGUAGE SQL ROWS 10;
5742
5743
5744 CREATE OR REPLACE FUNCTION authority.axis_authority_tags(a TEXT) RETURNS INT[] AS $$
5745     SELECT ARRAY_ACCUM(field) FROM authority.browse_axis_authority_field_map WHERE axis = $1;
5746 $$ LANGUAGE SQL;
5747
5748 CREATE OR REPLACE FUNCTION authority.axis_authority_tags_refs(a TEXT) RETURNS INT[] AS $$
5749     SELECT  ARRAY_CAT(
5750                 ARRAY[a.field],
5751                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.field)
5752             )
5753       FROM  authority.browse_axis_authority_field_map a
5754       WHERE axis = $1
5755 $$ LANGUAGE SQL;
5756
5757
5758
5759 CREATE OR REPLACE FUNCTION authority.btag_authority_tags(btag TEXT) RETURNS INT[] AS $$
5760     SELECT ARRAY_ACCUM(authority_field) FROM authority.control_set_bib_field WHERE tag = $1
5761 $$ LANGUAGE SQL;
5762
5763 CREATE OR REPLACE FUNCTION authority.btag_authority_tags_refs(btag TEXT) RETURNS INT[] AS $$
5764     SELECT  ARRAY_CAT(
5765                 ARRAY[a.authority_field],
5766                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.authority_field)
5767             )
5768       FROM  authority.control_set_bib_field a
5769       WHERE a.tag = $1
5770 $$ LANGUAGE SQL;
5771
5772
5773
5774 CREATE OR REPLACE FUNCTION authority.atag_authority_tags(atag TEXT) RETURNS INT[] AS $$
5775     SELECT ARRAY_ACCUM(id) FROM authority.control_set_authority_field WHERE tag = $1
5776 $$ LANGUAGE SQL;
5777
5778 CREATE OR REPLACE FUNCTION authority.atag_authority_tags_refs(atag TEXT) RETURNS INT[] AS $$
5779     SELECT  ARRAY_CAT(
5780                 ARRAY[a.id],
5781                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.id)
5782             )
5783       FROM  authority.control_set_authority_field a
5784       WHERE a.tag = $1
5785 $$ LANGUAGE SQL;
5786
5787
5788 CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5789     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4)
5790 $$ LANGUAGE SQL ROWS 10;
5791
5792 CREATE OR REPLACE FUNCTION authority.btag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5793     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4)
5794 $$ LANGUAGE SQL ROWS 10;
5795
5796 CREATE OR REPLACE FUNCTION authority.atag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5797     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4)
5798 $$ LANGUAGE SQL ROWS 10;
5799
5800 CREATE OR REPLACE FUNCTION authority.axis_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5801     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4)
5802 $$ LANGUAGE SQL ROWS 10;
5803
5804 CREATE OR REPLACE FUNCTION authority.btag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5805     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4)
5806 $$ LANGUAGE SQL ROWS 10;
5807
5808 CREATE OR REPLACE FUNCTION authority.atag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5809     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4)
5810 $$ LANGUAGE SQL ROWS 10;
5811
5812
5813 CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5814     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4)
5815 $$ LANGUAGE SQL ROWS 10;
5816
5817 CREATE OR REPLACE FUNCTION authority.btag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5818     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4)
5819 $$ LANGUAGE SQL ROWS 10;
5820
5821 CREATE OR REPLACE FUNCTION authority.atag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5822     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4)
5823 $$ LANGUAGE SQL ROWS 10;
5824
5825 CREATE OR REPLACE FUNCTION authority.axis_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5826     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4)
5827 $$ LANGUAGE SQL ROWS 10;
5828
5829 CREATE OR REPLACE FUNCTION authority.btag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5830     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4)
5831 $$ LANGUAGE SQL ROWS 10;
5832
5833 CREATE OR REPLACE FUNCTION authority.atag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5834     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4)
5835 $$ LANGUAGE SQL ROWS 10;
5836
5837
5838 CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5839     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4)
5840 $$ LANGUAGE SQL ROWS 10;
5841
5842 CREATE OR REPLACE FUNCTION authority.btag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5843     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4)
5844 $$ LANGUAGE SQL ROWS 10;
5845
5846 CREATE OR REPLACE FUNCTION authority.atag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5847     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4)
5848 $$ LANGUAGE SQL ROWS 10;
5849
5850 CREATE OR REPLACE FUNCTION authority.axis_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5851     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4)
5852 $$ LANGUAGE SQL ROWS 10;
5853
5854 CREATE OR REPLACE FUNCTION authority.btag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5855     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4)
5856 $$ LANGUAGE SQL ROWS 10;
5857
5858 CREATE OR REPLACE FUNCTION authority.atag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5859     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4)
5860 $$ LANGUAGE SQL ROWS 10;
5861
5862
5863 CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5864     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4)
5865 $$ LANGUAGE SQL ROWS 10;
5866
5867 CREATE OR REPLACE FUNCTION authority.btag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5868     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4)
5869 $$ LANGUAGE SQL ROWS 10;
5870
5871 CREATE OR REPLACE FUNCTION authority.atag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5872     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4)
5873 $$ LANGUAGE SQL ROWS 10;
5874
5875 CREATE OR REPLACE FUNCTION authority.axis_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5876     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4)
5877 $$ LANGUAGE SQL ROWS 10;
5878
5879 CREATE OR REPLACE FUNCTION authority.btag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5880     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4)
5881 $$ LANGUAGE SQL ROWS 10;
5882
5883 CREATE OR REPLACE FUNCTION authority.atag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5884     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4)
5885 $$ LANGUAGE SQL ROWS 10;
5886
5887
5888
5889 -- Evergreen DB patch 0641.schema.org_unit_setting_json_check.sql
5890 --
5891 --
5892
5893 -- check whether patch can be applied
5894 SELECT evergreen.upgrade_deps_block_check('0641', :eg_version);
5895
5896 ALTER TABLE actor.org_unit_setting ADD CONSTRAINT aous_must_be_json CHECK ( is_json(value) );
5897
5898 -- Evergreen DB patch 0642.data.acq-worksheet-hold-count.sql
5899
5900 -- check whether patch can be applied
5901 SELECT evergreen.upgrade_deps_block_check('0642', :eg_version);
5902
5903 UPDATE action_trigger.event_definition SET template = 
5904 $$
5905 [%- USE date -%]
5906 [%- SET li = target; -%]
5907 <div class="wrapper">
5908     <div class="summary" style='font-size:110%; font-weight:bold;'>
5909
5910         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
5911         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
5912         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
5913         <div class="lineid">Lineitem ID: [% li.id %]</div>
5914         <div>Open Holds: [% helpers.bre_open_hold_count(li.eg_bib_id) %]</div>
5915
5916         [% IF li.distribution_formulas.size > 0 %]
5917             [% SET forms = [] %]
5918             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
5919             <div>Distribution Formulas: [% forms.join(',') %]</div>
5920         [% END %]
5921
5922         [% IF li.lineitem_notes.size > 0 %]
5923             Lineitem Notes:
5924             <ul>
5925                 [%- FOR note IN li.lineitem_notes -%]
5926                     <li>
5927                     [% IF note.alert_text %]
5928                         [% note.alert_text.code -%] 
5929                         [% IF note.value -%]
5930                             : [% note.value %]
5931                         [% END %]
5932                     [% ELSE %]
5933                         [% note.value -%] 
5934                     [% END %]
5935                     </li>
5936                 [% END %]
5937             </ul>
5938         [% END %]
5939     </div>
5940     <br/>
5941     <table>
5942         <thead>
5943             <tr>
5944                 <th>Branch</th>
5945                 <th>Barcode</th>
5946                 <th>Call Number</th>
5947                 <th>Fund</th>
5948                 <th>Shelving Location</th>
5949                 <th>Recd.</th>
5950                 <th>Notes</th>
5951             </tr>
5952         </thead>
5953         <tbody>
5954         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
5955             [% 
5956                 IF detail.eg_copy_id;
5957                     SET copy = detail.eg_copy_id;
5958                     SET cn_label = copy.call_number.label;
5959                 ELSE; 
5960                     SET copy = detail; 
5961                     SET cn_label = detail.cn_label;
5962                 END 
5963             %]
5964             <tr>
5965                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
5966                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
5967                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
5968                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
5969                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
5970                 <td style='padding:5px;'>[% copy.location.name %]</td>
5971                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
5972                 <td style='padding:5px;'>[% detail.note %]</td>
5973             </tr>
5974         [% END %]
5975         </tbody>
5976     </table>
5977 </div>
5978 $$
5979 WHERE id = 14;
5980
5981
5982 SELECT evergreen.upgrade_deps_block_check('0643', :eg_version);
5983
5984 DO $$
5985 DECLARE x TEXT;
5986 BEGIN
5987
5988     FOR x IN
5989         SELECT  marc
5990           FROM  authority.record_entry
5991           WHERE id > 0
5992                 AND NOT deleted
5993                 AND id NOT IN (SELECT DISTINCT record FROM authority.simple_heading)
5994     LOOP
5995         INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5996             SELECT record, atag, value, sort_value FROM authority.simple_heading_set(x);
5997     END LOOP;
5998 END;
5999 $$;
6000
6001
6002
6003 SELECT evergreen.upgrade_deps_block_check('0644', :eg_version);
6004
6005 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6006 ( 'circ.holds.target_when_closed', 'circ',
6007     oils_i18n_gettext('circ.holds.target_when_closed',
6008         'Target copies for a hold even if copy''s circ lib is closed',
6009         'coust', 'label'),
6010     oils_i18n_gettext('circ.holds.target_when_closed',
6011         'If this setting is true at a given org unit or one of its ancestors, the hold targeter will target copies from this org unit even if the org unit is closed (according to the actor.org_unit.closed_date table).',
6012         'coust', 'description'),
6013     'bool'),
6014 ( 'circ.holds.target_when_closed_if_at_pickup_lib', 'circ',
6015     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6016         'Target copies for a hold even if copy''s circ lib is closed IF the circ lib is the hold''s pickup lib',
6017         'coust', 'label'),
6018     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6019         'If this setting is true at a given org unit or one of its ancestors, the hold targeter will target copies from this org unit even if the org unit is closed (according to the actor.org_unit.closed_date table) IF AND ONLY IF the copy''s circ lib is the same as the hold''s pickup lib.',
6020         'coust', 'description'),
6021     'bool')
6022 ;
6023
6024 -- Evergreen DB patch XXXX.data.hold-notification-cleanup-mod.sql
6025
6026 -- check whether patch can be applied
6027 SELECT evergreen.upgrade_deps_block_check('0647', :eg_version);
6028
6029 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
6030     'CreateHoldNotification',
6031     oils_i18n_gettext(
6032         'CreateHoldNotification',
6033         'Creates a hold_notification record for each notified hold',
6034         'atclean',
6035         'description'
6036     )
6037 );
6038
6039 UPDATE action_trigger.event_definition 
6040     SET 
6041         cleanup_success = 'CreateHoldNotification' 
6042     WHERE 
6043         id = 5 -- stock hold-ready email event_def
6044         AND cleanup_success IS NULL; -- don't clobber any existing cleanup mod
6045
6046 -- Evergreen DB patch XXXX.schema.unnest-hold-permit-upgrade-script-repair.sql
6047 --
6048 -- This patch makes no changes to the baseline schema and is 
6049 -- only meant to repair a previous upgrade script.
6050 --
6051
6052 -- check whether patch can be applied
6053 SELECT evergreen.upgrade_deps_block_check('0651', :eg_version);
6054
6055 --Removed dupe action.hold_request_permit_test
6056
6057 -- Evergreen DB patch XXXX.data.vandelay-queue-bib-bucket-type.sql
6058 --
6059
6060 -- check whether patch can be applied
6061 SELECT evergreen.upgrade_deps_block_check('0652', :eg_version);
6062
6063 INSERT INTO container.biblio_record_entry_bucket_type (code, label) VALUES (
6064     'vandelay_queue',
6065     oils_i18n_gettext('vandelay_queue', 'Vandelay Queue', 'cbrebt', 'label')
6066 );
6067
6068 -- Evergreen DB patch XXXX.schema.unapi-indb-optional-org.sql
6069
6070 -- check whether patch can be applied
6071 SELECT evergreen.upgrade_deps_block_check('0653', :eg_version);
6072
6073 CREATE OR REPLACE FUNCTION evergreen.org_top() RETURNS SETOF actor.org_unit AS $$ SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1; $$ LANGUAGE SQL ROWS 1;
6074
6075 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT DEFAULT '-', 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$
6076 DECLARE
6077     layout          unapi.bre_output_layout%ROWTYPE;
6078     transform       config.xml_transform%ROWTYPE;
6079     item_format     TEXT;
6080     tmp_xml         TEXT;
6081     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
6082     ouid            INT;
6083     element_list    TEXT[];
6084 BEGIN
6085
6086     IF org = '-' OR org IS NULL THEN
6087         SELECT shortname INTO org FROM evergreen.org_top();
6088     END IF;
6089
6090     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6091     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6092
6093     IF layout.name IS NULL THEN
6094         RETURN NULL::XML;
6095     END IF;
6096
6097     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
6098     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
6099
6100     -- Gather the bib xml
6101     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
6102
6103     IF layout.title_element IS NOT NULL THEN
6104         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;
6105     END IF;
6106
6107     IF layout.description_element IS NOT NULL THEN
6108         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;
6109     END IF;
6110
6111     IF layout.creator_element IS NOT NULL THEN
6112         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;
6113     END IF;
6114
6115     IF layout.update_ts_element IS NOT NULL THEN
6116         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;
6117     END IF;
6118
6119     IF unapi_url IS NOT NULL THEN
6120         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;
6121     END IF;
6122
6123     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
6124
6125     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
6126     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
6127         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;
6128     END LOOP;
6129
6130     RETURN tmp_xml::XML;
6131 END;
6132 $F$ LANGUAGE PLPGSQL;
6133
6134 CREATE OR REPLACE FUNCTION unapi.bre ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit INT DEFAULT NULL, soffset INT DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
6135 DECLARE
6136     me      biblio.record_entry%ROWTYPE;
6137     layout  unapi.bre_output_layout%ROWTYPE;
6138     xfrm    config.xml_transform%ROWTYPE;
6139     ouid    INT;
6140     tmp_xml TEXT;
6141     top_el  TEXT;
6142     output  XML;
6143     hxml    XML;
6144     axml    XML;
6145 BEGIN
6146
6147     IF org = '-' OR org IS NULL THEN
6148         SELECT shortname INTO org FROM evergreen.org_top();
6149     END IF;
6150
6151     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6152
6153     IF ouid IS NULL THEN
6154         RETURN NULL::XML;
6155     END IF;
6156
6157     IF format = 'holdings_xml' THEN -- the special case
6158         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6159         RETURN output;
6160     END IF;
6161
6162     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6163
6164     IF layout.name IS NULL THEN
6165         RETURN NULL::XML;
6166     END IF;
6167
6168     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6169
6170     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6171
6172     -- grab SVF if we need them
6173     IF ('mra' = ANY (includes)) THEN 
6174         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6175     ELSE
6176         axml := NULL::XML;
6177     END IF;
6178
6179     -- grab hodlings if we need them
6180     IF ('holdings_xml' = ANY (includes)) THEN 
6181         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6182     ELSE
6183         hxml := NULL::XML;
6184     END IF;
6185
6186
6187     -- generate our item node
6188
6189
6190     IF format = 'marcxml' THEN
6191         tmp_xml := me.marc;
6192         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6193            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6194         END IF; 
6195     ELSE
6196         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6197     END IF;
6198
6199     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6200
6201     IF axml IS NOT NULL THEN 
6202         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6203     END IF;
6204
6205     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6206         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6207     END IF;
6208
6209     IF ('bre.unapi' = ANY (includes)) THEN 
6210         output := REGEXP_REPLACE(
6211             tmp_xml,
6212             '</' || top_el || '>(.*?)',
6213             XMLELEMENT(
6214                 name abbr,
6215                 XMLATTRIBUTES(
6216                     'http://www.w3.org/1999/xhtml' AS xmlns,
6217                     'unapi-id' AS class,
6218                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6219                 )
6220             )::TEXT || '</' || top_el || E'>\\1'
6221         );
6222     ELSE
6223         output := tmp_xml;
6224     END IF;
6225
6226     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6227     RETURN output;
6228 END;
6229 $F$ LANGUAGE PLPGSQL;
6230
6231
6232
6233
6234 SELECT evergreen.upgrade_deps_block_check('0654', :eg_version);
6235
6236 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6237  ( 514, 'UPDATE_PATRON_ACTIVE_CARD', oils_i18n_gettext( 514,
6238     'Allows a user to manually adjust a patron''s active cards', 'ppl', 'description')),
6239  ( 515, 'UPDATE_PATRON_PRIMARY_CARD', oils_i18n_gettext( 515,
6240     'Allows a user to manually adjust a patron''s primary card', 'ppl', 'description'));
6241
6242 -- Evergreen DB patch 0655.config.bib_source.can_have_copies.sql
6243 --
6244 -- This column introduces the ability to prevent bib records associated
6245 -- with specific bib sources from being able to have volumes or MFHD
6246 -- records attached to them.
6247 --
6248
6249 -- check whether patch can be applied
6250 SELECT evergreen.upgrade_deps_block_check('0655', :eg_version);
6251
6252 ALTER TABLE config.bib_source
6253 ADD COLUMN can_have_copies BOOL NOT NULL DEFAULT TRUE;
6254
6255 -- Evergreen DB patch XXXX.LP893315_schema.function.filter_deleted_acns_from_unapi.holdings_xml.sql
6256 --
6257 -- Prevent deleted call numbers from hiding active call numbers / copies / URIs
6258 --
6259
6260 -- check whether patch can be applied
6261 SELECT evergreen.upgrade_deps_block_check('0656', :eg_version);
6262
6263 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$
6264      SELECT  XMLELEMENT(
6265                  name holdings,
6266                  XMLATTRIBUTES(
6267                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6268                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6269                  ),
6270                  XMLELEMENT(
6271                      name counts,
6272                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
6273                          SELECT  XMLELEMENT(
6274                                      name count,
6275                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6276                                  )::text
6277                            FROM  asset.opac_ou_record_copy_count($2,  $1)
6278                                      UNION
6279                          SELECT  XMLELEMENT(
6280                                      name count,
6281                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6282                                  )::text
6283                            FROM  asset.staff_ou_record_copy_count($2, $1)
6284                                      ORDER BY 1
6285                      )x)
6286                  ),
6287                  CASE 
6288                      WHEN ('bmp' = ANY ($5)) THEN
6289                         XMLELEMENT(
6290                             name monograph_parts,
6291                             (SELECT XMLAGG(bmp) FROM (
6292                                 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)
6293                                   FROM  biblio.monograph_part
6294                                   WHERE record = $1
6295                             )x)
6296                         )
6297                      ELSE NULL
6298                  END,
6299                  XMLELEMENT(
6300                      name volumes,
6301                      (SELECT XMLAGG(acn) FROM (
6302                         SELECT  unapi.acn(acn.id,'xml','volume',array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE)
6303                           FROM  asset.call_number acn
6304                           WHERE acn.record = $1
6305                                 AND acn.deleted IS FALSE
6306                                 AND EXISTS (
6307                                     SELECT  1
6308                                       FROM  asset.copy acp
6309                                             JOIN actor.org_unit_descendants(
6310                                                 $2,
6311                                                 (COALESCE(
6312                                                     $4,
6313                                                     (SELECT aout.depth
6314                                                       FROM  actor.org_unit_type aout
6315                                                             JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6316                                                     )
6317                                                 ))
6318                                             ) aoud ON (acp.circ_lib = aoud.id)
6319                                       LIMIT 1
6320                                )
6321                           ORDER BY label_sortkey
6322                           LIMIT $6
6323                           OFFSET $7
6324                      )x)
6325                  ),
6326                  CASE WHEN ('ssub' = ANY ($5)) THEN 
6327                      XMLELEMENT(
6328                          name subscriptions,
6329                          (SELECT XMLAGG(ssub) FROM (
6330                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6331                               FROM  serial.subscription
6332                               WHERE record_entry = $1
6333                         )x)
6334                      )
6335                  ELSE NULL END,
6336                  CASE WHEN ('acp' = ANY ($5)) THEN 
6337                      XMLELEMENT(
6338                          name foreign_copies,
6339                          (SELECT XMLAGG(acp) FROM (
6340                             SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6341                               FROM  biblio.peer_bib_copy_map p
6342                                     JOIN asset.copy c ON (p.target_copy = c.id)
6343                               WHERE NOT c.deleted AND peer_record = $1
6344                         )x)
6345                      )
6346                  ELSE NULL END
6347              );
6348 $F$ LANGUAGE SQL;
6349
6350 -- Evergreen DB patch 0657.schema.address-alert.sql
6351 --
6352
6353 -- check whether patch can be applied
6354 SELECT evergreen.upgrade_deps_block_check('0657', :eg_version);
6355
6356 CREATE TABLE actor.address_alert (
6357     id              SERIAL  PRIMARY KEY,
6358     owner           INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6359     active          BOOL    NOT NULL DEFAULT TRUE,
6360     match_all       BOOL    NOT NULL DEFAULT TRUE,
6361     alert_message   TEXT    NOT NULL,
6362     street1         TEXT,
6363     street2         TEXT,
6364     city            TEXT,
6365     county          TEXT,
6366     state           TEXT,
6367     country         TEXT,
6368     post_code       TEXT,
6369     mailing_address BOOL    NOT NULL DEFAULT FALSE,
6370     billing_address BOOL    NOT NULL DEFAULT FALSE
6371 );
6372
6373 CREATE OR REPLACE FUNCTION actor.address_alert_matches (
6374         org_unit INT, 
6375         street1 TEXT, 
6376         street2 TEXT, 
6377         city TEXT, 
6378         county TEXT, 
6379         state TEXT, 
6380         country TEXT, 
6381         post_code TEXT,
6382         mailing_address BOOL DEFAULT FALSE,
6383         billing_address BOOL DEFAULT FALSE
6384     ) RETURNS SETOF actor.address_alert AS $$
6385
6386 SELECT *
6387 FROM actor.address_alert
6388 WHERE
6389     active
6390     AND owner IN (SELECT id FROM actor.org_unit_ancestors($1)) 
6391     AND (
6392         (NOT mailing_address AND NOT billing_address)
6393         OR (mailing_address AND $9)
6394         OR (billing_address AND $10)
6395     )
6396     AND (
6397             (
6398                 match_all
6399                 AND COALESCE($2, '') ~* COALESCE(street1,   '.*')
6400                 AND COALESCE($3, '') ~* COALESCE(street2,   '.*')
6401                 AND COALESCE($4, '') ~* COALESCE(city,      '.*')
6402                 AND COALESCE($5, '') ~* COALESCE(county,    '.*')
6403                 AND COALESCE($6, '') ~* COALESCE(state,     '.*')
6404                 AND COALESCE($7, '') ~* COALESCE(country,   '.*')
6405                 AND COALESCE($8, '') ~* COALESCE(post_code, '.*')
6406             ) OR (
6407                 NOT match_all 
6408                 AND (  
6409                        $2 ~* street1
6410                     OR $3 ~* street2
6411                     OR $4 ~* city
6412                     OR $5 ~* county
6413                     OR $6 ~* state
6414                     OR $7 ~* country
6415                     OR $8 ~* post_code
6416                 )
6417             )
6418         )
6419     ORDER BY actor.org_unit_proximity(owner, $1)
6420 $$ LANGUAGE SQL;
6421
6422
6423 /* UNDO
6424 DROP FUNCTION actor.address_alert_matches(INT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, BOOL, BOOL);
6425 DROP TABLE actor.address_alert;
6426 */
6427 -- Evergreen DB patch 0659.add_create_report_perms.sql
6428 --
6429 -- Add a permission to control the ability to create report templates
6430 --
6431
6432 -- check whether patch can be applied
6433 SELECT evergreen.upgrade_deps_block_check('0659', :eg_version);
6434
6435 -- FIXME: add/check SQL statements to perform the upgrade
6436 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6437  ( 516, 'CREATE_REPORT_TEMPLATE', oils_i18n_gettext( 516,
6438     'Allows a user to create report templates', 'ppl', 'description' ));
6439
6440 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6441     SELECT grp, 516, depth, grantable
6442         FROM permission.grp_perm_map
6443         WHERE perm = (
6444             SELECT id
6445                 FROM permission.perm_list
6446                 WHERE code = 'RUN_REPORTS'
6447         );
6448
6449
6450
6451 SELECT evergreen.upgrade_deps_block_check('0660', :eg_version);
6452
6453 UPDATE action_trigger.event_definition SET template = $$
6454 [%-
6455 # target is the bookbag itself. The 'items' variable does not need to be in
6456 # the environment because a special reactor will take care of filling it in.
6457
6458 FOR item IN items;
6459     bibxml = helpers.unapi_bre(item.target_biblio_record_entry, {flesh => '{mra}'});
6460     title = "";
6461     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
6462         title = title _ part.textContent;
6463     END;
6464     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
6465     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
6466
6467     helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% helpers.csv_datum(item_type) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
6468 END -%]
6469 $$
6470 WHERE reactor = 'ContainerCSV';
6471
6472 -- Evergreen DB patch 0661.data.yaous-opac-tag-circed-items.sql
6473 --
6474 -- Add org unit setting that enables users who have opted in to
6475 -- tracking their circulation history to see which items they
6476 -- have previously checked out in search results.
6477 --
6478
6479 -- check whether patch can be applied
6480 SELECT evergreen.upgrade_deps_block_check('0661', :eg_version);
6481
6482 INSERT into config.org_unit_setting_type 
6483     (name, grp, label, description, datatype) 
6484     VALUES ( 
6485         'opac.search.tag_circulated_items', 
6486         'opac',
6487         oils_i18n_gettext(
6488             'opac.search.tag_circulated_items',
6489             'Tag Circulated Items in Results',
6490             'coust', 
6491             'label'
6492         ),
6493         oils_i18n_gettext(
6494             'opac.search.tag_circulated_items',
6495             'When a user is both logged in and has opted in to circulation history tracking, turning on this setting will cause previous (or currently) circulated items to be highlighted in search results',
6496             'coust', 
6497             'description'
6498         ),
6499         'bool'
6500     );
6501
6502
6503 -- Evergreen DB patch 0662.schema.coded-value-map-index-normalizer.sql
6504 --
6505
6506 -- check whether patch can be applied
6507 SELECT evergreen.upgrade_deps_block_check('0662', :eg_version);
6508
6509 -- create the normalizer
6510 CREATE OR REPLACE FUNCTION evergreen.coded_value_map_normalizer( input TEXT, ctype TEXT ) 
6511     RETURNS TEXT AS $F$
6512         SELECT COALESCE(value,$1) 
6513             FROM config.coded_value_map 
6514             WHERE ctype = $2 AND code = $1;
6515 $F$ LANGUAGE SQL;
6516
6517 -- register the normalizer
6518 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6519     'Coded Value Map Normalizer', 
6520     'Applies coded_value_map mapping of values',
6521     'coded_value_map_normalizer', 
6522     1
6523 );
6524
6525 -- Evergreen DB patch 0663.schema.archive_circ_stat_cats.sql
6526 --
6527 -- Enables users to set copy and patron stat cats to be archivable
6528 -- for the purposes of statistics even after the circs are aged.
6529 --
6530
6531 -- check whether patch can be applied
6532 SELECT evergreen.upgrade_deps_block_check('0663', :eg_version);
6533
6534 -- New tables
6535
6536 CREATE TABLE action.archive_actor_stat_cat (
6537     id          BIGSERIAL   PRIMARY KEY,
6538     xact        BIGINT      NOT NULL,
6539     stat_cat    INT         NOT NULL,
6540     value       TEXT        NOT NULL
6541 );
6542
6543 CREATE TABLE action.archive_asset_stat_cat (
6544     id          BIGSERIAL   PRIMARY KEY,
6545     xact        BIGINT      NOT NULL,
6546     stat_cat    INT         NOT NULL,
6547     value       TEXT        NOT NULL
6548 );
6549
6550 -- Add columns to existing tables
6551
6552 -- Archive Flag Columns
6553 ALTER TABLE actor.stat_cat
6554     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6555 ALTER TABLE asset.stat_cat
6556     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6557
6558 -- Circulation copy column
6559 ALTER TABLE action.circulation
6560     ADD COLUMN copy_location INT NULL REFERENCES asset.copy_location(id) DEFERRABLE INITIALLY DEFERRED;
6561
6562 -- Create trigger function to auto-fill the copy_location field
6563 CREATE OR REPLACE FUNCTION action.fill_circ_copy_location () RETURNS TRIGGER AS $$
6564 BEGIN
6565     SELECT INTO NEW.copy_location location FROM asset.copy WHERE id = NEW.target_copy;
6566     RETURN NEW;
6567 END;
6568 $$ LANGUAGE PLPGSQL;
6569
6570 -- Create trigger function to auto-archive stat cat entries
6571 CREATE OR REPLACE FUNCTION action.archive_stat_cats () RETURNS TRIGGER AS $$
6572 BEGIN
6573     INSERT INTO action.archive_actor_stat_cat(xact, stat_cat, value)
6574         SELECT NEW.id, asceum.stat_cat, asceum.stat_cat_entry
6575         FROM actor.stat_cat_entry_usr_map asceum
6576              JOIN actor.stat_cat sc ON asceum.stat_cat = sc.id
6577         WHERE NEW.usr = asceum.target_usr AND sc.checkout_archive;
6578     INSERT INTO action.archive_asset_stat_cat(xact, stat_cat, value)
6579         SELECT NEW.id, ascecm.stat_cat, asce.value
6580         FROM asset.stat_cat_entry_copy_map ascecm
6581              JOIN asset.stat_cat sc ON ascecm.stat_cat = sc.id
6582              JOIN asset.stat_cat_entry asce ON ascecm.stat_cat_entry = asce.id
6583         WHERE NEW.target_copy = ascecm.owning_copy AND sc.checkout_archive;
6584     RETURN NULL;
6585 END;
6586 $$ LANGUAGE PLPGSQL;
6587
6588 -- Apply triggers
6589 CREATE TRIGGER fill_circ_copy_location_tgr BEFORE INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.fill_circ_copy_location();
6590 CREATE TRIGGER archive_stat_cats_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.archive_stat_cats();
6591
6592 -- Ensure all triggers are disabled for speedy updates!
6593 ALTER TABLE action.circulation DISABLE TRIGGER ALL;
6594
6595 -- Update view to use circ's copy_location field instead of the copy's current copy_location field
6596 CREATE OR REPLACE VIEW action.all_circulation AS
6597     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6598         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6599         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
6600         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6601         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6602         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6603       FROM  action.aged_circulation
6604             UNION ALL
6605     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,
6606         cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
6607         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6608         circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6609         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6610         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6611         circ.parent_circ
6612       FROM  action.circulation circ
6613         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6614         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6615         JOIN actor.usr p ON (circ.usr = p.id)
6616         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6617         LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
6618
6619 -- Update action.circulation with real copy_location numbers instead of all NULL
6620 DO $$BEGIN RAISE WARNING 'We are about to do an update on every row in action.circulation. This may take a while. %', timeofday(); END;$$;
6621 UPDATE action.circulation circ SET copy_location = ac.location FROM asset.copy ac WHERE ac.id = circ.target_copy;
6622
6623 -- Set not null/default on new column, re-enable triggers
6624 ALTER TABLE action.circulation
6625     ALTER COLUMN copy_location SET NOT NULL,
6626     ALTER COLUMN copy_location SET DEFAULT 1,
6627     ENABLE TRIGGER ALL;
6628
6629 -- Evergreen DB patch 0664.schema.hold-current-shelf-lib.sql
6630 --
6631 --
6632
6633
6634 -- check whether patch can be applied
6635 SELECT evergreen.upgrade_deps_block_check('0664', :eg_version);
6636
6637 -- add the new column
6638 ALTER TABLE action.hold_request ADD COLUMN current_shelf_lib 
6639     INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED;
6640
6641 -- Add some others before the UPDATE we are about to do breaks our ability to add columns
6642 -- But we need this table first.
6643 CREATE TABLE config.sms_carrier (
6644     id              SERIAL PRIMARY KEY,
6645     region          TEXT,
6646     name            TEXT,
6647     email_gateway   TEXT,
6648     active          BOOLEAN DEFAULT TRUE
6649 );
6650
6651 ALTER TABLE action.hold_request ADD COLUMN sms_notify TEXT;
6652 ALTER TABLE action.hold_request ADD COLUMN sms_carrier INT REFERENCES config.sms_carrier (id);
6653 ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
6654     sms_notify IS NULL
6655     OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
6656 );
6657
6658
6659
6660 -- set the value for current_shelf_lib on existing shelved holds
6661 UPDATE action.hold_request
6662     SET current_shelf_lib = pickup_lib
6663     FROM asset.copy
6664     WHERE 
6665             action.hold_request.shelf_time IS NOT NULL 
6666         AND action.hold_request.capture_time IS NOT NULL
6667         AND action.hold_request.current_copy IS NOT NULL
6668         AND action.hold_request.fulfillment_time IS NULL
6669         AND action.hold_request.cancel_time IS NULL
6670         AND asset.copy.id = action.hold_request.current_copy
6671         AND asset.copy.status = 8; -- on holds shelf
6672
6673
6674 SELECT evergreen.upgrade_deps_block_check('0666', :eg_version);
6675
6676 -- 950.data.seed-values.sql
6677 INSERT INTO config.settings_group (name, label) VALUES
6678     (
6679         'sms',
6680         oils_i18n_gettext(
6681             'sms',
6682             'SMS Text Messages',
6683             'csg',
6684             'label'
6685         )
6686     )
6687 ;
6688
6689 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6690     (
6691         'sms.enable',
6692         'sms',
6693         oils_i18n_gettext(
6694             'sms.enable',
6695             'Enable features that send SMS text messages.',
6696             'coust',
6697             'label'
6698         ),
6699         oils_i18n_gettext(
6700             'sms.enable',
6701             'Current features that use SMS include hold-ready-for-pickup notifications and a "Send Text" action for call numbers in the OPAC. If this setting is not enabled, the SMS options will not be offered to the user.  Unless you are carefully silo-ing patrons and their use of the OPAC, the context org for this setting should be the top org in the org hierarchy, otherwise patrons can trample their user settings when jumping between orgs.',
6702             'coust',
6703             'description'
6704         ),
6705         'bool'
6706     )
6707     ,(
6708         'sms.disable_authentication_requirement.callnumbers',
6709         'sms',
6710         oils_i18n_gettext(
6711             'sms.disable_authentication_requirement.callnumbers',
6712             'Disable auth requirement for texting call numbers.',
6713             'coust',
6714             'label'
6715         ),
6716         oils_i18n_gettext(
6717             'sms.disable_authentication_requirement.callnumbers',
6718             'Disable authentication requirement for sending call number information via SMS from the OPAC.',
6719             'coust',
6720             'description'
6721         ),
6722         'bool'
6723     )
6724 ;
6725
6726 -- 090.schema.action.sql
6727 -- 950.data.seed-values.sql
6728 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
6729     'opac.default_sms_carrier',
6730     'sms',
6731     TRUE,
6732     oils_i18n_gettext(
6733         'opac.default_sms_carrier',
6734         'Default SMS/Text Carrier',
6735         'cust',
6736         'label'
6737     ),
6738     oils_i18n_gettext(
6739         'opac.default_sms_carrier',
6740         'Default SMS/Text Carrier',
6741         'cust',
6742         'description'
6743     ),
6744     'link',
6745     'csc'
6746 );
6747
6748 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6749     'opac.default_sms_notify',
6750     'sms',
6751     TRUE,
6752     oils_i18n_gettext(
6753         'opac.default_sms_notify',
6754         'Default SMS/Text Number',
6755         'cust',
6756         'label'
6757     ),
6758     oils_i18n_gettext(
6759         'opac.default_sms_notify',
6760         'Default SMS/Text Number',
6761         'cust',
6762         'description'
6763     ),
6764     'string'
6765 );
6766
6767 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6768     'opac.default_phone',
6769     'opac',
6770     TRUE,
6771     oils_i18n_gettext(
6772         'opac.default_phone',
6773         'Default Phone Number',
6774         'cust',
6775         'label'
6776     ),
6777     oils_i18n_gettext(
6778         'opac.default_phone',
6779         'Default Phone Number',
6780         'cust',
6781         'description'
6782     ),
6783     'string'
6784 );
6785
6786 SELECT setval( 'config.sms_carrier_id_seq', 1000 );
6787 INSERT INTO config.sms_carrier VALUES
6788
6789     -- Testing
6790     (
6791         1,
6792         oils_i18n_gettext(
6793             1,
6794             'Local',
6795             'csc',
6796             'region'
6797         ),
6798         oils_i18n_gettext(
6799             1,
6800             'Test Carrier',
6801             'csc',
6802             'name'
6803         ),
6804         'opensrf+$number@localhost',
6805         FALSE
6806     ),
6807
6808     -- Canada & USA
6809     (
6810         2,
6811         oils_i18n_gettext(
6812             2,
6813             'Canada & USA',
6814             'csc',
6815             'region'
6816         ),
6817         oils_i18n_gettext(
6818             2,
6819             'Rogers Wireless',
6820             'csc',
6821             'name'
6822         ),
6823         '$number@pcs.rogers.com',
6824         TRUE
6825     ),
6826     (
6827         3,
6828         oils_i18n_gettext(
6829             3,
6830             'Canada & USA',
6831             'csc',
6832             'region'
6833         ),
6834         oils_i18n_gettext(
6835             3,
6836             'Rogers Wireless (Alternate)',
6837             'csc',
6838             'name'
6839         ),
6840         '1$number@mms.rogers.com',
6841         TRUE
6842     ),
6843     (
6844         4,
6845         oils_i18n_gettext(
6846             4,
6847             'Canada & USA',
6848             'csc',
6849             'region'
6850         ),
6851         oils_i18n_gettext(
6852             4,
6853             'Telus Mobility',
6854             'csc',
6855             'name'
6856         ),
6857         '$number@msg.telus.com',
6858         TRUE
6859     ),
6860
6861     -- Canada
6862     (
6863         5,
6864         oils_i18n_gettext(
6865             5,
6866             'Canada',
6867             'csc',
6868             'region'
6869         ),
6870         oils_i18n_gettext(
6871             5,
6872             'Koodo Mobile',
6873             'csc',
6874             'name'
6875         ),
6876         '$number@msg.telus.com',
6877         TRUE
6878     ),
6879     (
6880         6,
6881         oils_i18n_gettext(
6882             6,
6883             'Canada',
6884             'csc',
6885             'region'
6886         ),
6887         oils_i18n_gettext(
6888             6,
6889             'Fido',
6890             'csc',
6891             'name'
6892         ),
6893         '$number@fido.ca',
6894         TRUE
6895     ),
6896     (
6897         7,
6898         oils_i18n_gettext(
6899             7,
6900             'Canada',
6901             'csc',
6902             'region'
6903         ),
6904         oils_i18n_gettext(
6905             7,
6906             'Bell Mobility & Solo Mobile',
6907             'csc',
6908             'name'
6909         ),
6910         '$number@txt.bell.ca',
6911         TRUE
6912     ),
6913     (
6914         8,
6915         oils_i18n_gettext(
6916             8,
6917             'Canada',
6918             'csc',
6919             'region'
6920         ),
6921         oils_i18n_gettext(
6922             8,
6923             'Bell Mobility & Solo Mobile (Alternate)',
6924             'csc',
6925             'name'
6926         ),
6927         '$number@txt.bellmobility.ca',
6928         TRUE
6929     ),
6930     (
6931         9,
6932         oils_i18n_gettext(
6933             9,
6934             'Canada',
6935             'csc',
6936             'region'
6937         ),
6938         oils_i18n_gettext(
6939             9,
6940             'Aliant',
6941             'csc',
6942             'name'
6943         ),
6944         '$number@sms.wirefree.informe.ca',
6945         TRUE
6946     ),
6947     (
6948         10,
6949         oils_i18n_gettext(
6950             10,
6951             'Canada',
6952             'csc',
6953             'region'
6954         ),
6955         oils_i18n_gettext(
6956             10,
6957             'PC Telecom',
6958             'csc',
6959             'name'
6960         ),
6961         '$number@mobiletxt.ca',
6962         TRUE
6963     ),
6964     (
6965         11,
6966         oils_i18n_gettext(
6967             11,
6968             'Canada',
6969             'csc',
6970             'region'
6971         ),
6972         oils_i18n_gettext(
6973             11,
6974             'SaskTel',
6975             'csc',
6976             'name'
6977         ),
6978         '$number@sms.sasktel.com',
6979         TRUE
6980     ),
6981     (
6982         12,
6983         oils_i18n_gettext(
6984             12,
6985             'Canada',
6986             'csc',
6987             'region'
6988         ),
6989         oils_i18n_gettext(
6990             12,
6991             'MTS Mobility',
6992             'csc',
6993             'name'
6994         ),
6995         '$number@text.mtsmobility.com',
6996         TRUE
6997     ),
6998     (
6999         13,
7000         oils_i18n_gettext(
7001             13,
7002             'Canada',
7003             'csc',
7004             'region'
7005         ),
7006         oils_i18n_gettext(
7007             13,
7008             'Virgin Mobile',
7009             'csc',
7010             'name'
7011         ),
7012         '$number@vmobile.ca',
7013         TRUE
7014     ),
7015
7016     -- International
7017     (
7018         14,
7019         oils_i18n_gettext(
7020             14,
7021             'International',
7022             'csc',
7023             'region'
7024         ),
7025         oils_i18n_gettext(
7026             14,
7027             'Iridium',
7028             'csc',
7029             'name'
7030         ),
7031         '$number@msg.iridium.com',
7032         TRUE
7033     ),
7034     (
7035         15,
7036         oils_i18n_gettext(
7037             15,
7038             'International',
7039             'csc',
7040             'region'
7041         ),
7042         oils_i18n_gettext(
7043             15,
7044             'Globalstar',
7045             'csc',
7046             'name'
7047         ),
7048         '$number@msg.globalstarusa.com',
7049         TRUE
7050     ),
7051     (
7052         16,
7053         oils_i18n_gettext(
7054             16,
7055             'International',
7056             'csc',
7057             'region'
7058         ),
7059         oils_i18n_gettext(
7060             16,
7061             'Bulletin.net',
7062             'csc',
7063             'name'
7064         ),
7065         '$number@bulletinmessenger.net', -- International Formatted number
7066         TRUE
7067     ),
7068     (
7069         17,
7070         oils_i18n_gettext(
7071             17,
7072             'International',
7073             'csc',
7074             'region'
7075         ),
7076         oils_i18n_gettext(
7077             17,
7078             'Panacea Mobile',
7079             'csc',
7080             'name'
7081         ),
7082         '$number@api.panaceamobile.com',
7083         TRUE
7084     ),
7085
7086     -- USA
7087     (
7088         18,
7089         oils_i18n_gettext(
7090             18,
7091             'USA',
7092             'csc',
7093             'region'
7094         ),
7095         oils_i18n_gettext(
7096             18,
7097             'C Beyond',
7098             'csc',
7099             'name'
7100         ),
7101         '$number@cbeyond.sprintpcs.com',
7102         TRUE
7103     ),
7104     (
7105         19,
7106         oils_i18n_gettext(
7107             19,
7108             'Alaska, USA',
7109             'csc',
7110             'region'
7111         ),
7112         oils_i18n_gettext(
7113             19,
7114             'General Communications, Inc.',
7115             'csc',
7116             'name'
7117         ),
7118         '$number@mobile.gci.net',
7119         TRUE
7120     ),
7121     (
7122         20,
7123         oils_i18n_gettext(
7124             20,
7125             'California, USA',
7126             'csc',
7127             'region'
7128         ),
7129         oils_i18n_gettext(
7130             20,
7131             'Golden State Cellular',
7132             'csc',
7133             'name'
7134         ),
7135         '$number@gscsms.com',
7136         TRUE
7137     ),
7138     (
7139         21,
7140         oils_i18n_gettext(
7141             21,
7142             'Cincinnati, Ohio, USA',
7143             'csc',
7144             'region'
7145         ),
7146         oils_i18n_gettext(
7147             21,
7148             'Cincinnati Bell',
7149             'csc',
7150             'name'
7151         ),
7152         '$number@gocbw.com',
7153         TRUE
7154     ),
7155     (
7156         22,
7157         oils_i18n_gettext(
7158             22,
7159             'Hawaii, USA',
7160             'csc',
7161             'region'
7162         ),
7163         oils_i18n_gettext(
7164             22,
7165             'Hawaiian Telcom Wireless',
7166             'csc',
7167             'name'
7168         ),
7169         '$number@hawaii.sprintpcs.com',
7170         TRUE
7171     ),
7172     (
7173         23,
7174         oils_i18n_gettext(
7175             23,
7176             'Midwest, USA',
7177             'csc',
7178             'region'
7179         ),
7180         oils_i18n_gettext(
7181             23,
7182             'i wireless (T-Mobile)',
7183             'csc',
7184             'name'
7185         ),
7186         '$number.iws@iwspcs.net',
7187         TRUE
7188     ),
7189     (
7190         24,
7191         oils_i18n_gettext(
7192             24,
7193             'USA',
7194             'csc',
7195             'region'
7196         ),
7197         oils_i18n_gettext(
7198             24,
7199             'i-wireless (Sprint PCS)',
7200             'csc',
7201             'name'
7202         ),
7203         '$number@iwirelesshometext.com',
7204         TRUE
7205     ),
7206     (
7207         25,
7208         oils_i18n_gettext(
7209             25,
7210             'USA',
7211             'csc',
7212             'region'
7213         ),
7214         oils_i18n_gettext(
7215             25,
7216             'MetroPCS',
7217             'csc',
7218             'name'
7219         ),
7220         '$number@mymetropcs.com',
7221         TRUE
7222     ),
7223     (
7224         26,
7225         oils_i18n_gettext(
7226             26,
7227             'USA',
7228             'csc',
7229             'region'
7230         ),
7231         oils_i18n_gettext(
7232             26,
7233             'Kajeet',
7234             'csc',
7235             'name'
7236         ),
7237         '$number@mobile.kajeet.net',
7238         TRUE
7239     ),
7240     (
7241         27,
7242         oils_i18n_gettext(
7243             27,
7244             'USA',
7245             'csc',
7246             'region'
7247         ),
7248         oils_i18n_gettext(
7249             27,
7250             'Element Mobile',
7251             'csc',
7252             'name'
7253         ),
7254         '$number@SMS.elementmobile.net',
7255         TRUE
7256     ),
7257     (
7258         28,
7259         oils_i18n_gettext(
7260             28,
7261             'USA',
7262             'csc',
7263             'region'
7264         ),
7265         oils_i18n_gettext(
7266             28,
7267             'Esendex',
7268             'csc',
7269             'name'
7270         ),
7271         '$number@echoemail.net',
7272         TRUE
7273     ),
7274     (
7275         29,
7276         oils_i18n_gettext(
7277             29,
7278             'USA',
7279             'csc',
7280             'region'
7281         ),
7282         oils_i18n_gettext(
7283             29,
7284             'Boost Mobile',
7285             'csc',
7286             'name'
7287         ),
7288         '$number@myboostmobile.com',
7289         TRUE
7290     ),
7291     (
7292         30,
7293         oils_i18n_gettext(
7294             30,
7295             'USA',
7296             'csc',
7297             'region'
7298         ),
7299         oils_i18n_gettext(
7300             30,
7301             'BellSouth',
7302             'csc',
7303             'name'
7304         ),
7305         '$number@bellsouth.com',
7306         TRUE
7307     ),
7308     (
7309         31,
7310         oils_i18n_gettext(
7311             31,
7312             'USA',
7313             'csc',
7314             'region'
7315         ),
7316         oils_i18n_gettext(
7317             31,
7318             'Bluegrass Cellular',
7319             'csc',
7320             'name'
7321         ),
7322         '$number@sms.bluecell.com',
7323         TRUE
7324     ),
7325     (
7326         32,
7327         oils_i18n_gettext(
7328             32,
7329             'USA',
7330             'csc',
7331             'region'
7332         ),
7333         oils_i18n_gettext(
7334             32,
7335             'AT&T Enterprise Paging',
7336             'csc',
7337             'name'
7338         ),
7339         '$number@page.att.net',
7340         TRUE
7341     ),
7342     (
7343         33,
7344         oils_i18n_gettext(
7345             33,
7346             'USA',
7347             'csc',
7348             'region'
7349         ),
7350         oils_i18n_gettext(
7351             33,
7352             'AT&T Mobility/Wireless',
7353             'csc',
7354             'name'
7355         ),
7356         '$number@txt.att.net',
7357         TRUE
7358     ),
7359     (
7360         34,
7361         oils_i18n_gettext(
7362             34,
7363             'USA',
7364             'csc',
7365             'region'
7366         ),
7367         oils_i18n_gettext(
7368             34,
7369             'AT&T Global Smart Messaging Suite',
7370             'csc',
7371             'name'
7372         ),
7373         '$number@sms.smartmessagingsuite.com',
7374         TRUE
7375     ),
7376     (
7377         35,
7378         oils_i18n_gettext(
7379             35,
7380             'USA',
7381             'csc',
7382             'region'
7383         ),
7384         oils_i18n_gettext(
7385             35,
7386             'Alltel (Allied Wireless)',
7387             'csc',
7388             'name'
7389         ),
7390         '$number@sms.alltelwireless.com',
7391         TRUE
7392     ),
7393     (
7394         36,
7395         oils_i18n_gettext(
7396             36,
7397             'USA',
7398             'csc',
7399             'region'
7400         ),
7401         oils_i18n_gettext(
7402             36,
7403             'Alaska Communications',
7404             'csc',
7405             'name'
7406         ),
7407         '$number@msg.acsalaska.com',
7408         TRUE
7409     ),
7410     (
7411         37,
7412         oils_i18n_gettext(
7413             37,
7414             'USA',
7415             'csc',
7416             'region'
7417         ),
7418         oils_i18n_gettext(
7419             37,
7420             'Ameritech',
7421             'csc',
7422             'name'
7423         ),
7424         '$number@paging.acswireless.com',
7425         TRUE
7426     ),
7427     (
7428         38,
7429         oils_i18n_gettext(
7430             38,
7431             'USA',
7432             'csc',
7433             'region'
7434         ),
7435         oils_i18n_gettext(
7436             38,
7437             'Cingular (GoPhone prepaid)',
7438             'csc',
7439             'name'
7440         ),
7441         '$number@cingulartext.com',
7442         TRUE
7443     ),
7444     (
7445         39,
7446         oils_i18n_gettext(
7447             39,
7448             'USA',
7449             'csc',
7450             'region'
7451         ),
7452         oils_i18n_gettext(
7453             39,
7454             'Cingular (Postpaid)',
7455             'csc',
7456             'name'
7457         ),
7458         '$number@cingular.com',
7459         TRUE
7460     ),
7461     (
7462         40,
7463         oils_i18n_gettext(
7464             40,
7465             'USA',
7466             'csc',
7467             'region'
7468         ),
7469         oils_i18n_gettext(
7470             40,
7471             'Cellular One (Dobson) / O2 / Orange',
7472             'csc',
7473             'name'
7474         ),
7475         '$number@mobile.celloneusa.com',
7476         TRUE
7477     ),
7478     (
7479         41,
7480         oils_i18n_gettext(
7481             41,
7482             'USA',
7483             'csc',
7484             'region'
7485         ),
7486         oils_i18n_gettext(
7487             41,
7488             'Cellular South',
7489             'csc',
7490             'name'
7491         ),
7492         '$number@csouth1.com',
7493         TRUE
7494     ),
7495     (
7496         42,
7497         oils_i18n_gettext(
7498             42,
7499             'USA',
7500             'csc',
7501             'region'
7502         ),
7503         oils_i18n_gettext(
7504             42,
7505             'Cellcom',
7506             'csc',
7507             'name'
7508         ),
7509         '$number@cellcom.quiktxt.com',
7510         TRUE
7511     ),
7512     (
7513         43,
7514         oils_i18n_gettext(
7515             43,
7516             'USA',
7517             'csc',
7518             'region'
7519         ),
7520         oils_i18n_gettext(
7521             43,
7522             'Chariton Valley Wireless',
7523             'csc',
7524             'name'
7525         ),
7526         '$number@sms.cvalley.net',
7527         TRUE
7528     ),
7529     (
7530         44,
7531         oils_i18n_gettext(
7532             44,
7533             'USA',
7534             'csc',
7535             'region'
7536         ),
7537         oils_i18n_gettext(
7538             44,
7539             'Cricket',
7540             'csc',
7541             'name'
7542         ),
7543         '$number@sms.mycricket.com',
7544         TRUE
7545     ),
7546     (
7547         45,
7548         oils_i18n_gettext(
7549             45,
7550             'USA',
7551             'csc',
7552             'region'
7553         ),
7554         oils_i18n_gettext(
7555             45,
7556             'Cleartalk Wireless',
7557             'csc',
7558             'name'
7559         ),
7560         '$number@sms.cleartalk.us',
7561         TRUE
7562     ),
7563     (
7564         46,
7565         oils_i18n_gettext(
7566             46,
7567             'USA',
7568             'csc',
7569             'region'
7570         ),
7571         oils_i18n_gettext(
7572             46,
7573             'Edge Wireless',
7574             'csc',
7575             'name'
7576         ),
7577         '$number@sms.edgewireless.com',
7578         TRUE
7579     ),
7580     (
7581         47,
7582         oils_i18n_gettext(
7583             47,
7584             'USA',
7585             'csc',
7586             'region'
7587         ),
7588         oils_i18n_gettext(
7589             47,
7590             'Syringa Wireless',
7591             'csc',
7592             'name'
7593         ),
7594         '$number@rinasms.com',
7595         TRUE
7596     ),
7597     (
7598         48,
7599         oils_i18n_gettext(
7600             48,
7601             'USA',
7602             'csc',
7603             'region'
7604         ),
7605         oils_i18n_gettext(
7606             48,
7607             'T-Mobile',
7608             'csc',
7609             'name'
7610         ),
7611         '$number@tmomail.net',
7612         TRUE
7613     ),
7614     (
7615         49,
7616         oils_i18n_gettext(
7617             49,
7618             'USA',
7619             'csc',
7620             'region'
7621         ),
7622         oils_i18n_gettext(
7623             49,
7624             'Straight Talk / PagePlus Cellular',
7625             'csc',
7626             'name'
7627         ),
7628         '$number@vtext.com',
7629         TRUE
7630     ),
7631     (
7632         50,
7633         oils_i18n_gettext(
7634             50,
7635             'USA',
7636             'csc',
7637             'region'
7638         ),
7639         oils_i18n_gettext(
7640             50,
7641             'South Central Communications',
7642             'csc',
7643             'name'
7644         ),
7645         '$number@rinasms.com',
7646         TRUE
7647     ),
7648     (
7649         51,
7650         oils_i18n_gettext(
7651             51,
7652             'USA',
7653             'csc',
7654             'region'
7655         ),
7656         oils_i18n_gettext(
7657             51,
7658             'Simple Mobile',
7659             'csc',
7660             'name'
7661         ),
7662         '$number@smtext.com',
7663         TRUE
7664     ),
7665     (
7666         52,
7667         oils_i18n_gettext(
7668             52,
7669             'USA',
7670             'csc',
7671             'region'
7672         ),
7673         oils_i18n_gettext(
7674             52,
7675             'Sprint (PCS)',
7676             'csc',
7677             'name'
7678         ),
7679         '$number@messaging.sprintpcs.com',
7680         TRUE
7681     ),
7682     (
7683         53,
7684         oils_i18n_gettext(
7685             53,
7686             'USA',
7687             'csc',
7688             'region'
7689         ),
7690         oils_i18n_gettext(
7691             53,
7692             'Nextel',
7693             'csc',
7694             'name'
7695         ),
7696         '$number@messaging.nextel.com',
7697         TRUE
7698     ),
7699     (
7700         54,
7701         oils_i18n_gettext(
7702             54,
7703             'USA',
7704             'csc',
7705             'region'
7706         ),
7707         oils_i18n_gettext(
7708             54,
7709             'Pioneer Cellular',
7710             'csc',
7711             'name'
7712         ),
7713         '$number@zsend.com', -- nine digit number
7714         TRUE
7715     ),
7716     (
7717         55,
7718         oils_i18n_gettext(
7719             55,
7720             'USA',
7721             'csc',
7722             'region'
7723         ),
7724         oils_i18n_gettext(
7725             55,
7726             'Qwest Wireless',
7727             'csc',
7728             'name'
7729         ),
7730         '$number@qwestmp.com',
7731         TRUE
7732     ),
7733     (
7734         56,
7735         oils_i18n_gettext(
7736             56,
7737             'USA',
7738             'csc',
7739             'region'
7740         ),
7741         oils_i18n_gettext(
7742             56,
7743             'US Cellular',
7744             'csc',
7745             'name'
7746         ),
7747         '$number@email.uscc.net',
7748         TRUE
7749     ),
7750     (
7751         57,
7752         oils_i18n_gettext(
7753             57,
7754             'USA',
7755             'csc',
7756             'region'
7757         ),
7758         oils_i18n_gettext(
7759             57,
7760             'Unicel',
7761             'csc',
7762             'name'
7763         ),
7764         '$number@utext.com',
7765         TRUE
7766     ),
7767     (
7768         58,
7769         oils_i18n_gettext(
7770             58,
7771             'USA',
7772             'csc',
7773             'region'
7774         ),
7775         oils_i18n_gettext(
7776             58,
7777             'Teleflip',
7778             'csc',
7779             'name'
7780         ),
7781         '$number@teleflip.com',
7782         TRUE
7783     ),
7784     (
7785         59,
7786         oils_i18n_gettext(
7787             59,
7788             'USA',
7789             'csc',
7790             'region'
7791         ),
7792         oils_i18n_gettext(
7793             59,
7794             'Virgin Mobile',
7795             'csc',
7796             'name'
7797         ),
7798         '$number@vmobl.com',
7799         TRUE
7800     ),
7801     (
7802         60,
7803         oils_i18n_gettext(
7804             60,
7805             'USA',
7806             'csc',
7807             'region'
7808         ),
7809         oils_i18n_gettext(
7810             60,
7811             'Verizon Wireless',
7812             'csc',
7813             'name'
7814         ),
7815         '$number@vtext.com',
7816         TRUE
7817     ),
7818     (
7819         61,
7820         oils_i18n_gettext(
7821             61,
7822             'USA',
7823             'csc',
7824             'region'
7825         ),
7826         oils_i18n_gettext(
7827             61,
7828             'USA Mobility',
7829             'csc',
7830             'name'
7831         ),
7832         '$number@usamobility.net',
7833         TRUE
7834     ),
7835     (
7836         62,
7837         oils_i18n_gettext(
7838             62,
7839             'USA',
7840             'csc',
7841             'region'
7842         ),
7843         oils_i18n_gettext(
7844             62,
7845             'Viaero',
7846             'csc',
7847             'name'
7848         ),
7849         '$number@viaerosms.com',
7850         TRUE
7851     ),
7852     (
7853         63,
7854         oils_i18n_gettext(
7855             63,
7856             'USA',
7857             'csc',
7858             'region'
7859         ),
7860         oils_i18n_gettext(
7861             63,
7862             'TracFone',
7863             'csc',
7864             'name'
7865         ),
7866         '$number@mmst5.tracfone.com',
7867         TRUE
7868     ),
7869     (
7870         64,
7871         oils_i18n_gettext(
7872             64,
7873             'USA',
7874             'csc',
7875             'region'
7876         ),
7877         oils_i18n_gettext(
7878             64,
7879             'Centennial Wireless',
7880             'csc',
7881             'name'
7882         ),
7883         '$number@cwemail.com',
7884         TRUE
7885     ),
7886
7887     -- South Korea and USA
7888     (
7889         65,
7890         oils_i18n_gettext(
7891             65,
7892             'South Korea and USA',
7893             'csc',
7894             'region'
7895         ),
7896         oils_i18n_gettext(
7897             65,
7898             'Helio',
7899             'csc',
7900             'name'
7901         ),
7902         '$number@myhelio.com',
7903         TRUE
7904     )
7905 ;
7906
7907 INSERT INTO permission.perm_list ( id, code, description ) VALUES
7908     (
7909         519,
7910         'ADMIN_SMS_CARRIER',
7911         oils_i18n_gettext(
7912             519,
7913             'Allows a user to add/create/delete SMS Carrier entries.',
7914             'ppl',
7915             'description'
7916         )
7917     )
7918 ;
7919
7920 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
7921     SELECT
7922         pgt.id, perm.id, aout.depth, TRUE
7923     FROM
7924         permission.grp_tree pgt,
7925         permission.perm_list perm,
7926         actor.org_unit_type aout
7927     WHERE
7928         pgt.name = 'Global Administrator' AND
7929         aout.name = 'Consortium' AND
7930         perm.code = 'ADMIN_SMS_CARRIER';
7931
7932 INSERT INTO action_trigger.reactor (
7933     module,
7934     description
7935 ) VALUES (
7936     'SendSMS',
7937     'Send an SMS text message based on a user-defined template'
7938 );
7939
7940 INSERT INTO action_trigger.event_definition (
7941     active,
7942     owner,
7943     name,
7944     hook,
7945     validator,
7946     reactor,
7947     cleanup_success,
7948     delay,
7949     delay_field,
7950     group_field,
7951     template
7952 ) VALUES (
7953     true,
7954     1, -- admin
7955     'Hold Ready for Pickup SMS Notification',
7956     'hold.available',
7957     'HoldIsAvailable',
7958     'SendSMS',
7959     'CreateHoldNotification',
7960     '00:30:00',
7961     'shelf_time',
7962     'sms_notify',
7963     '[%- USE date -%]
7964 [%- user = target.0.usr -%]
7965 From: [%- params.sender_email || default_sender %]
7966 To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
7967 Subject: [% target.size %] hold(s) ready
7968
7969 [% FOR hold IN target %][%-
7970   bibxml = helpers.xml_doc( hold.current_copy.call_number.record.marc );
7971   title = "";
7972   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a"]'');
7973     title = title _ part.textContent;
7974   END;
7975   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
7976 %][% hold.usr.first_given_name %]:[% title %] @ [% hold.pickup_lib.name %]
7977 [% END %]
7978 '
7979 );
7980
7981 INSERT INTO action_trigger.environment (
7982     event_def,
7983     path
7984 ) VALUES (
7985     currval('action_trigger.event_definition_id_seq'),
7986     'current_copy.call_number.record.simple_record'
7987 ), (
7988     currval('action_trigger.event_definition_id_seq'),
7989     'usr'
7990 ), (
7991     currval('action_trigger.event_definition_id_seq'),
7992     'pickup_lib.billing_address'
7993 );
7994
7995 INSERT INTO action_trigger.hook(
7996     key,
7997     core_type,
7998     description,
7999     passive
8000 ) VALUES (
8001     'acn.format.sms_text',
8002     'acn',
8003     oils_i18n_gettext(
8004         'acn.format.sms_text',
8005         'A text message has been requested for a call number.',
8006         'ath',
8007         'description'
8008     ),
8009     FALSE
8010 );
8011
8012 INSERT INTO action_trigger.event_definition (
8013     active,
8014     owner,
8015     name,
8016     hook,
8017     validator,
8018     reactor,
8019     template
8020 ) VALUES (
8021     true,
8022     1, -- admin
8023     'SMS Call Number',
8024     'acn.format.sms_text',
8025     'NOOP_True',
8026     'SendSMS',
8027     '[%- USE date -%]
8028 From: [%- params.sender_email || default_sender %]
8029 To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
8030 Subject: Call Number
8031
8032 [%-
8033   bibxml = helpers.xml_doc( target.record.marc );
8034   title = "";
8035   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a" or @code="b"]'');
8036     title = title _ part.textContent;
8037   END;
8038   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
8039 %]
8040 Call Number: [% target.label %]
8041 Location: [% helpers.get_most_populous_location( target.id ).name %]
8042 Library: [% target.owning_lib.name %]
8043 [%- IF title %]
8044 Title: [% title %]
8045 [%- END %]
8046 [%- IF author %]
8047 Author: [% author %]
8048 [%- END %]
8049 '
8050 );
8051
8052 INSERT INTO action_trigger.environment (
8053     event_def,
8054     path
8055 ) VALUES (
8056     currval('action_trigger.event_definition_id_seq'),
8057     'record.simple_record'
8058 ), (
8059     currval('action_trigger.event_definition_id_seq'),
8060     'owning_lib.billing_address'
8061 );
8062
8063
8064 -- DELETE FROM actor.usr_setting WHERE name = 'opac.default_phone' OR name in ( SELECT name FROM config.usr_setting_type WHERE grp = 'sms' ); DELETE FROM config.usr_setting_type WHERE name = 'opac.default_phone' OR grp = 'sms'; DELETE FROM actor.org_unit_setting WHERE name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type_log WHERE field_name in ( SELECT name FROM config.org_unit_setting_type WHERE grp = 'sms' ); DELETE FROM config.org_unit_setting_type WHERE grp = 'sms'; DELETE FROM config.settings_group WHERE name = 'sms'; DELETE FROM permission.grp_perm_map WHERE perm = 519; DELETE FROM permission.perm_list WHERE id = 519; ALTER TABLE action.hold_request DROP CONSTRAINT sms_check; ALTER TABLE action.hold_request DROP COLUMN sms_notify; ALTER TABLE action.hold_request DROP COLUMN sms_carrier; DROP TABLE config.sms_carrier; DELETE FROM action_trigger.event WHERE event_def = ( SELECT id FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.environment WHERE event_def = ( SELECT id FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification' ); DELETE FROM action_trigger.event_definition WHERE name = 'Hold Ready for Pickup SMS Notification'; DELETE FROM action_trigger.event WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.environment WHERE event_def IN ( SELECT id FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text' ); DELETE FROM action_trigger.event_definition WHERE hook = 'acn.format.sms_text'; DELETE FROM action_trigger.hook WHERE key = 'acn.format.sms_text'; DELETE FROM action_trigger.reactor WHERE module = 'SendSMS'; DELETE FROM config.upgrade_log WHERE version = 'XXXX';
8065
8066
8067 SELECT evergreen.upgrade_deps_block_check('0667', :eg_version);
8068
8069 ALTER TABLE config.standing_penalty ADD staff_alert BOOL NOT NULL DEFAULT FALSE;
8070
8071 -- 20 is ALERT_NOTE
8072 -- for backwards compat, set all blocking penalties to alerts
8073 UPDATE config.standing_penalty SET staff_alert = TRUE 
8074     WHERE id = 20 OR block_list IS NOT NULL;
8075
8076 -- Evergreen DB patch 0668.schema.fix_indb_hold_permit.sql
8077 --
8078 -- FIXME: insert description of change, if needed
8079 --
8080
8081
8082 -- check whether patch can be applied
8083 SELECT evergreen.upgrade_deps_block_check('0668', :eg_version);
8084
8085 -- FIXME: add/check SQL statements to perform the upgrade
8086 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$
8087 DECLARE
8088     matchpoint_id        INT;
8089     user_object        actor.usr%ROWTYPE;
8090     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
8091     standing_penalty    config.standing_penalty%ROWTYPE;
8092     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
8093     transit_source        actor.org_unit%ROWTYPE;
8094     item_object        asset.copy%ROWTYPE;
8095     item_cn_object     asset.call_number%ROWTYPE;
8096     item_status_object  config.copy_status%ROWTYPE;
8097     item_location_object    asset.copy_location%ROWTYPE;
8098     ou_skip              actor.org_unit_setting%ROWTYPE;
8099     result            action.matrix_test_result;
8100     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
8101     use_active_date   TEXT;
8102     age_protect_date  TIMESTAMP WITH TIME ZONE;
8103     hold_count        INT;
8104     hold_transit_prox    INT;
8105     frozen_hold_count    INT;
8106     context_org_list    INT[];
8107     done            BOOL := FALSE;
8108 BEGIN
8109     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8110     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
8111
8112     result.success := TRUE;
8113
8114     -- Fail if we couldn't find a user
8115     IF user_object.id IS NULL THEN
8116         result.fail_part := 'no_user';
8117         result.success := FALSE;
8118         done := TRUE;
8119         RETURN NEXT result;
8120         RETURN;
8121     END IF;
8122
8123     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8124
8125     -- Fail if we couldn't find a copy
8126     IF item_object.id IS NULL THEN
8127         result.fail_part := 'no_item';
8128         result.success := FALSE;
8129         done := TRUE;
8130         RETURN NEXT result;
8131         RETURN;
8132     END IF;
8133
8134     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
8135     result.matchpoint := matchpoint_id;
8136
8137     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
8138
8139     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
8140     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
8141         result.fail_part := 'circ.holds.target_skip_me';
8142         result.success := FALSE;
8143         done := TRUE;
8144         RETURN NEXT result;
8145         RETURN;
8146     END IF;
8147
8148     -- Fail if user is barred
8149     IF user_object.barred IS TRUE THEN
8150         result.fail_part := 'actor.usr.barred';
8151         result.success := FALSE;
8152         done := TRUE;
8153         RETURN NEXT result;
8154         RETURN;
8155     END IF;
8156
8157     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8158     SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status;
8159     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8160
8161     -- Fail if we couldn't find any matchpoint (requires a default)
8162     IF matchpoint_id IS NULL THEN
8163         result.fail_part := 'no_matchpoint';
8164         result.success := FALSE;
8165         done := TRUE;
8166         RETURN NEXT result;
8167         RETURN;
8168     END IF;
8169
8170     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
8171
8172     IF hold_test.holdable IS FALSE THEN
8173         result.fail_part := 'config.hold_matrix_test.holdable';
8174         result.success := FALSE;
8175         done := TRUE;
8176         RETURN NEXT result;
8177     END IF;
8178
8179     IF item_object.holdable IS FALSE THEN
8180         result.fail_part := 'item.holdable';
8181         result.success := FALSE;
8182         done := TRUE;
8183         RETURN NEXT result;
8184     END IF;
8185
8186     IF item_status_object.holdable IS FALSE THEN
8187         result.fail_part := 'status.holdable';
8188         result.success := FALSE;
8189         done := TRUE;
8190         RETURN NEXT result;
8191     END IF;
8192
8193     IF item_location_object.holdable IS FALSE THEN
8194         result.fail_part := 'location.holdable';
8195         result.success := FALSE;
8196         done := TRUE;
8197         RETURN NEXT result;
8198     END IF;
8199
8200     IF hold_test.transit_range IS NOT NULL THEN
8201         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
8202         IF hold_test.distance_is_from_owner THEN
8203             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;
8204         ELSE
8205             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
8206         END IF;
8207
8208         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
8209
8210         IF NOT FOUND THEN
8211             result.fail_part := 'transit_range';
8212             result.success := FALSE;
8213             done := TRUE;
8214             RETURN NEXT result;
8215         END IF;
8216     END IF;
8217  
8218     FOR standing_penalty IN
8219         SELECT  DISTINCT csp.*
8220           FROM  actor.usr_standing_penalty usp
8221                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8222           WHERE usr = match_user
8223                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8224                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8225                 AND csp.block_list LIKE '%HOLD%' LOOP
8226
8227         result.fail_part := standing_penalty.name;
8228         result.success := FALSE;
8229         done := TRUE;
8230         RETURN NEXT result;
8231     END LOOP;
8232
8233     IF hold_test.stop_blocked_user IS TRUE THEN
8234         FOR standing_penalty IN
8235             SELECT  DISTINCT csp.*
8236               FROM  actor.usr_standing_penalty usp
8237                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8238               WHERE usr = match_user
8239                     AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8240                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8241                     AND csp.block_list LIKE '%CIRC%' LOOP
8242     
8243             result.fail_part := standing_penalty.name;
8244             result.success := FALSE;
8245             done := TRUE;
8246             RETURN NEXT result;
8247         END LOOP;
8248     END IF;
8249
8250     IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
8251         SELECT    INTO hold_count COUNT(*)
8252           FROM    action.hold_request
8253           WHERE    usr = match_user
8254             AND fulfillment_time IS NULL
8255             AND cancel_time IS NULL
8256             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
8257
8258         IF hold_count >= hold_test.max_holds THEN
8259             result.fail_part := 'config.hold_matrix_test.max_holds';
8260             result.success := FALSE;
8261             done := TRUE;
8262             RETURN NEXT result;
8263         END IF;
8264     END IF;
8265
8266     IF item_object.age_protect IS NOT NULL THEN
8267         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
8268         IF hold_test.distance_is_from_owner THEN
8269             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
8270         ELSE
8271             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
8272         END IF;
8273         IF use_active_date = 'true' THEN
8274             age_protect_date := COALESCE(item_object.active_date, NOW());
8275         ELSE
8276             age_protect_date := item_object.create_date;
8277         END IF;
8278         IF age_protect_date + age_protect_object.age > NOW() THEN
8279             IF hold_test.distance_is_from_owner THEN
8280                 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8281                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
8282             ELSE
8283                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
8284             END IF;
8285
8286             IF hold_transit_prox > age_protect_object.prox THEN
8287                 result.fail_part := 'config.rule_age_hold_protect.prox';
8288                 result.success := FALSE;
8289                 done := TRUE;
8290                 RETURN NEXT result;
8291             END IF;
8292         END IF;
8293     END IF;
8294
8295     IF NOT done THEN
8296         RETURN NEXT result;
8297     END IF;
8298
8299     RETURN;
8300 END;
8301 $func$ LANGUAGE plpgsql;
8302
8303
8304 -- Evergreen DB patch 0669.data.recall_and_force_holds.sql
8305 --
8306 -- FIXME: insert description of change, if needed
8307 --
8308
8309
8310 -- check whether patch can be applied
8311 SELECT evergreen.upgrade_deps_block_check('0669', :eg_version);
8312
8313 -- FIXME: add/check SQL statements to perform the upgrade
8314 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8315  ( 517, 'COPY_HOLDS_FORCE', oils_i18n_gettext( 517, 
8316     'Allow a user to place a force hold on a specific copy', 'ppl', 'description' )),
8317  ( 518, 'COPY_HOLDS_RECALL', oils_i18n_gettext( 518, 
8318     'Allow a user to place a cataloging recall on a specific copy', 'ppl', 'description' ));
8319
8320
8321 -- Evergreen DB patch 0670.data.mark-email-and-phone-invalid.sql
8322 --
8323 -- Add org unit settings and standing penalty types to support
8324 -- the mark email/phone invalid features.
8325 --
8326
8327 -- check whether patch can be applied
8328 SELECT evergreen.upgrade_deps_block_check('0670', :eg_version);
8329
8330
8331 INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) VALUES
8332     (
8333         31,
8334         'INVALID_PATRON_EMAIL_ADDRESS',
8335         oils_i18n_gettext(
8336             31,
8337             'Patron had an invalid email address',
8338             'csp',
8339             'label'
8340         ),
8341         TRUE,
8342         0
8343     ),
8344     (
8345         32,
8346         'INVALID_PATRON_DAY_PHONE',
8347         oils_i18n_gettext(
8348             32,
8349             'Patron had an invalid daytime phone number',
8350             'csp',
8351             'label'
8352         ),
8353         TRUE,
8354         0
8355     ),
8356     (
8357         33,
8358         'INVALID_PATRON_EVENING_PHONE',
8359         oils_i18n_gettext(
8360             33,
8361             'Patron had an invalid evening phone number',
8362             'csp',
8363             'label'
8364         ),
8365         TRUE,
8366         0
8367     ),
8368     (
8369         34,
8370         'INVALID_PATRON_OTHER_PHONE',
8371         oils_i18n_gettext(
8372             34,
8373             'Patron had an invalid other phone number',
8374             'csp',
8375             'label'
8376         ),
8377         TRUE,
8378         0
8379     );
8380
8381
8382
8383 SELECT evergreen.upgrade_deps_block_check('0671', :eg_version);
8384
8385 ALTER TABLE asset.copy_location
8386     ADD COLUMN checkin_alert BOOL NOT NULL DEFAULT FALSE;
8387
8388 -- Evergreen DB patch 0673.data.acq-cancel-reason-cleanup.sql
8389 --
8390
8391 -- check whether patch can be applied
8392 SELECT evergreen.upgrade_deps_block_check('0673', :eg_version);
8393
8394 DELETE FROM
8395     acq.cancel_reason
8396 WHERE
8397     -- any entries with id >= 2000 were added locally.  
8398     id < 2000 
8399
8400     -- these cancel_reason's are actively used by the system
8401     AND id NOT IN (1, 2, 3, 1002, 1003, 1004, 1005, 1010, 1024, 1211, 1221, 1246, 1283)
8402
8403     -- don't delete any cancel_reason's that may be in use locally
8404     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.user_request WHERE cancel_reason IS NOT NULL)
8405     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.purchase_order WHERE cancel_reason IS NOT NULL)
8406     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem WHERE cancel_reason IS NOT NULL)
8407     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem_detail WHERE cancel_reason IS NOT NULL)
8408     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_lineitem_history WHERE cancel_reason IS NOT NULL)
8409     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_purchase_order_history WHERE cancel_reason IS NOT NULL);
8410
8411
8412 SELECT evergreen.upgrade_deps_block_check('0674', :eg_version);
8413
8414 ALTER TABLE config.copy_status
8415           ADD COLUMN restrict_copy_delete BOOL NOT NULL DEFAULT FALSE;
8416
8417 UPDATE config.copy_status
8418 SET restrict_copy_delete = TRUE
8419 WHERE id IN (1,3,6,8);
8420
8421 INSERT INTO permission.perm_list (id, code, description) VALUES (
8422     520,
8423     'COPY_DELETE_WARNING.override',
8424     'Allow a user to override warnings about deleting copies in problematic situations.'
8425 );
8426
8427
8428 SELECT evergreen.upgrade_deps_block_check('0675', :eg_version);
8429
8430 -- set expected row count to low value to avoid problem
8431 -- where use of this function by the circ tagging feature
8432 -- results in full scans of asset.call_number
8433 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
8434     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
8435 $$ LANGUAGE SQL ROWS 10;
8436
8437
8438 SELECT evergreen.upgrade_deps_block_check('0676', :eg_version);
8439
8440 INSERT INTO config.global_flag (name, label, enabled, value) VALUES (
8441     'opac.use_autosuggest',
8442     'OPAC: Show auto-completing suggestions dialog under basic search box (put ''opac_visible'' into the value field to limit suggestions to OPAC-visible items, or blank the field for a possible performance improvement)',
8443     TRUE,
8444     'opac_visible'
8445 );
8446
8447 CREATE TABLE metabib.browse_entry (
8448     id BIGSERIAL PRIMARY KEY,
8449     value TEXT unique,
8450     index_vector tsvector
8451 );
8452 --Skip this, will be created differently later
8453 --CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIST (index_vector);
8454 CREATE TRIGGER metabib_browse_entry_fti_trigger
8455     BEFORE INSERT OR UPDATE ON metabib.browse_entry
8456     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
8457
8458
8459 CREATE TABLE metabib.browse_entry_def_map (
8460     id BIGSERIAL PRIMARY KEY,
8461     entry BIGINT REFERENCES metabib.browse_entry (id),
8462     def INT REFERENCES config.metabib_field (id),
8463     source BIGINT REFERENCES biblio.record_entry (id)
8464 );
8465
8466 ALTER TABLE config.metabib_field ADD COLUMN browse_field BOOLEAN DEFAULT TRUE NOT NULL;
8467 ALTER TABLE config.metabib_field ADD COLUMN browse_xpath TEXT;
8468
8469 ALTER TABLE config.metabib_class ADD COLUMN bouyant BOOLEAN DEFAULT FALSE NOT NULL;
8470 ALTER TABLE config.metabib_class ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
8471 ALTER TABLE config.metabib_field ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
8472
8473 -- one good exception to default true:
8474 UPDATE config.metabib_field
8475     SET browse_field = FALSE
8476     WHERE (field_class = 'keyword' AND name = 'keyword') OR
8477         (field_class = 'subject' AND name = 'complete');
8478
8479 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
8480 -- We're only touching it here to add a DELETE statement to the IF NEW.deleted
8481 -- block.
8482
8483 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
8484 DECLARE
8485     transformed_xml TEXT;
8486     prev_xfrm       TEXT;
8487     normalizer      RECORD;
8488     xfrm            config.xml_transform%ROWTYPE;
8489     attr_value      TEXT;
8490     new_attrs       HSTORE := ''::HSTORE;
8491     attr_def        config.record_attr_definition%ROWTYPE;
8492 BEGIN
8493
8494     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
8495         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
8496         DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
8497         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
8498         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
8499         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
8500         RETURN NEW; -- and we're done
8501     END IF;
8502
8503     IF TG_OP = 'UPDATE' THEN -- re-ingest?
8504         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
8505
8506         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
8507             RETURN NEW;
8508         END IF;
8509     END IF;
8510
8511     -- Record authority linking
8512     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
8513     IF NOT FOUND THEN
8514         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
8515     END IF;
8516
8517     -- Flatten and insert the mfr data
8518     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
8519     IF NOT FOUND THEN
8520         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
8521
8522         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
8523         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
8524         IF NOT FOUND THEN
8525             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
8526
8527                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
8528                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
8529                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
8530                       WHERE record = NEW.id
8531                             AND tag LIKE attr_def.tag
8532                             AND CASE
8533                                 WHEN attr_def.sf_list IS NOT NULL 
8534                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
8535                                 ELSE TRUE
8536                                 END
8537                       GROUP BY tag
8538                       ORDER BY tag
8539                       LIMIT 1;
8540
8541                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
8542                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
8543
8544                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
8545
8546                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
8547             
8548                     -- See if we can skip the XSLT ... it's expensive
8549                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
8550                         -- Can't skip the transform
8551                         IF xfrm.xslt <> '---' THEN
8552                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
8553                         ELSE
8554                             transformed_xml := NEW.marc;
8555                         END IF;
8556             
8557                         prev_xfrm := xfrm.name;
8558                     END IF;
8559
8560                     IF xfrm.name IS NULL THEN
8561                         -- just grab the marcxml (empty) transform
8562                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
8563                         prev_xfrm := xfrm.name;
8564                     END IF;
8565
8566                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
8567
8568                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
8569                     SELECT  m.value INTO attr_value
8570                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
8571                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
8572                       WHERE v.subfield = attr_def.phys_char_sf
8573                       LIMIT 1; -- Just in case ...
8574
8575                 END IF;
8576
8577                 -- apply index normalizers to attr_value
8578                 FOR normalizer IN
8579                     SELECT  n.func AS func,
8580                             n.param_count AS param_count,
8581                             m.params AS params
8582                       FROM  config.index_normalizer n
8583                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
8584                       WHERE attr = attr_def.name
8585                       ORDER BY m.pos LOOP
8586                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
8587                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
8588                             CASE
8589                                 WHEN normalizer.param_count > 0
8590                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
8591                                     ELSE ''
8592                                 END ||
8593                             ')' INTO attr_value;
8594         
8595                 END LOOP;
8596
8597                 -- Add the new value to the hstore
8598                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
8599
8600             END LOOP;
8601
8602             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
8603                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
8604             ELSE
8605                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
8606             END IF;
8607
8608         END IF;
8609     END IF;
8610
8611     -- Gather and insert the field entry data
8612     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
8613
8614     -- Located URI magic
8615     IF TG_OP = 'INSERT' THEN
8616         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
8617         IF NOT FOUND THEN
8618             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
8619         END IF;
8620     ELSE
8621         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
8622         IF NOT FOUND THEN
8623             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
8624         END IF;
8625     END IF;
8626
8627     -- (re)map metarecord-bib linking
8628     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
8629         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
8630         IF NOT FOUND THEN
8631             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
8632         END IF;
8633     ELSE -- we're doing an update, and we're not deleted, remap
8634         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
8635         IF NOT FOUND THEN
8636             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
8637         END IF;
8638     END IF;
8639
8640     RETURN NEW;
8641 END;
8642 $func$ LANGUAGE PLPGSQL;
8643
8644 CREATE OR REPLACE FUNCTION metabib.browse_normalize(facet_text TEXT, mapped_field INT) RETURNS TEXT AS $$
8645 DECLARE
8646     normalizer  RECORD;
8647 BEGIN
8648
8649     FOR normalizer IN
8650         SELECT  n.func AS func,
8651                 n.param_count AS param_count,
8652                 m.params AS params
8653           FROM  config.index_normalizer n
8654                 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
8655           WHERE m.field = mapped_field AND m.pos < 0
8656           ORDER BY m.pos LOOP
8657
8658             EXECUTE 'SELECT ' || normalizer.func || '(' ||
8659                 quote_literal( facet_text ) ||
8660                 CASE
8661                     WHEN normalizer.param_count > 0
8662                         THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
8663                         ELSE ''
8664                     END ||
8665                 ')' INTO facet_text;
8666
8667     END LOOP;
8668
8669     RETURN facet_text;
8670 END;
8671
8672 $$ LANGUAGE PLPGSQL;
8673
8674 DROP FUNCTION biblio.extract_metabib_field_entry(bigint, text);
8675 DROP FUNCTION biblio.extract_metabib_field_entry(bigint);
8676
8677 DROP TYPE metabib.field_entry_template;
8678 CREATE TYPE metabib.field_entry_template AS (
8679         field_class     TEXT,
8680         field           INT,
8681         facet_field     BOOL,
8682         search_field    BOOL,
8683         browse_field   BOOL,
8684         source          BIGINT,
8685         value           TEXT
8686 );
8687
8688
8689 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
8690 DECLARE
8691     bib     biblio.record_entry%ROWTYPE;
8692     idx     config.metabib_field%ROWTYPE;
8693     xfrm        config.xml_transform%ROWTYPE;
8694     prev_xfrm   TEXT;
8695     transformed_xml TEXT;
8696     xml_node    TEXT;
8697     xml_node_list   TEXT[];
8698     facet_text  TEXT;
8699     browse_text TEXT;
8700     raw_text    TEXT;
8701     curr_text   TEXT;
8702     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
8703     output_row  metabib.field_entry_template%ROWTYPE;
8704 BEGIN
8705
8706     -- Get the record
8707     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
8708
8709     -- Loop over the indexing entries
8710     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
8711
8712         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
8713
8714         -- See if we can skip the XSLT ... it's expensive
8715         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
8716             -- Can't skip the transform
8717             IF xfrm.xslt <> '---' THEN
8718                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
8719             ELSE
8720                 transformed_xml := bib.marc;
8721             END IF;
8722
8723             prev_xfrm := xfrm.name;
8724         END IF;
8725
8726         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8727
8728         raw_text := NULL;
8729         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
8730             CONTINUE WHEN xml_node !~ E'^\\s*<';
8731
8732             curr_text := ARRAY_TO_STRING(
8733                 oils_xpath( '//text()',
8734                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
8735                         REGEXP_REPLACE( -- This escapes embeded <s
8736                             xml_node,
8737                             $re$(>[^<]+)(<)([^>]+<)$re$,
8738                             E'\\1&lt;\\3',
8739                             'g'
8740                         ),
8741                         '&(?!amp;)',
8742                         '&amp;',
8743                         'g'
8744                     )
8745                 ),
8746                 ' '
8747             );
8748
8749             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
8750
8751             IF raw_text IS NOT NULL THEN
8752                 raw_text := raw_text || joiner;
8753             END IF;
8754
8755             raw_text := COALESCE(raw_text,'') || curr_text;
8756
8757             -- autosuggest/metabib.browse_entry
8758             IF idx.browse_field THEN
8759
8760                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
8761                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8762                 ELSE
8763                     browse_text := curr_text;
8764                 END IF;
8765
8766                 output_row.field_class = idx.field_class;
8767                 output_row.field = idx.id;
8768                 output_row.source = rid;
8769                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
8770
8771                 output_row.browse_field = TRUE;
8772                 RETURN NEXT output_row;
8773                 output_row.browse_field = FALSE;
8774             END IF;
8775
8776             -- insert raw node text for faceting
8777             IF idx.facet_field THEN
8778
8779                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
8780                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8781                 ELSE
8782                     facet_text := curr_text;
8783                 END IF;
8784
8785                 output_row.field_class = idx.field_class;
8786                 output_row.field = -1 * idx.id;
8787                 output_row.source = rid;
8788                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
8789
8790                 output_row.facet_field = TRUE;
8791                 RETURN NEXT output_row;
8792                 output_row.facet_field = FALSE;
8793             END IF;
8794
8795         END LOOP;
8796
8797         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
8798
8799         -- insert combined node text for searching
8800         IF idx.search_field THEN
8801             output_row.field_class = idx.field_class;
8802             output_row.field = idx.id;
8803             output_row.source = rid;
8804             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
8805
8806             output_row.search_field = TRUE;
8807             RETURN NEXT output_row;
8808         END IF;
8809
8810     END LOOP;
8811
8812 END;
8813 $func$ LANGUAGE PLPGSQL;
8814
8815 -- default to a space joiner
8816 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
8817     SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
8818     $func$ LANGUAGE SQL;
8819
8820
8821 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
8822 DECLARE
8823     fclass          RECORD;
8824     ind_data        metabib.field_entry_template%ROWTYPE;
8825     mbe_row         metabib.browse_entry%ROWTYPE;
8826     mbe_id          BIGINT;
8827 BEGIN
8828     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
8829     IF NOT FOUND THEN
8830         FOR fclass IN SELECT * FROM config.metabib_class LOOP
8831             -- RAISE NOTICE 'Emptying out %', fclass.name;
8832             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
8833         END LOOP;
8834         DELETE FROM metabib.facet_entry WHERE source = bib_id;
8835         DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
8836     END IF;
8837
8838     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
8839         IF ind_data.field < 0 THEN
8840             ind_data.field = -1 * ind_data.field;
8841         END IF;
8842
8843         IF ind_data.facet_field THEN
8844             INSERT INTO metabib.facet_entry (field, source, value)
8845                 VALUES (ind_data.field, ind_data.source, ind_data.value);
8846         END IF;
8847
8848         IF ind_data.browse_field THEN
8849             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
8850             IF FOUND THEN
8851                 mbe_id := mbe_row.id;
8852             ELSE
8853                 INSERT INTO metabib.browse_entry (value) VALUES
8854                     (metabib.browse_normalize(ind_data.value, ind_data.field));
8855                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
8856             END IF;
8857
8858             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
8859                 VALUES (mbe_id, ind_data.field, ind_data.source);
8860         END IF;
8861
8862         IF ind_data.search_field THEN
8863             EXECUTE $$
8864                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
8865                     VALUES ($$ ||
8866                         quote_literal(ind_data.field) || $$, $$ ||
8867                         quote_literal(ind_data.source) || $$, $$ ||
8868                         quote_literal(ind_data.value) ||
8869                     $$);$$;
8870         END IF;
8871
8872     END LOOP;
8873
8874     RETURN;
8875 END;
8876 $func$ LANGUAGE PLPGSQL;
8877
8878 -- This mimics a specific part of QueryParser, turning the first part of a
8879 -- classed search (search_class) into a set of classes and possibly fields.
8880 -- search_class might look like "author" or "title|proper" or "ti|uniform"
8881 -- or "au" or "au|corporate|personal" or anything like that, where the first
8882 -- element of the list you get by separating on the "|" character is either
8883 -- a registered class (config.metabib_class) or an alias
8884 -- (config.metabib_search_alias), and the rest of any such elements are
8885 -- fields (config.metabib_field).
8886 CREATE OR REPLACE
8887     FUNCTION metabib.search_class_to_registered_components(search_class TEXT)
8888     RETURNS SETOF RECORD AS $func$
8889 DECLARE
8890     search_parts        TEXT[];
8891     field_name          TEXT;
8892     search_part_count   INTEGER;
8893     rec                 RECORD;
8894     registered_class    config.metabib_class%ROWTYPE;
8895     registered_alias    config.metabib_search_alias%ROWTYPE;
8896     registered_field    config.metabib_field%ROWTYPE;
8897 BEGIN
8898     search_parts := REGEXP_SPLIT_TO_ARRAY(search_class, E'\\|');
8899
8900     search_part_count := ARRAY_LENGTH(search_parts, 1);
8901     IF search_part_count = 0 THEN
8902         RETURN;
8903     ELSE
8904         SELECT INTO registered_class
8905             * FROM config.metabib_class WHERE name = search_parts[1];
8906         IF FOUND THEN
8907             IF search_part_count < 2 THEN   -- all fields
8908                 rec := (registered_class.name, NULL::INTEGER);
8909                 RETURN NEXT rec;
8910                 RETURN; -- done
8911             END IF;
8912             FOR field_name IN SELECT *
8913                 FROM UNNEST(search_parts[2:search_part_count]) LOOP
8914                 SELECT INTO registered_field
8915                     * FROM config.metabib_field
8916                     WHERE name = field_name AND
8917                         field_class = registered_class.name;
8918                 IF FOUND THEN
8919                     rec := (registered_class.name, registered_field.id);
8920                     RETURN NEXT rec;
8921                 END IF;
8922             END LOOP;
8923         ELSE
8924             -- maybe we have an alias?
8925             SELECT INTO registered_alias
8926                 * FROM config.metabib_search_alias WHERE alias=search_parts[1];
8927             IF NOT FOUND THEN
8928                 RETURN;
8929             ELSE
8930                 IF search_part_count < 2 THEN   -- return w/e the alias says
8931                     rec := (
8932                         registered_alias.field_class, registered_alias.field
8933                     );
8934                     RETURN NEXT rec;
8935                     RETURN; -- done
8936                 ELSE
8937                     FOR field_name IN SELECT *
8938                         FROM UNNEST(search_parts[2:search_part_count]) LOOP
8939                         SELECT INTO registered_field
8940                             * FROM config.metabib_field
8941                             WHERE name = field_name AND
8942                                 field_class = registered_alias.field_class;
8943                         IF FOUND THEN
8944                             rec := (
8945                                 registered_alias.field_class,
8946                                 registered_field.id
8947                             );
8948                             RETURN NEXT rec;
8949                         END IF;
8950                     END LOOP;
8951                 END IF;
8952             END IF;
8953         END IF;
8954     END IF;
8955 END;
8956 $func$ LANGUAGE PLPGSQL;
8957
8958
8959 CREATE OR REPLACE
8960     FUNCTION metabib.suggest_browse_entries(
8961         query_text      TEXT,   -- 'foo' or 'foo & ba:*',ready for to_tsquery()
8962         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
8963         headline_opts   TEXT,   -- markup options for ts_headline()
8964         visibility_org  INTEGER,-- null if you don't want opac visibility test
8965         query_limit     INTEGER,-- use in LIMIT clause of interal query
8966         normalization   INTEGER -- argument to TS_RANK_CD()
8967     ) RETURNS TABLE (
8968         value                   TEXT,   -- plain
8969         field                   INTEGER,
8970         bouyant_and_class_match BOOL,
8971         field_match             BOOL,
8972         field_weight            INTEGER,
8973         rank                    REAL,
8974         bouyant                 BOOL,
8975         match                   TEXT    -- marked up
8976     ) AS $func$
8977 DECLARE
8978     query                   TSQUERY;
8979     opac_visibility_join    TEXT;
8980     search_class_join       TEXT;
8981     r_fields                RECORD;
8982 BEGIN
8983     query := TO_TSQUERY('keyword', query_text);
8984
8985     IF visibility_org IS NOT NULL THEN
8986         opac_visibility_join := '
8987     JOIN asset.opac_visible_copies aovc ON (
8988         aovc.record = mbedm.source AND
8989         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
8990     )';
8991     ELSE
8992         opac_visibility_join := '';
8993     END IF;
8994
8995     -- The following determines whether we only provide suggestsons matching
8996     -- the user's selected search_class, or whether we show other suggestions
8997     -- too. The reason for MIN() is that for search_classes like
8998     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
8999     -- implication is that if title as a class doesn't have restrict,
9000     -- nor does the proper field, but the uniform field does, you're going
9001     -- to get 'false' for your overall evaluation of 'should we restrict?'
9002     -- To invert that, change from MIN() to MAX().
9003
9004     SELECT
9005         INTO r_fields
9006             MIN(cmc.restrict::INT) AS restrict_class,
9007             MIN(cmf.restrict::INT) AS restrict_field
9008         FROM metabib.search_class_to_registered_components(search_class)
9009             AS _registered (field_class TEXT, field INT)
9010         JOIN
9011             config.metabib_class cmc ON (cmc.name = _registered.field_class)
9012         LEFT JOIN
9013             config.metabib_field cmf ON (cmf.id = _registered.field);
9014
9015     -- evaluate 'should we restrict?'
9016     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
9017         search_class_join := '
9018     JOIN
9019         metabib.search_class_to_registered_components($2)
9020         AS _registered (field_class TEXT, field INT) ON (
9021             (_registered.field IS NULL AND
9022                 _registered.field_class = cmf.field_class) OR
9023             (_registered.field = cmf.id)
9024         )
9025     ';
9026     ELSE
9027         search_class_join := '
9028     LEFT JOIN
9029         metabib.search_class_to_registered_components($2)
9030         AS _registered (field_class TEXT, field INT) ON (
9031             _registered.field_class = cmc.name
9032         )
9033     ';
9034     END IF;
9035
9036     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $1, $3) FROM (SELECT DISTINCT
9037         mbe.value,
9038         cmf.id,
9039         cmc.bouyant AND _registered.field_class IS NOT NULL,
9040         _registered.field = cmf.id,
9041         cmf.weight,
9042         TS_RANK_CD(mbe.index_vector, $1, $6),
9043         cmc.bouyant
9044     FROM metabib.browse_entry_def_map mbedm
9045     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
9046     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
9047     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
9048     '  || search_class_join || opac_visibility_join ||
9049     ' WHERE $1 @@ mbe.index_vector
9050     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
9051     LIMIT $5) x
9052     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
9053     '   -- sic, repeat the order by clause in the outer select too
9054     USING
9055         query, search_class, headline_opts,
9056         visibility_org, query_limit, normalization
9057         ;
9058
9059     -- sort order:
9060     --  bouyant AND chosen class = match class
9061     --  chosen field = match field
9062     --  field weight
9063     --  rank
9064     --  bouyancy
9065     --  value itself
9066
9067 END;
9068 $func$ LANGUAGE PLPGSQL;
9069
9070 -- The advantage of this over the stock regexp_split_to_array() is that it
9071 -- won't degrade unicode strings.
9072 CREATE OR REPLACE FUNCTION evergreen.regexp_split_to_array(TEXT, TEXT)
9073 RETURNS TEXT[] AS $$
9074     return encode_array_literal([split $_[1], $_[0]]);
9075 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
9076
9077
9078 -- Adds some logic for browse_entry to split on non-word chars for index_vector, post-normalize
9079 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
9080 DECLARE
9081     normalizer      RECORD;
9082     value           TEXT := '';
9083 BEGIN
9084
9085     value := NEW.value;
9086
9087     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
9088         FOR normalizer IN
9089             SELECT  n.func AS func,
9090                     n.param_count AS param_count,
9091                     m.params AS params
9092               FROM  config.index_normalizer n
9093                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
9094               WHERE field = NEW.field AND m.pos < 0
9095               ORDER BY m.pos LOOP
9096                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
9097                     quote_literal( value ) ||
9098                     CASE
9099                         WHEN normalizer.param_count > 0
9100                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
9101                             ELSE ''
9102                         END ||
9103                     ')' INTO value;
9104
9105         END LOOP;
9106
9107         NEW.value := value;
9108     END IF;
9109
9110     IF NEW.index_vector = ''::tsvector THEN
9111         RETURN NEW;
9112     END IF;
9113
9114     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
9115         FOR normalizer IN
9116             SELECT  n.func AS func,
9117                     n.param_count AS param_count,
9118                     m.params AS params
9119               FROM  config.index_normalizer n
9120                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
9121               WHERE field = NEW.field AND m.pos >= 0
9122               ORDER BY m.pos LOOP
9123                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
9124                     quote_literal( value ) ||
9125                     CASE
9126                         WHEN normalizer.param_count > 0
9127                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
9128                             ELSE ''
9129                         END ||
9130                     ')' INTO value;
9131
9132         END LOOP;
9133     END IF;
9134
9135     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
9136         value :=  ARRAY_TO_STRING(
9137             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
9138         );
9139     END IF;
9140
9141     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
9142
9143     RETURN NEW;
9144 END;
9145 $$ LANGUAGE PLPGSQL;
9146
9147 -- Evergreen DB patch 0677.schema.circ_limits.sql
9148 --
9149 -- FIXME: insert description of change, if needed
9150 --
9151
9152
9153 -- check whether patch can be applied
9154 SELECT evergreen.upgrade_deps_block_check('0677', :eg_version);
9155
9156 -- FIXME: add/check SQL statements to perform the upgrade
9157 -- Limit groups for circ counting
9158 CREATE TABLE config.circ_limit_group (
9159     id          SERIAL  PRIMARY KEY,
9160     name        TEXT    UNIQUE NOT NULL,
9161     description TEXT
9162 );
9163
9164 -- Limit sets
9165 CREATE TABLE config.circ_limit_set (
9166     id          SERIAL  PRIMARY KEY,
9167     name        TEXT    UNIQUE NOT NULL,
9168     owning_lib  INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
9169     items_out   INT     NOT NULL, -- Total current active circulations must be less than this. 0 means skip counting (always pass)
9170     depth       INT     NOT NULL DEFAULT 0, -- Depth count starts at
9171     global      BOOL    NOT NULL DEFAULT FALSE, -- If enabled, include everything below depth, otherwise ancestors/descendants only
9172     description TEXT
9173 );
9174
9175 -- Linkage between matchpoints and limit sets
9176 CREATE TABLE config.circ_matrix_limit_set_map (
9177     id          SERIAL  PRIMARY KEY,
9178     matchpoint  INT     NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9179     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9180     fallthrough BOOL    NOT NULL DEFAULT FALSE, -- If true fallthrough will grab this rule as it goes along
9181     active      BOOL    NOT NULL DEFAULT TRUE,
9182     CONSTRAINT circ_limit_set_once_per_matchpoint UNIQUE (matchpoint, limit_set)
9183 );
9184
9185 -- Linkage between limit sets and circ mods
9186 CREATE TABLE config.circ_limit_set_circ_mod_map (
9187     id          SERIAL  PRIMARY KEY,
9188     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9189     circ_mod    TEXT    NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
9190     CONSTRAINT cm_once_per_set UNIQUE (limit_set, circ_mod)
9191 );
9192
9193 -- Linkage between limit sets and limit groups
9194 CREATE TABLE config.circ_limit_set_group_map (
9195     id          SERIAL  PRIMARY KEY,
9196     limit_set    INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9197     limit_group INT     NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9198     check_only  BOOL    NOT NULL DEFAULT FALSE, -- If true, don't accumulate this limit_group for storing with the circulation
9199     CONSTRAINT clg_once_per_set UNIQUE (limit_set, limit_group)
9200 );
9201
9202 -- Linkage between limit groups and circulations
9203 CREATE TABLE action.circulation_limit_group_map (
9204     circ        BIGINT      NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9205     limit_group INT         NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9206     PRIMARY KEY (circ, limit_group)
9207 );
9208
9209 -- Function for populating the circ/limit group mappings
9210 CREATE OR REPLACE FUNCTION action.link_circ_limit_groups ( BIGINT, INT[] ) RETURNS VOID AS $func$
9211     INSERT INTO action.circulation_limit_group_map(circ, limit_group) SELECT $1, id FROM config.circ_limit_group WHERE id IN (SELECT * FROM UNNEST($2));
9212 $func$ LANGUAGE SQL;
9213
9214 DROP TYPE IF EXISTS action.circ_matrix_test_result CASCADE;
9215 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, limit_groups INT[] );
9216
9217 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$
9218 DECLARE
9219     user_object             actor.usr%ROWTYPE;
9220     standing_penalty        config.standing_penalty%ROWTYPE;
9221     item_object             asset.copy%ROWTYPE;
9222     item_status_object      config.copy_status%ROWTYPE;
9223     item_location_object    asset.copy_location%ROWTYPE;
9224     result                  action.circ_matrix_test_result;
9225     circ_test               action.found_circ_matrix_matchpoint;
9226     circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
9227     circ_limit_set          config.circ_limit_set%ROWTYPE;
9228     hold_ratio              action.hold_stats%ROWTYPE;
9229     penalty_type            TEXT;
9230     items_out               INT;
9231     context_org_list        INT[];
9232     done                    BOOL := FALSE;
9233 BEGIN
9234     -- Assume success unless we hit a failure condition
9235     result.success := TRUE;
9236
9237     -- Need user info to look up matchpoints
9238     SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
9239
9240     -- (Insta)Fail if we couldn't find the user
9241     IF user_object.id IS NULL THEN
9242         result.fail_part := 'no_user';
9243         result.success := FALSE;
9244         done := TRUE;
9245         RETURN NEXT result;
9246         RETURN;
9247     END IF;
9248
9249     -- Need item info to look up matchpoints
9250     SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
9251
9252     -- (Insta)Fail if we couldn't find the item 
9253     IF item_object.id IS NULL THEN
9254         result.fail_part := 'no_item';
9255         result.success := FALSE;
9256         done := TRUE;
9257         RETURN NEXT result;
9258         RETURN;
9259     END IF;
9260
9261     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
9262
9263     circ_matchpoint             := circ_test.matchpoint;
9264     result.matchpoint           := circ_matchpoint.id;
9265     result.circulate            := circ_matchpoint.circulate;
9266     result.duration_rule        := circ_matchpoint.duration_rule;
9267     result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
9268     result.max_fine_rule        := circ_matchpoint.max_fine_rule;
9269     result.hard_due_date        := circ_matchpoint.hard_due_date;
9270     result.renewals             := circ_matchpoint.renewals;
9271     result.grace_period         := circ_matchpoint.grace_period;
9272     result.buildrows            := circ_test.buildrows;
9273
9274     -- (Insta)Fail if we couldn't find a matchpoint
9275     IF circ_test.success = false THEN
9276         result.fail_part := 'no_matchpoint';
9277         result.success := FALSE;
9278         done := TRUE;
9279         RETURN NEXT result;
9280         RETURN;
9281     END IF;
9282
9283     -- All failures before this point are non-recoverable
9284     -- Below this point are possibly overridable failures
9285
9286     -- Fail if the user is barred
9287     IF user_object.barred IS TRUE THEN
9288         result.fail_part := 'actor.usr.barred';
9289         result.success := FALSE;
9290         done := TRUE;
9291         RETURN NEXT result;
9292     END IF;
9293
9294     -- Fail if the item can't circulate
9295     IF item_object.circulate IS FALSE THEN
9296         result.fail_part := 'asset.copy.circulate';
9297         result.success := FALSE;
9298         done := TRUE;
9299         RETURN NEXT result;
9300     END IF;
9301
9302     -- Fail if the item isn't in a circulateable status on a non-renewal
9303     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
9304         result.fail_part := 'asset.copy.status';
9305         result.success := FALSE;
9306         done := TRUE;
9307         RETURN NEXT result;
9308     -- Alternately, fail if the item isn't checked out on a renewal
9309     ELSIF renewal AND item_object.status <> 1 THEN
9310         result.fail_part := 'asset.copy.status';
9311         result.success := FALSE;
9312         done := TRUE;
9313         RETURN NEXT result;
9314     END IF;
9315
9316     -- Fail if the item can't circulate because of the shelving location
9317     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
9318     IF item_location_object.circulate IS FALSE THEN
9319         result.fail_part := 'asset.copy_location.circulate';
9320         result.success := FALSE;
9321         done := TRUE;
9322         RETURN NEXT result;
9323     END IF;
9324
9325     -- Use Circ OU for penalties and such
9326     SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
9327
9328     IF renewal THEN
9329         penalty_type = '%RENEW%';
9330     ELSE
9331         penalty_type = '%CIRC%';
9332     END IF;
9333
9334     FOR standing_penalty IN
9335         SELECT  DISTINCT csp.*
9336           FROM  actor.usr_standing_penalty usp
9337                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
9338           WHERE usr = match_user
9339                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
9340                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
9341                 AND csp.block_list LIKE penalty_type LOOP
9342
9343         result.fail_part := standing_penalty.name;
9344         result.success := FALSE;
9345         done := TRUE;
9346         RETURN NEXT result;
9347     END LOOP;
9348
9349     -- Fail if the test is set to hard non-circulating
9350     IF circ_matchpoint.circulate IS FALSE THEN
9351         result.fail_part := 'config.circ_matrix_test.circulate';
9352         result.success := FALSE;
9353         done := TRUE;
9354         RETURN NEXT result;
9355     END IF;
9356
9357     -- Fail if the total copy-hold ratio is too low
9358     IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
9359         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
9360         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
9361             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
9362             result.success := FALSE;
9363             done := TRUE;
9364             RETURN NEXT result;
9365         END IF;
9366     END IF;
9367
9368     -- Fail if the available copy-hold ratio is too low
9369     IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
9370         IF hold_ratio.hold_count IS NULL THEN
9371             SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
9372         END IF;
9373         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
9374             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
9375             result.success := FALSE;
9376             done := TRUE;
9377             RETURN NEXT result;
9378         END IF;
9379     END IF;
9380
9381     -- Fail if the user has too many items out by defined limit sets
9382     FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
9383       JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
9384       WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
9385         ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
9386         ) LOOP
9387             IF circ_limit_set.items_out > 0 AND NOT renewal THEN
9388                 SELECT INTO context_org_list ARRAY_AGG(aou.id)
9389                   FROM actor.org_unit_full_path( circ_ou ) aou
9390                     JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
9391                   WHERE aout.depth >= circ_limit_set.depth;
9392                 IF circ_limit_set.global THEN
9393                     WITH RECURSIVE descendant_depth AS (
9394                         SELECT  ou.id,
9395                             ou.parent_ou
9396                         FROM  actor.org_unit ou
9397                         WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
9398                             UNION
9399                         SELECT  ou.id,
9400                             ou.parent_ou
9401                         FROM  actor.org_unit ou
9402                             JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
9403                     ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
9404                 END IF;
9405                 SELECT INTO items_out COUNT(DISTINCT circ.id)
9406                   FROM action.circulation circ
9407                     JOIN asset.copy copy ON (copy.id = circ.target_copy)
9408                     LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
9409                   WHERE circ.usr = match_user
9410                     AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
9411                     AND circ.checkin_time IS NULL
9412                     AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
9413                     AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
9414                         OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
9415                     );
9416                 IF items_out >= circ_limit_set.items_out THEN
9417                     result.fail_part := 'config.circ_matrix_circ_mod_test';
9418                     result.success := FALSE;
9419                     done := TRUE;
9420                     RETURN NEXT result;
9421                 END IF;
9422             END IF;
9423             SELECT INTO result.limit_groups result.limit_groups || ARRAY_AGG(limit_group) FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id AND NOT check_only;
9424     END LOOP;
9425
9426     -- If we passed everything, return the successful matchpoint
9427     IF NOT done THEN
9428         RETURN NEXT result;
9429     END IF;
9430
9431     RETURN;
9432 END;
9433 $func$ LANGUAGE plpgsql;
9434
9435 -- We need to re-create these, as they got dropped with the type above.
9436 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
9437     SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
9438 $func$ LANGUAGE SQL;
9439
9440 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
9441     SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
9442 $func$ LANGUAGE SQL;
9443
9444 -- Temp function for migrating circ mod limits.
9445 CREATE OR REPLACE FUNCTION evergreen.temp_migrate_circ_mod_limits() RETURNS VOID AS $func$
9446 DECLARE
9447     circ_mod_group config.circ_matrix_circ_mod_test%ROWTYPE;
9448     current_set INT;
9449     circ_mod_count INT;
9450 BEGIN
9451     FOR circ_mod_group IN SELECT * FROM config.circ_matrix_circ_mod_test LOOP
9452         INSERT INTO config.circ_limit_set(name, owning_lib, items_out, depth, global, description)
9453             SELECT org_unit || ' : Matchpoint ' || circ_mod_group.matchpoint || ' : Circ Mod Test ' || circ_mod_group.id, org_unit, circ_mod_group.items_out, 0, false, 'Migrated from Circ Mod Test System'
9454                 FROM config.circ_matrix_matchpoint WHERE id = circ_mod_group.matchpoint
9455             RETURNING id INTO current_set;
9456         INSERT INTO config.circ_matrix_limit_set_map(matchpoint, limit_set, fallthrough, active) VALUES (circ_mod_group.matchpoint, current_set, false, true);
9457         INSERT INTO config.circ_limit_set_circ_mod_map(limit_set, circ_mod)
9458             SELECT current_set, circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = circ_mod_group.id;
9459         SELECT INTO circ_mod_count count(id) FROM config.circ_limit_set_circ_mod_map WHERE limit_set = current_set;
9460         RAISE NOTICE 'Created limit set with id % and % circ modifiers attached to matchpoint %', current_set, circ_mod_count, circ_mod_group.matchpoint;
9461     END LOOP;
9462 END;
9463 $func$ LANGUAGE plpgsql;
9464
9465 -- Run the temp function
9466 SELECT * FROM evergreen.temp_migrate_circ_mod_limits();
9467
9468 -- Drop the temp function
9469 DROP FUNCTION evergreen.temp_migrate_circ_mod_limits();
9470
9471 --Drop the old tables
9472 --Not sure we want to do this. Keeping them may help "something went wrong" correction.
9473 --DROP TABLE IF EXISTS config.circ_matrix_circ_mod_test_map, config.circ_matrix_circ_mod_test;
9474
9475
9476 -- Evergreen DB patch 0678.data.vandelay-default-merge-profiles.sql
9477
9478 -- check whether patch can be applied
9479 SELECT evergreen.upgrade_deps_block_check('0678', :eg_version);
9480
9481 INSERT INTO vandelay.merge_profile (owner, name, replace_spec) 
9482     VALUES (1, 'Match-Only Merge', '901c');
9483
9484 INSERT INTO vandelay.merge_profile (owner, name, preserve_spec) 
9485     VALUES (1, 'Full Overlay', '901c');
9486
9487 -- Evergreen DB patch 0681.schema.user-activity.sql
9488 --
9489
9490 -- check whether patch can be applied
9491 SELECT evergreen.upgrade_deps_block_check('0681', :eg_version);
9492
9493 -- SCHEMA --
9494
9495 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
9496
9497 CREATE TABLE config.usr_activity_type (
9498     id          SERIAL                      PRIMARY KEY, 
9499     ewho        TEXT,
9500     ewhat       TEXT,
9501     ehow        TEXT,
9502     label       TEXT                        NOT NULL, -- i18n
9503     egroup      config.usr_activity_group   NOT NULL,
9504     enabled     BOOL                        NOT NULL DEFAULT TRUE,
9505     transient   BOOL                        NOT NULL DEFAULT FALSE,
9506     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
9507 );
9508
9509 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
9510     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
9511
9512 CREATE TABLE actor.usr_activity (
9513     id          BIGSERIAL   PRIMARY KEY,
9514     usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
9515     etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
9516     event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
9517 );
9518
9519 -- remove transient activity entries on insert of new entries
9520 CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
9521 BEGIN
9522     DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
9523         WHERE atype.transient AND 
9524             NEW.etype = atype.id AND
9525             act.etype = atype.id AND
9526             act.usr = NEW.usr;
9527     RETURN NEW;
9528 END;
9529 $$ LANGUAGE PLPGSQL;
9530
9531 CREATE TRIGGER remove_transient_usr_activity
9532     BEFORE INSERT ON actor.usr_activity
9533     FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
9534
9535 -- given a set of activity criteria, find the most approprate activity type
9536 CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
9537         ewho TEXT, 
9538         ewhat TEXT, 
9539         ehow TEXT
9540     ) RETURNS SETOF config.usr_activity_type AS $$
9541 SELECT * FROM config.usr_activity_type 
9542     WHERE 
9543         enabled AND 
9544         (ewho  IS NULL OR ewho  = $1) AND
9545         (ewhat IS NULL OR ewhat = $2) AND
9546         (ehow  IS NULL OR ehow  = $3) 
9547     ORDER BY 
9548         -- BOOL comparisons sort false to true
9549         COALESCE(ewho, '')  != COALESCE($1, ''),
9550         COALESCE(ewhat,'')  != COALESCE($2, ''),
9551         COALESCE(ehow, '')  != COALESCE($3, '') 
9552     LIMIT 1;
9553 $$ LANGUAGE SQL;
9554
9555 -- given a set of activity criteria, finds the best
9556 -- activity type and inserts the activity entry
9557 CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
9558         usr INT,
9559         ewho TEXT, 
9560         ewhat TEXT, 
9561         ehow TEXT
9562     ) RETURNS SETOF actor.usr_activity AS $$
9563 DECLARE
9564     new_row actor.usr_activity%ROWTYPE;
9565 BEGIN
9566     SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
9567     IF FOUND THEN
9568         new_row.usr := usr;
9569         INSERT INTO actor.usr_activity (usr, etype) 
9570             VALUES (usr, new_row.etype)
9571             RETURNING * INTO new_row;
9572         RETURN NEXT new_row;
9573     END IF;
9574 END;
9575 $$ LANGUAGE plpgsql;
9576
9577 -- SEED DATA --
9578
9579 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
9580
9581      -- authen/authz actions
9582      -- note: "opensrf" is the default ingress/ehow
9583      (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
9584     ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
9585     ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
9586     ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
9587     ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
9588     ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
9589     ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
9590     ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
9591
9592     ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
9593     ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
9594     ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
9595     ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
9596     ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
9597     ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
9598     ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
9599
9600      -- authen/authz actions w/ known uses of "who"
9601     ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
9602     ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
9603     ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
9604     ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
9605     ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
9606     ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
9607     ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
9608
9609     ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
9610     ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
9611     -- ...
9612     ;
9613
9614 -- reserve the first 1000 slots
9615 SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
9616
9617 INSERT INTO config.org_unit_setting_type 
9618     (name, label, description, grp, datatype) 
9619     VALUES (
9620         'circ.patron.usr_activity_retrieve.max',
9621          oils_i18n_gettext(
9622             'circ.patron.usr_activity_retrieve.max',
9623             'Max user activity entries to retrieve (staff client)',
9624             'coust', 
9625             'label'
9626         ),
9627         oils_i18n_gettext(
9628             'circ.patron.usr_activity_retrieve.max',
9629             'Sets the maxinum number of recent user activity entries to retrieve for display in the staff client.  0 means show none, -1 means show all.  Default is 1.',
9630             'coust', 
9631             'description'
9632         ),
9633         'gui',
9634         'integer'
9635     );
9636
9637
9638 SELECT evergreen.upgrade_deps_block_check('0682', :eg_version);
9639
9640 CREATE TABLE asset.copy_location_group (
9641     id              SERIAL  PRIMARY KEY,
9642     name            TEXT    NOT NULL, -- i18n
9643     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9644     pos             INT     NOT NULL DEFAULT 0,
9645     top             BOOL    NOT NULL DEFAULT FALSE,
9646     opac_visible    BOOL    NOT NULL DEFAULT TRUE,
9647     CONSTRAINT lgroup_once_per_owner UNIQUE (owner,name)
9648 );
9649
9650 CREATE TABLE asset.copy_location_group_map (
9651     id       SERIAL PRIMARY KEY,
9652     location    INT     NOT NULL REFERENCES asset.copy_location (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9653     lgroup      INT     NOT NULL REFERENCES asset.copy_location_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9654     CONSTRAINT  lgroup_once_per_group UNIQUE (lgroup,location)
9655 );
9656
9657 -- check whether patch can be applied
9658 SELECT evergreen.upgrade_deps_block_check('0683', :eg_version);
9659
9660 INSERT INTO action_trigger.event_params (event_def, param, value)
9661     VALUES (5, 'check_email_notify', 1);
9662 INSERT INTO action_trigger.event_params (event_def, param, value)
9663     VALUES (7, 'check_email_notify', 1);
9664 INSERT INTO action_trigger.event_params (event_def, param, value)
9665     VALUES (9, 'check_email_notify', 1);
9666 INSERT INTO action_trigger.validator (module,description) VALUES
9667     ('HoldNotifyCheck',
9668     oils_i18n_gettext(
9669         'HoldNotifyCheck',
9670         'Check Hold notification flag(s)',
9671         'atval',
9672         'description'
9673     ));
9674 UPDATE action_trigger.event_definition SET validator = 'HoldNotifyCheck' WHERE id = 9;
9675
9676 -- NOT COVERED: Adding check_sms_notify to the proper trigger. It doesn't have a static id.
9677
9678 -- check whether patch can be applied
9679 SELECT evergreen.upgrade_deps_block_check('0684', :eg_version);
9680
9681 -- schema --
9682
9683 -- Replace the constraints with more flexible ENUM's
9684 ALTER TABLE vandelay.queue DROP CONSTRAINT queue_queue_type_check;
9685 ALTER TABLE vandelay.bib_queue DROP CONSTRAINT bib_queue_queue_type_check;
9686 ALTER TABLE vandelay.authority_queue DROP CONSTRAINT authority_queue_queue_type_check;
9687
9688 CREATE TYPE vandelay.bib_queue_queue_type AS ENUM ('bib', 'acq');
9689 CREATE TYPE vandelay.authority_queue_queue_type AS ENUM ('authority');
9690
9691 -- dropped column is also implemented by the child tables
9692 ALTER TABLE vandelay.queue DROP COLUMN queue_type; 
9693
9694 -- to recover after using the undo sql from below
9695 -- alter table vandelay.bib_queue  add column queue_type text default 'bib' not null;
9696 -- alter table vandelay.authority_queue  add column queue_type text default 'authority' not null;
9697
9698 -- modify the child tables to use the ENUMs
9699 ALTER TABLE vandelay.bib_queue 
9700     ALTER COLUMN queue_type DROP DEFAULT,
9701     ALTER COLUMN queue_type TYPE vandelay.bib_queue_queue_type 
9702         USING (queue_type::vandelay.bib_queue_queue_type),
9703     ALTER COLUMN queue_type SET DEFAULT 'bib';
9704
9705 ALTER TABLE vandelay.authority_queue 
9706     ALTER COLUMN queue_type DROP DEFAULT,
9707     ALTER COLUMN queue_type TYPE vandelay.authority_queue_queue_type 
9708         USING (queue_type::vandelay.authority_queue_queue_type),
9709     ALTER COLUMN queue_type SET DEFAULT 'authority';
9710
9711 -- give lineitems a pointer to their vandelay queued_record
9712
9713 ALTER TABLE acq.lineitem ADD COLUMN queued_record BIGINT
9714     REFERENCES vandelay.queued_bib_record (id) 
9715     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
9716
9717 ALTER TABLE acq.acq_lineitem_history ADD COLUMN queued_record BIGINT
9718     REFERENCES vandelay.queued_bib_record (id) 
9719     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
9720
9721 -- seed data --
9722
9723 INSERT INTO permission.perm_list ( id, code, description ) 
9724     VALUES ( 
9725         521, 
9726         'IMPORT_ACQ_LINEITEM_BIB_RECORD_UPLOAD', 
9727         oils_i18n_gettext( 
9728             521,
9729             'Allows a user to create new bibs directly from an ACQ MARC file upload', 
9730             'ppl', 
9731             'description' 
9732         )
9733     );
9734
9735
9736 INSERT INTO vandelay.import_error ( code, description ) 
9737     VALUES ( 
9738         'import.record.perm_failure', 
9739         oils_i18n_gettext(
9740             'import.record.perm_failure', 
9741             'Perm failure creating a record', 'vie', 'description') 
9742     );
9743
9744
9745
9746
9747 -- Evergreen DB patch 0685.data.bluray_vr_format.sql
9748 --
9749 -- FIXME: insert description of change, if needed
9750 --
9751
9752
9753 -- check whether patch can be applied
9754 SELECT evergreen.upgrade_deps_block_check('0685', :eg_version);
9755
9756 -- FIXME: add/check SQL statements to perform the upgrade
9757 DO $FUNC$
9758 DECLARE
9759     same_marc BOOL;
9760 BEGIN
9761     -- Check if it is already there
9762     PERFORM * FROM config.marc21_physical_characteristic_value_map v
9763         JOIN config.marc21_physical_characteristic_subfield_map s ON v.ptype_subfield = s.id
9764         WHERE s.ptype_key = 'v' AND s.subfield = 'e' AND s.start_pos = '4' AND s.length = '1'
9765             AND v.value = 's';
9766
9767     -- If it is, bail.
9768     IF FOUND THEN
9769         RETURN;
9770     END IF;
9771
9772     -- Otherwise, insert it
9773     INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label)
9774     SELECT 's',id,'Blu-ray'
9775         FROM config.marc21_physical_characteristic_subfield_map
9776         WHERE ptype_key = 'v' AND subfield = 'e' AND start_pos = '4' AND length = '1';
9777
9778     -- And reingest the blue-ray items so that things see the new value
9779     SELECT INTO same_marc enabled FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc';
9780     UPDATE config.internal_flag SET enabled = true WHERE name = 'ingest.reingest.force_on_same_marc';
9781     UPDATE biblio.record_entry SET marc=marc WHERE id IN (SELECT record
9782         FROM
9783             metabib.full_rec a JOIN metabib.full_rec b USING (record)
9784         WHERE
9785             a.tag = 'LDR' AND a.value LIKE '______g%'
9786         AND b.tag = '007' AND b.value LIKE 'v___s%');
9787     UPDATE config.internal_flag SET enabled = same_marc WHERE name = 'ingest.reingest.force_on_same_marc';
9788 END;
9789 $FUNC$;
9790
9791
9792 -- Evergreen DB patch 0686.schema.auditor_boost.sql
9793 --
9794 -- FIXME: insert description of change, if needed
9795 --
9796 -- check whether patch can be applied
9797 SELECT evergreen.upgrade_deps_block_check('0686', :eg_version);
9798
9799 -- FIXME: add/check SQL statements to perform the upgrade
9800 -- These three functions are for capturing, getting, and clearing user and workstation information
9801
9802 -- Set the User AND workstation in one call. Tis faster. And less calls.
9803 -- First argument is user, second is workstation
9804 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
9805     $_SHARED{"eg_audit_user"} = $_[0];
9806     $_SHARED{"eg_audit_ws"} = $_[1];
9807 $$ LANGUAGE plperl;
9808
9809 -- Get the User AND workstation in one call. Less calls, useful for joins ;)
9810 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
9811     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
9812 $$ LANGUAGE plperl;
9813
9814 -- Clear the audit info, for whatever reason
9815 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
9816     delete($_SHARED{"eg_audit_user"});
9817     delete($_SHARED{"eg_audit_ws"});
9818 $$ LANGUAGE plperl;
9819
9820 CREATE OR REPLACE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9821 BEGIN
9822     EXECUTE $$
9823         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
9824             audit_id    BIGINT                          PRIMARY KEY,
9825             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
9826             audit_action        TEXT                            NOT NULL,
9827             audit_user  INT,
9828             audit_ws    INT,
9829             LIKE $$ || sch || $$.$$ || tbl || $$
9830         );
9831     $$;
9832         RETURN TRUE;
9833 END;
9834 $creator$ LANGUAGE 'plpgsql';
9835
9836 CREATE OR REPLACE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9837 DECLARE
9838     column_list TEXT[];
9839 BEGIN
9840     SELECT INTO column_list array_agg(a.attname)
9841         FROM pg_catalog.pg_attribute a
9842             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
9843             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9844         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
9845
9846     EXECUTE $$
9847         CREATE OR REPLACE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
9848         RETURNS TRIGGER AS $func$
9849         BEGIN
9850             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, $$
9851             || array_to_string(column_list, ', ') || $$ )
9852                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
9853                     now(),
9854                     SUBSTR(TG_OP,1,1),
9855                     eg_user,
9856                     eg_ws,
9857                     OLD.$$ || array_to_string(column_list, ', OLD.') || $$
9858                 FROM auditor.get_audit_info();
9859             RETURN NULL;
9860         END;
9861         $func$ LANGUAGE 'plpgsql';
9862     $$;
9863     RETURN TRUE;
9864 END;
9865 $creator$ LANGUAGE 'plpgsql';
9866
9867 CREATE OR REPLACE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9868 DECLARE
9869     column_list TEXT[];
9870 BEGIN
9871     SELECT INTO column_list array_agg(a.attname)
9872         FROM pg_catalog.pg_attribute a
9873             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
9874             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9875         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
9876
9877     EXECUTE $$
9878         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
9879             SELECT -1 AS audit_id,
9880                    now() AS audit_time,
9881                    '-' AS audit_action,
9882                    -1 AS audit_user,
9883                    -1 AS audit_ws,
9884                    $$ || array_to_string(column_list, ', ') || $$
9885               FROM $$ || sch || $$.$$ || tbl || $$
9886                 UNION ALL
9887             SELECT audit_id, audit_time, audit_action, audit_user, audit_ws,
9888             $$ || array_to_string(column_list, ', ') || $$
9889               FROM auditor.$$ || sch || $$_$$ || tbl || $$_history;
9890     $$;
9891     RETURN TRUE;
9892 END;
9893 $creator$ LANGUAGE 'plpgsql';
9894
9895 -- Corrects all column discrepencies between audit table and core table:
9896 -- Adds missing columns
9897 -- Removes leftover columns
9898 -- Updates types
9899 -- Also, ensures all core auditor columns exist.
9900 CREATE OR REPLACE FUNCTION auditor.fix_columns() RETURNS VOID AS $BODY$
9901 DECLARE
9902     current_table TEXT = ''; -- Storage for post-loop main table name
9903     current_audit_table TEXT = ''; -- Storage for post-loop audit table name
9904     query TEXT = ''; -- Storage for built query
9905     cr RECORD; -- column record object
9906     alter_t BOOL = false; -- Has the alter table command been appended yet
9907     auditor_cores TEXT[] = ARRAY[]::TEXT[]; -- Core auditor function list (filled inside of loop)
9908     core_column TEXT; -- The current core column we are adding
9909 BEGIN
9910     FOR cr IN
9911         WITH audit_tables AS ( -- Basic grab of auditor tables. Anything in the auditor namespace, basically. With oids.
9912             SELECT c.oid AS audit_oid, c.relname AS audit_table
9913             FROM pg_catalog.pg_class c
9914             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9915             WHERE relkind='r' AND nspname = 'auditor'
9916         ),
9917         table_set AS ( -- Union of auditor tables with their "main" tables. With oids.
9918             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
9919             FROM pg_catalog.pg_class c
9920             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9921             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
9922             WHERE relkind = 'r'
9923         ),
9924         column_lists AS ( -- All columns associated with the auditor or main table, grouped by the main table's oid.
9925             SELECT DISTINCT ON (main_oid, attname) t.main_oid, a.attname
9926             FROM table_set t
9927             JOIN pg_catalog.pg_attribute a ON a.attrelid IN (t.main_oid, t.audit_oid)
9928             WHERE attnum > 0 AND NOT attisdropped
9929         ),
9930         column_defs AS ( -- The motherload, every audit table and main table plus column names and defs.
9931             SELECT audit_table,
9932                    main_namespace,
9933                    main_table,
9934                    a.attname AS main_column, -- These two will be null for columns that have since been deleted, or for auditor core columns
9935                    pg_catalog.format_type(a.atttypid, a.atttypmod) AS main_column_def,
9936                    b.attname AS audit_column, -- These two will be null for columns that have since been added
9937                    pg_catalog.format_type(b.atttypid, b.atttypmod) AS audit_column_def
9938             FROM table_set t
9939             JOIN column_lists c USING (main_oid)
9940             LEFT JOIN pg_catalog.pg_attribute a ON a.attname = c.attname AND a.attrelid = t.main_oid AND a.attnum > 0 AND NOT a.attisdropped
9941             LEFT JOIN pg_catalog.pg_attribute b ON b.attname = c.attname AND b.attrelid = t.audit_oid AND b.attnum > 0 AND NOT b.attisdropped
9942         )
9943         -- Nice sorted output from the above
9944         SELECT * FROM column_defs WHERE main_column_def IS DISTINCT FROM audit_column_def ORDER BY main_namespace, main_table, main_column, audit_column
9945     LOOP
9946         IF current_table <> (cr.main_namespace || '.' || cr.main_table) THEN -- New table?
9947             FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Update missing core auditor columns
9948                 IF NOT alter_t THEN -- Add ALTER TABLE if we haven't already
9949                     query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9950                     alter_t:=TRUE;
9951                 ELSE
9952                     query:=query || $$,$$;
9953                 END IF;
9954                 -- Bit of a sneaky bit here. Create audit_id as a bigserial so it gets automatic values and doesn't complain about nulls when becoming a PRIMARY KEY.
9955                 query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
9956             END LOOP;
9957             IF alter_t THEN -- Open alter table = needs a semicolon
9958                 query:=query || $$; $$;
9959                 alter_t:=FALSE;
9960                 IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
9961                     -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
9962                     -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
9963                     EXECUTE query;
9964                     EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
9965                         $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
9966                     EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
9967                         $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
9968                     query:='';
9969                 END IF;
9970             END IF;
9971             -- New table means we reset the list of needed auditor core columns
9972             auditor_cores = ARRAY['audit_id bigint', 'audit_time timestamp with time zone', 'audit_action text', 'audit_user integer', 'audit_ws integer'];
9973             -- And store some values for use later, because we can't rely on cr in all places.
9974             current_table:=cr.main_namespace || '.' || cr.main_table;
9975             current_audit_table:=cr.audit_table;
9976         END IF;
9977         IF cr.main_column IS NULL AND cr.audit_column LIKE 'audit_%' THEN -- Core auditor column?
9978             -- Remove core from list of cores
9979             SELECT INTO auditor_cores array_agg(core) FROM unnest(auditor_cores) AS core WHERE core != (cr.audit_column || ' ' || cr.audit_column_def);
9980         ELSIF cr.main_column IS NULL THEN -- Main column doesn't exist, and it isn't an auditor column. Needs dropping from the auditor.
9981             IF NOT alter_t THEN
9982                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9983                 alter_t:=TRUE;
9984             ELSE
9985                 query:=query || $$,$$;
9986             END IF;
9987             query:=query || $$ DROP COLUMN $$ || cr.audit_column;
9988         ELSIF cr.audit_column IS NULL AND cr.main_column IS NOT NULL THEN -- New column auditor doesn't have. Add it.
9989             IF NOT alter_t THEN
9990                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9991                 alter_t:=TRUE;
9992             ELSE
9993                 query:=query || $$,$$;
9994             END IF;
9995             query:=query || $$ ADD COLUMN $$ || cr.main_column || $$ $$ || cr.main_column_def;
9996         ELSIF cr.main_column IS NOT NULL AND cr.audit_column IS NOT NULL THEN -- Both sides have this column, but types differ. Fix that.
9997             IF NOT alter_t THEN
9998                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9999                 alter_t:=TRUE;
10000             ELSE
10001                 query:=query || $$,$$;
10002             END IF;
10003             query:=query || $$ ALTER COLUMN $$ || cr.audit_column || $$ TYPE $$ || cr.main_column_def;
10004         END IF;
10005     END LOOP;
10006     FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Repeat this outside of the loop to catch the last table
10007         IF NOT alter_t THEN
10008             query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
10009             alter_t:=TRUE;
10010         ELSE
10011             query:=query || $$,$$;
10012         END IF;
10013         -- Bit of a sneaky bit here. Create audit_id as a bigserial so it gets automatic values and doesn't complain about nulls when becoming a PRIMARY KEY.
10014         query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
10015     END LOOP;
10016     IF alter_t THEN -- Open alter table = needs a semicolon
10017         query:=query || $$;$$;
10018         IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
10019             -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
10020             -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
10021             EXECUTE query;
10022             EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
10023                 $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
10024             EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
10025                 $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
10026             query:='';
10027         END IF;
10028     END IF;
10029     EXECUTE query;
10030 END;
10031 $BODY$ LANGUAGE plpgsql;
10032
10033 -- Update it all routine
10034 CREATE OR REPLACE FUNCTION auditor.update_auditors() RETURNS boolean AS $BODY$
10035 DECLARE
10036     auditor_name TEXT;
10037     table_schema TEXT;
10038     table_name TEXT;
10039 BEGIN
10040     -- Drop Lifecycle view(s) before potential column changes
10041     FOR auditor_name IN
10042         SELECT c.relname
10043             FROM pg_catalog.pg_class c
10044                 JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10045             WHERE relkind = 'v' AND n.nspname = 'auditor' LOOP
10046         EXECUTE $$ DROP VIEW auditor.$$ || auditor_name || $$;$$;
10047     END LOOP;
10048     -- Fix all column discrepencies
10049     PERFORM auditor.fix_columns();
10050     -- Re-create trigger functions and lifecycle views
10051     FOR table_schema, table_name IN
10052         WITH audit_tables AS (
10053             SELECT c.oid AS audit_oid, c.relname AS audit_table
10054             FROM pg_catalog.pg_class c
10055             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10056             WHERE relkind='r' AND nspname = 'auditor'
10057         ),
10058         table_set AS (
10059             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
10060             FROM pg_catalog.pg_class c
10061             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10062             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
10063             WHERE relkind = 'r'
10064         )
10065         SELECT main_namespace, main_table FROM table_set LOOP
10066         
10067         PERFORM auditor.create_auditor_func(table_schema, table_name);
10068         PERFORM auditor.create_auditor_lifecycle(table_schema, table_name);
10069     END LOOP;
10070     RETURN TRUE;
10071 END;
10072 $BODY$ LANGUAGE plpgsql;
10073
10074 -- Go ahead and update them all now
10075 SELECT auditor.update_auditors();
10076
10077
10078 -- Evergreen DB patch 0687.schema.enhance_reingest.sql
10079 --
10080 -- FIXME: insert description of change, if needed
10081 --
10082
10083
10084 -- check whether patch can be applied
10085 SELECT evergreen.upgrade_deps_block_check('0687', :eg_version);
10086 SELECT evergreen.upgrade_deps_block_check('0711', :eg_version); -- introduces
10087 -- changes to metabib.reingest_metabib_field_entries() that must happen here
10088 -- rather than later in a separate CREATE OR REPLACE FUNCTION statement.
10089
10090 -- FIXME: add/check SQL statements to perform the upgrade
10091 -- New function def
10092 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT, skip_facet BOOL DEFAULT FALSE, skip_browse BOOL DEFAULT FALSE, skip_search BOOL DEFAULT FALSE ) RETURNS VOID AS $func$
10093 DECLARE
10094     fclass          RECORD;
10095     ind_data        metabib.field_entry_template%ROWTYPE;
10096     mbe_row         metabib.browse_entry%ROWTYPE;
10097     mbe_id          BIGINT;
10098     normalized_value    TEXT;
10099 BEGIN
10100     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10101     IF NOT FOUND THEN
10102         IF NOT skip_search THEN
10103             FOR fclass IN SELECT * FROM config.metabib_class LOOP
10104                 -- RAISE NOTICE 'Emptying out %', fclass.name;
10105                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10106             END LOOP;
10107         END IF;
10108         IF NOT skip_facet THEN
10109             DELETE FROM metabib.facet_entry WHERE source = bib_id;
10110         END IF;
10111         IF NOT skip_browse THEN
10112             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
10113         END IF;
10114     END IF;
10115
10116     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10117         IF ind_data.field < 0 THEN
10118             ind_data.field = -1 * ind_data.field;
10119         END IF;
10120
10121         IF ind_data.facet_field AND NOT skip_facet THEN
10122             INSERT INTO metabib.facet_entry (field, source, value)
10123                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10124         END IF;
10125
10126         IF ind_data.browse_field AND NOT skip_browse THEN
10127             -- A caveat about this SELECT: this should take care of replacing
10128             -- old mbe rows when data changes, but not if normalization (by
10129             -- which I mean specifically the output of
10130             -- evergreen.oils_tsearch2()) changes.  It may or may not be
10131             -- expensive to add a comparison of index_vector to index_vector
10132             -- to the WHERE clause below.
10133             normalized_value := metabib.browse_normalize(
10134                 ind_data.value, ind_data.field
10135             );
10136
10137             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = normalized_value;
10138             IF FOUND THEN
10139                 mbe_id := mbe_row.id;
10140             ELSE
10141                 INSERT INTO metabib.browse_entry (value) VALUES (normalized_value);
10142                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
10143             END IF;
10144
10145             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
10146                 VALUES (mbe_id, ind_data.field, ind_data.source);
10147         END IF;
10148
10149         IF ind_data.search_field AND NOT skip_search THEN
10150             EXECUTE $$
10151                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10152                     VALUES ($$ ||
10153                         quote_literal(ind_data.field) || $$, $$ ||
10154                         quote_literal(ind_data.source) || $$, $$ ||
10155                         quote_literal(ind_data.value) ||
10156                     $$);$$;
10157         END IF;
10158
10159     END LOOP;
10160
10161     RETURN;
10162 END;
10163 $func$ LANGUAGE PLPGSQL;
10164
10165 -- Delete old one
10166 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
10167
10168 -- Evergreen DB patch 0688.data.circ_history_export_csv.sql
10169 --
10170 -- FIXME: insert description of change, if needed
10171 --
10172
10173 -- check whether patch can be applied
10174 SELECT evergreen.upgrade_deps_block_check('0688', :eg_version);
10175
10176 INSERT INTO action_trigger.hook (key, core_type, description, passive)
10177 VALUES (
10178     'circ.format.history.csv',
10179     'circ',
10180     oils_i18n_gettext(
10181         'circ.format.history.csv',
10182         'Produce CSV of circulation history',
10183         'ath',
10184         'description'
10185     ),
10186     FALSE
10187 );
10188
10189 INSERT INTO action_trigger.event_definition (
10190     active, owner, name, hook, reactor, validator, group_field, template) 
10191 VALUES (
10192     TRUE, 1, 'Circ History CSV', 'circ.format.history.csv', 'ProcessTemplate', 'NOOP_True', 'usr',
10193 $$
10194 Title,Author,Call Number,Barcode,Format
10195 [%-
10196 FOR circ IN target;
10197     bibxml = helpers.unapi_bre(circ.target_copy.call_number.record, {flesh => '{mra}'});
10198     title = "";
10199     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10200         title = title _ part.textContent;
10201     END;
10202     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10203     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value') %]
10204
10205     [%- helpers.csv_datum(title) -%],
10206     [%- helpers.csv_datum(author) -%],
10207     [%- helpers.csv_datum(circ.target_copy.call_number.label) -%],
10208     [%- helpers.csv_datum(circ.target_copy.barcode) -%],
10209     [%- helpers.csv_datum(item_type) %]
10210 [%- END -%]
10211 $$
10212 );
10213
10214 INSERT INTO action_trigger.environment (event_def, path)
10215     VALUES (
10216         currval('action_trigger.event_definition_id_seq'),
10217         'target_copy.call_number'
10218     );
10219
10220
10221 -- Evergreen DB patch 0689.data.record_print_format_update.sql
10222 --
10223 -- Updates print and email templates for bib record actions
10224 --
10225
10226 -- check whether patch can be applied
10227 SELECT evergreen.upgrade_deps_block_check('0689', :eg_version);
10228
10229 UPDATE action_trigger.event_definition SET template = $$
10230 <div>
10231     <style> li { padding: 8px; margin 5px; }</style>
10232     <ol>
10233     [% FOR cbreb IN target %]
10234     [% FOR item IN cbreb.items;
10235         bre_id = item.target_biblio_record_entry;
10236
10237         bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10238         FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10239             title = title _ part.textContent;
10240         END;
10241
10242         author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10243         item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10244         publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10245         pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10246         isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10247         issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10248         upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10249         %]
10250
10251         <li>
10252             Bib ID# [% bre_id %]<br/>
10253             [% IF isbn %]ISBN: [% isbn %]<br/>[% END %]
10254             [% IF issn %]ISSN: [% issn %]<br/>[% END %]
10255             [% IF upc  %]UPC:  [% upc %]<br/>[% END %]
10256             Title: [% title %]<br />
10257             Author: [% author %]<br />
10258             Publication Info: [% publisher %] [% pubdate %]<br/>
10259             Item Type: [% item_type %]
10260         </li>
10261     [% END %]
10262     [% END %]
10263     </ol>
10264 </div>
10265 $$ 
10266 WHERE hook = 'biblio.format.record_entry.print' AND id < 100; -- sample data
10267
10268
10269 UPDATE action_trigger.event_definition SET delay = '00:00:00', template = $$
10270 [%- SET user = target.0.owner -%]
10271 To: [%- params.recipient_email || user.email %]
10272 From: [%- params.sender_email || default_sender %]
10273 Subject: Bibliographic Records
10274
10275 [% FOR cbreb IN target %]
10276 [% FOR item IN cbreb.items;
10277     bre_id = item.target_biblio_record_entry;
10278
10279     bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10280     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10281         title = title _ part.textContent;
10282     END;
10283
10284     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10285     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10286     publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10287     pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10288     isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10289     issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10290     upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10291 %]
10292
10293 [% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
10294 [% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
10295 [% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
10296 [% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
10297 Title: [% title %]
10298 Author: [% author %]
10299 Publication Info: [% publisher %] [% pubdate %]
10300 Item Type: [% item_type %]
10301
10302 [% END %]
10303 [% END %]
10304 $$ 
10305 WHERE hook = 'biblio.format.record_entry.email' AND id < 100; -- sample data
10306
10307 -- remove a swath of unused environment entries
10308
10309 DELETE FROM action_trigger.environment env 
10310     USING action_trigger.event_definition def 
10311     WHERE env.event_def = def.id AND 
10312         env.path != 'items' AND 
10313         def.hook = 'biblio.format.record_entry.print' AND 
10314         def.id < 100; -- sample data
10315
10316 DELETE FROM action_trigger.environment env 
10317     USING action_trigger.event_definition def 
10318     WHERE env.event_def = def.id AND 
10319         env.path != 'items' AND 
10320         env.path != 'owner' AND 
10321         def.hook = 'biblio.format.record_entry.email' AND 
10322         def.id < 100; -- sample data
10323
10324 -- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
10325 --
10326 -- Rewrite the in-database unapi functions to include per-object limits and
10327 -- offsets, such as a maximum number of copies and call numbers for given
10328 -- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
10329 -- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
10330 -- call number).
10331 --
10332 -- Add some notion of "preferred library" that will provide copy counts
10333 -- and optionally affect the sorting of returned copies.
10334 --
10335 -- Sort copies by availability, preferring the most available copies.
10336 --
10337 -- Return located URIs.
10338 --
10339 --
10340
10341 -- check whether patch can be applied
10342 SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
10343
10344 -- The simplest way to apply all of these changes is just to replace the unapi
10345 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
10346 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
10347 -- if it does, then the attempt to create the unapi schema in the following
10348 -- transaction will also fail. Not graceful, but safe!
10349 DROP SCHEMA IF EXISTS unapi CASCADE;
10350
10351 CREATE SCHEMA unapi;
10352
10353 CREATE OR REPLACE FUNCTION evergreen.org_top()
10354 RETURNS SETOF actor.org_unit AS $$
10355     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
10356 $$ LANGUAGE SQL STABLE
10357 ROWS 1;
10358
10359 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
10360 RETURNS anyarray AS $$
10361     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
10362 $$ LANGUAGE SQL STABLE;
10363
10364 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
10365 RETURNS INTEGER AS $$
10366     WITH search_libs AS (
10367         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
10368     )
10369     SELECT COALESCE(
10370         (SELECT -10000 FROM actor.org_unit
10371          WHERE $1 = $3 AND id = $3 AND $2 IN (
10372                 SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
10373              )
10374         ),
10375         (SELECT distance FROM search_libs WHERE id = $1),
10376         10000
10377     );
10378 $$ LANGUAGE SQL STABLE;
10379
10380 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
10381 RETURNS INTEGER AS $$
10382     WITH totally_available AS (
10383         SELECT id, 0 AS avail_rank
10384         FROM config.copy_status
10385         WHERE opac_visible IS TRUE
10386             AND copy_active IS TRUE
10387             AND id != 1 -- "Checked out"
10388     ), almost_available AS (
10389         SELECT id, 10 AS avail_rank
10390         FROM config.copy_status
10391         WHERE holdable IS TRUE
10392             AND opac_visible IS TRUE
10393             AND copy_active IS FALSE
10394             OR id = 1 -- "Checked out"
10395     )
10396     SELECT COALESCE(
10397         (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
10398         (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
10399         100
10400     );
10401 $$ LANGUAGE SQL STABLE;
10402
10403 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
10404     bibid BIGINT, 
10405     ouid INT,
10406     depth INT DEFAULT NULL,
10407     slimit HSTORE DEFAULT NULL,
10408     soffset HSTORE DEFAULT NULL,
10409     pref_lib INT DEFAULT NULL
10410 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
10411     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
10412         SELECT acn.id, aou.name, acn.label_sortkey,
10413             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
10414             RANK() OVER w
10415         FROM asset.call_number acn
10416             JOIN asset.copy acp ON (acn.id = acp.call_number)
10417             JOIN actor.org_unit_descendants( $2, COALESCE(
10418                 $3, (
10419                     SELECT depth
10420                     FROM actor.org_unit_type aout
10421                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
10422                     WHERE ou.id = $2
10423                 ), $6)
10424             ) AS aou ON (acp.circ_lib = aou.id)
10425         WHERE acn.record = $1
10426             AND acn.deleted IS FALSE
10427             AND acp.deleted IS FALSE
10428         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
10429         WINDOW w AS (
10430             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
10431         )
10432     ) AS ua
10433     GROUP BY ua.id, ua.name, ua.label_sortkey
10434     ORDER BY rank, ua.name, ua.label_sortkey
10435     LIMIT ($4 -> 'acn')::INT
10436     OFFSET ($5 -> 'acn')::INT;
10437 $$
10438 LANGUAGE SQL STABLE;
10439
10440 CREATE OR REPLACE FUNCTION evergreen.located_uris (
10441     bibid BIGINT, 
10442     ouid INT,
10443     pref_lib INT DEFAULT NULL
10444 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
10445     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10446       FROM asset.call_number acn
10447            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10448            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10449            INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
10450       WHERE acn.record = $1
10451           AND acn.deleted IS FALSE
10452           AND auri.active IS TRUE
10453     UNION
10454     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10455       FROM asset.call_number acn
10456            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10457            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10458            INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
10459       WHERE acn.record = $1
10460           AND acn.deleted IS FALSE
10461           AND auri.active IS TRUE;
10462 $$
10463 LANGUAGE SQL STABLE;
10464
10465 CREATE TABLE unapi.bre_output_layout (
10466     name                TEXT    PRIMARY KEY,
10467     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
10468     mime_type           TEXT    NOT NULL,
10469     feed_top            TEXT    NOT NULL,
10470     holdings_element    TEXT,
10471     title_element       TEXT,
10472     description_element TEXT,
10473     creator_element     TEXT,
10474     update_ts_element   TEXT
10475 );
10476
10477 INSERT INTO unapi.bre_output_layout
10478     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
10479         VALUES
10480     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
10481     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
10482     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
10483 ;
10484
10485 -- Dummy functions, so we can create the real ones out of order
10486 CREATE OR REPLACE FUNCTION unapi.aou    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10487 CREATE OR REPLACE FUNCTION unapi.acnp   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10488 CREATE OR REPLACE FUNCTION unapi.acns   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10489 CREATE OR REPLACE FUNCTION unapi.acn    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10490 CREATE OR REPLACE FUNCTION unapi.ssub   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10491 CREATE OR REPLACE FUNCTION unapi.sdist  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10492 CREATE OR REPLACE FUNCTION unapi.sstr   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10493 CREATE OR REPLACE FUNCTION unapi.sitem  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10494 CREATE OR REPLACE FUNCTION unapi.sunit  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10495 CREATE OR REPLACE FUNCTION unapi.sisum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10496 CREATE OR REPLACE FUNCTION unapi.sbsum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10497 CREATE OR REPLACE FUNCTION unapi.sssum  ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10498 CREATE OR REPLACE FUNCTION unapi.siss   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10499 CREATE OR REPLACE FUNCTION unapi.auri   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10500 CREATE OR REPLACE FUNCTION unapi.acp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10501 CREATE OR REPLACE FUNCTION unapi.acpn   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10502 CREATE OR REPLACE FUNCTION unapi.acl    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10503 CREATE OR REPLACE FUNCTION unapi.ccs    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10504 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10505 CREATE OR REPLACE FUNCTION unapi.bre (
10506     obj_id BIGINT,
10507     format TEXT,
10508     ename TEXT,
10509     includes TEXT[],
10510     org TEXT,
10511     depth INT DEFAULT NULL,
10512     slimit HSTORE DEFAULT NULL,
10513     soffset HSTORE DEFAULT NULL,
10514     include_xmlns BOOL DEFAULT TRUE,
10515     pref_lib INT DEFAULT NULL
10516 )
10517 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10518 CREATE OR REPLACE FUNCTION unapi.bmp    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10519 CREATE OR REPLACE FUNCTION unapi.mra    ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10520 CREATE OR REPLACE FUNCTION unapi.circ   ( obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10521
10522 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10523     bid BIGINT,
10524     ouid INT,
10525     org TEXT,
10526     depth INT DEFAULT NULL,
10527     includes TEXT[] DEFAULT NULL::TEXT[],
10528     slimit HSTORE DEFAULT NULL,
10529     soffset HSTORE DEFAULT NULL,
10530     include_xmlns BOOL DEFAULT TRUE,
10531     pref_lib INT DEFAULT NULL
10532 )
10533 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10534
10535 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE 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 STABLE;
10536
10537 CREATE OR REPLACE FUNCTION unapi.memoize (classname TEXT, obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10538 DECLARE
10539     key     TEXT;
10540     output  XML;
10541 BEGIN
10542     key :=
10543         'id'        || COALESCE(obj_id::TEXT,'') ||
10544         'format'    || COALESCE(format::TEXT,'') ||
10545         'ename'     || COALESCE(ename::TEXT,'') ||
10546         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
10547         'org'       || COALESCE(org::TEXT,'') ||
10548         'depth'     || COALESCE(depth::TEXT,'') ||
10549         'slimit'    || COALESCE(slimit::TEXT,'') ||
10550         'soffset'   || COALESCE(soffset::TEXT,'') ||
10551         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
10552     -- RAISE NOTICE 'memoize key: %', key;
10553
10554     key := MD5(key);
10555     -- RAISE NOTICE 'memoize hash: %', key;
10556
10557     -- XXX cache logic ... memcached? table?
10558
10559     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;
10560     RETURN output;
10561 END;
10562 $F$ LANGUAGE PLPGSQL STABLE;
10563
10564 CREATE OR REPLACE FUNCTION unapi.biblio_record_entry_feed ( id_list BIGINT[], format TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE 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$
10565 DECLARE
10566     layout          unapi.bre_output_layout%ROWTYPE;
10567     transform       config.xml_transform%ROWTYPE;
10568     item_format     TEXT;
10569     tmp_xml         TEXT;
10570     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
10571     ouid            INT;
10572     element_list    TEXT[];
10573 BEGIN
10574
10575     IF org = '-' OR org IS NULL THEN
10576         SELECT shortname INTO org FROM evergreen.org_top();
10577     END IF;
10578
10579     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10580     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10581
10582     IF layout.name IS NULL THEN
10583         RETURN NULL::XML;
10584     END IF;
10585
10586     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
10587     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
10588
10589     -- Gather the bib xml
10590     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
10591
10592     IF layout.title_element IS NOT NULL THEN
10593         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
10594     END IF;
10595
10596     IF layout.description_element IS NOT NULL THEN
10597         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
10598     END IF;
10599
10600     IF layout.creator_element IS NOT NULL THEN
10601         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
10602     END IF;
10603
10604     IF layout.update_ts_element IS NOT NULL THEN
10605         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts;
10606     END IF;
10607
10608     IF unapi_url IS NOT NULL THEN
10609         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;
10610     END IF;
10611
10612     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
10613
10614     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
10615     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
10616         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
10617     END LOOP;
10618
10619     RETURN tmp_xml::XML;
10620 END;
10621 $F$ LANGUAGE PLPGSQL STABLE;
10622
10623 CREATE OR REPLACE FUNCTION unapi.bre (
10624     obj_id BIGINT,
10625     format TEXT,
10626     ename TEXT,
10627     includes TEXT[],
10628     org TEXT,
10629     depth INT DEFAULT NULL,
10630     slimit HSTORE DEFAULT NULL,
10631     soffset HSTORE DEFAULT NULL,
10632     include_xmlns BOOL DEFAULT TRUE,
10633     pref_lib INT DEFAULT NULL
10634 )
10635 RETURNS XML AS $F$
10636 DECLARE
10637     me      biblio.record_entry%ROWTYPE;
10638     layout  unapi.bre_output_layout%ROWTYPE;
10639     xfrm    config.xml_transform%ROWTYPE;
10640     ouid    INT;
10641     tmp_xml TEXT;
10642     top_el  TEXT;
10643     output  XML;
10644     hxml    XML;
10645     axml    XML;
10646 BEGIN
10647
10648     IF org = '-' OR org IS NULL THEN
10649         SELECT shortname INTO org FROM evergreen.org_top();
10650     END IF;
10651
10652     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10653
10654     IF ouid IS NULL THEN
10655         RETURN NULL::XML;
10656     END IF;
10657
10658     IF format = 'holdings_xml' THEN -- the special case
10659         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
10660         RETURN output;
10661     END IF;
10662
10663     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10664
10665     IF layout.name IS NULL THEN
10666         RETURN NULL::XML;
10667     END IF;
10668
10669     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
10670
10671     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
10672
10673     -- grab SVF if we need them
10674     IF ('mra' = ANY (includes)) THEN 
10675         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
10676     ELSE
10677         axml := NULL::XML;
10678     END IF;
10679
10680     -- grab holdings if we need them
10681     IF ('holdings_xml' = ANY (includes)) THEN 
10682         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
10683     ELSE
10684         hxml := NULL::XML;
10685     END IF;
10686
10687
10688     -- generate our item node
10689
10690
10691     IF format = 'marcxml' THEN
10692         tmp_xml := me.marc;
10693         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
10694            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
10695         END IF; 
10696     ELSE
10697         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
10698     END IF;
10699
10700     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
10701
10702     IF axml IS NOT NULL THEN 
10703         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
10704     END IF;
10705
10706     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
10707         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
10708     END IF;
10709
10710     IF ('bre.unapi' = ANY (includes)) THEN 
10711         output := REGEXP_REPLACE(
10712             tmp_xml,
10713             '</' || top_el || '>(.*?)',
10714             XMLELEMENT(
10715                 name abbr,
10716                 XMLATTRIBUTES(
10717                     'http://www.w3.org/1999/xhtml' AS xmlns,
10718                     'unapi-id' AS class,
10719                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
10720                 )
10721             )::TEXT || '</' || top_el || E'>\\1'
10722         );
10723     ELSE
10724         output := tmp_xml;
10725     END IF;
10726
10727     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
10728     RETURN output;
10729 END;
10730 $F$ LANGUAGE PLPGSQL STABLE;
10731
10732 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10733     bid BIGINT,
10734     ouid INT,
10735     org TEXT,
10736     depth INT DEFAULT NULL,
10737     includes TEXT[] DEFAULT NULL::TEXT[],
10738     slimit HSTORE DEFAULT NULL,
10739     soffset HSTORE DEFAULT NULL,
10740     include_xmlns BOOL DEFAULT TRUE,
10741     pref_lib INT DEFAULT NULL
10742 )
10743 RETURNS XML AS $F$
10744      SELECT  XMLELEMENT(
10745                  name holdings,
10746                  XMLATTRIBUTES(
10747                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10748                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
10749                  ),
10750                  XMLELEMENT(
10751                      name counts,
10752                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
10753                          SELECT  XMLELEMENT(
10754                                      name count,
10755                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10756                                  )::text
10757                            FROM  asset.opac_ou_record_copy_count($2,  $1)
10758                                      UNION
10759                          SELECT  XMLELEMENT(
10760                                      name count,
10761                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10762                                  )::text
10763                            FROM  asset.staff_ou_record_copy_count($2, $1)
10764                                      UNION
10765                          SELECT  XMLELEMENT(
10766                                      name count,
10767                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10768                                  )::text
10769                            FROM  asset.opac_ou_record_copy_count($9,  $1)
10770                                      ORDER BY 1
10771                      )x)
10772                  ),
10773                  CASE 
10774                      WHEN ('bmp' = ANY ($5)) THEN
10775                         XMLELEMENT(
10776                             name monograph_parts,
10777                             (SELECT XMLAGG(bmp) FROM (
10778                                 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)
10779                                   FROM  biblio.monograph_part
10780                                   WHERE record = $1
10781                             )x)
10782                         )
10783                      ELSE NULL
10784                  END,
10785                  XMLELEMENT(
10786                      name volumes,
10787                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
10788                         -- Physical copies
10789                         SELECT  unapi.acn(y.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
10790                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
10791                         UNION ALL
10792                         -- Located URIs
10793                         SELECT unapi.acn(uris.id,'xml','volume',evergreen.array_remove_item_by_value( evergreen.array_remove_item_by_value($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), 0, name, label_sortkey
10794                         FROM evergreen.located_uris($1, $2, $9) AS uris
10795                      )x)
10796                  ),
10797                  CASE WHEN ('ssub' = ANY ($5)) THEN 
10798                      XMLELEMENT(
10799                          name subscriptions,
10800                          (SELECT XMLAGG(ssub) FROM (
10801                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
10802                               FROM  serial.subscription
10803                               WHERE record_entry = $1
10804                         )x)
10805                      )
10806                  ELSE NULL END,
10807                  CASE WHEN ('acp' = ANY ($5)) THEN 
10808                      XMLELEMENT(
10809                          name foreign_copies,
10810                          (SELECT XMLAGG(acp) FROM (
10811                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
10812                               FROM  biblio.peer_bib_copy_map p
10813                                     JOIN asset.copy c ON (p.target_copy = c.id)
10814                               WHERE NOT c.deleted AND p.peer_record = $1
10815                             LIMIT ($6 -> 'acp')::INT
10816                             OFFSET ($7 -> 'acp')::INT
10817                         )x)
10818                      )
10819                  ELSE NULL END
10820              );
10821 $F$ LANGUAGE SQL STABLE;
10822
10823 CREATE OR REPLACE FUNCTION unapi.ssub ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10824         SELECT  XMLELEMENT(
10825                     name subscription,
10826                     XMLATTRIBUTES(
10827                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10828                         'tag:open-ils.org:U2@ssub/' || id AS id,
10829                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
10830                         start_date AS start, end_date AS end, expected_date_offset
10831                     ),
10832                     CASE 
10833                         WHEN ('sdist' = ANY ($4)) THEN
10834                             XMLELEMENT( name distributions,
10835                                 (SELECT XMLAGG(sdist) FROM (
10836                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
10837                                       FROM  serial.distribution
10838                                       WHERE subscription = ssub.id
10839                                 )x)
10840                             )
10841                         ELSE NULL
10842                     END
10843                 )
10844           FROM  serial.subscription ssub
10845           WHERE id = $1
10846           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
10847 $F$ LANGUAGE SQL STABLE;
10848
10849 CREATE OR REPLACE FUNCTION unapi.sdist ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10850         SELECT  XMLELEMENT(
10851                     name distribution,
10852                     XMLATTRIBUTES(
10853                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10854                         'tag:open-ils.org:U2@sdist/' || id AS id,
10855                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
10856                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
10857                         unit_label_prefix, label, unit_label_suffix, summary_method
10858                     ),
10859                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
10860                     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,
10861                     CASE 
10862                         WHEN ('sstr' = ANY ($4)) THEN
10863                             XMLELEMENT( name streams,
10864                                 (SELECT XMLAGG(sstr) FROM (
10865                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10866                                       FROM  serial.stream
10867                                       WHERE distribution = sdist.id
10868                                 )x)
10869                             )
10870                         ELSE NULL
10871                     END,
10872                     XMLELEMENT( name summaries,
10873                         CASE 
10874                             WHEN ('sbsum' = ANY ($4)) THEN
10875                                 (SELECT XMLAGG(sbsum) FROM (
10876                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10877                                       FROM  serial.basic_summary
10878                                       WHERE distribution = sdist.id
10879                                 )x)
10880                             ELSE NULL
10881                         END,
10882                         CASE 
10883                             WHEN ('sisum' = ANY ($4)) THEN
10884                                 (SELECT XMLAGG(sisum) FROM (
10885                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10886                                       FROM  serial.index_summary
10887                                       WHERE distribution = sdist.id
10888                                 )x)
10889                             ELSE NULL
10890                         END,
10891                         CASE 
10892                             WHEN ('sssum' = ANY ($4)) THEN
10893                                 (SELECT XMLAGG(sssum) FROM (
10894                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10895                                       FROM  serial.supplement_summary
10896                                       WHERE distribution = sdist.id
10897                                 )x)
10898                             ELSE NULL
10899                         END
10900                     )
10901                 )
10902           FROM  serial.distribution sdist
10903           WHERE id = $1
10904           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
10905 $F$ LANGUAGE SQL STABLE;
10906
10907 CREATE OR REPLACE FUNCTION unapi.sstr ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10908     SELECT  XMLELEMENT(
10909                 name stream,
10910                 XMLATTRIBUTES(
10911                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10912                     'tag:open-ils.org:U2@sstr/' || id AS id,
10913                     routing_label
10914                 ),
10915                 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,
10916                 CASE 
10917                     WHEN ('sitem' = ANY ($4)) THEN
10918                         XMLELEMENT( name items,
10919                             (SELECT XMLAGG(sitem) FROM (
10920                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
10921                                   FROM  serial.item
10922                                   WHERE stream = sstr.id
10923                             )x)
10924                         )
10925                     ELSE NULL
10926                 END
10927             )
10928       FROM  serial.stream sstr
10929       WHERE id = $1
10930       GROUP BY id, routing_label, distribution;
10931 $F$ LANGUAGE SQL STABLE;
10932
10933 CREATE OR REPLACE FUNCTION unapi.siss ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10934     SELECT  XMLELEMENT(
10935                 name issuance,
10936                 XMLATTRIBUTES(
10937                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10938                     'tag:open-ils.org:U2@siss/' || id AS id,
10939                     create_date, edit_date, label, date_published,
10940                     holding_code, holding_type, holding_link_id
10941                 ),
10942                 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,
10943                 CASE 
10944                     WHEN ('sitem' = ANY ($4)) THEN
10945                         XMLELEMENT( name items,
10946                             (SELECT XMLAGG(sitem) FROM (
10947                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
10948                                   FROM  serial.item
10949                                   WHERE issuance = sstr.id
10950                             )x)
10951                         )
10952                     ELSE NULL
10953                 END
10954             )
10955       FROM  serial.issuance sstr
10956       WHERE id = $1
10957       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
10958 $F$ LANGUAGE SQL STABLE;
10959
10960 CREATE OR REPLACE FUNCTION unapi.sitem ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10961         SELECT  XMLELEMENT(
10962                     name serial_item,
10963                     XMLATTRIBUTES(
10964                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10965                         'tag:open-ils.org:U2@sitem/' || id AS id,
10966                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
10967                         date_expected, date_received
10968                     ),
10969                     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,
10970                     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,
10971                     CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( unit, $2, 'serial_unit', evergreen.array_remove_item_by_value($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
10972                     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
10973 --                    XMLELEMENT( name notes,
10974 --                        CASE 
10975 --                            WHEN ('acpn' = ANY ($4)) THEN
10976 --                                (SELECT XMLAGG(acpn) FROM (
10977 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
10978 --                                      FROM  asset.copy_note
10979 --                                      WHERE owning_copy = cp.id AND pub
10980 --                                )x)
10981 --                            ELSE NULL
10982 --                        END
10983 --                    )
10984                 )
10985           FROM  serial.item sitem
10986           WHERE id = $1;
10987 $F$ LANGUAGE SQL STABLE;
10988
10989
10990 CREATE OR REPLACE FUNCTION unapi.sssum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
10991     SELECT  XMLELEMENT(
10992                 name serial_summary,
10993                 XMLATTRIBUTES(
10994                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10995                     'tag:open-ils.org:U2@sbsum/' || id AS id,
10996                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
10997                 ),
10998                 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
10999             )
11000       FROM  serial.supplement_summary ssum
11001       WHERE id = $1
11002       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11003 $F$ LANGUAGE SQL STABLE;
11004
11005 CREATE OR REPLACE FUNCTION unapi.sbsum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11006     SELECT  XMLELEMENT(
11007                 name serial_summary,
11008                 XMLATTRIBUTES(
11009                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11010                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11011                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
11012                 ),
11013                 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
11014             )
11015       FROM  serial.basic_summary ssum
11016       WHERE id = $1
11017       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11018 $F$ LANGUAGE SQL STABLE;
11019
11020 CREATE OR REPLACE FUNCTION unapi.sisum ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11021     SELECT  XMLELEMENT(
11022                 name serial_summary,
11023                 XMLATTRIBUTES(
11024                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11025                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11026                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
11027                 ),
11028                 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
11029             )
11030       FROM  serial.index_summary ssum
11031       WHERE id = $1
11032       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11033 $F$ LANGUAGE SQL STABLE;
11034
11035
11036 CREATE OR REPLACE FUNCTION unapi.aou ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11037 DECLARE
11038     output XML;
11039 BEGIN
11040     IF ename = 'circlib' THEN
11041         SELECT  XMLELEMENT(
11042                     name circlib,
11043                     XMLATTRIBUTES(
11044                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11045                         id AS ident
11046                     ),
11047                     name
11048                 ) INTO output
11049           FROM  actor.org_unit aou
11050           WHERE id = obj_id;
11051     ELSE
11052         EXECUTE $$SELECT  XMLELEMENT(
11053                     name $$ || ename || $$,
11054                     XMLATTRIBUTES(
11055                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11056                         'tag:open-ils.org:U2@aou/' || id AS id,
11057                         shortname, name, opac_visible
11058                     )
11059                 )
11060           FROM  actor.org_unit aou
11061          WHERE id = $1 $$ INTO output USING obj_id;
11062     END IF;
11063
11064     RETURN output;
11065
11066 END;
11067 $F$ LANGUAGE PLPGSQL STABLE;
11068
11069 CREATE OR REPLACE FUNCTION unapi.acl ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11070     SELECT  XMLELEMENT(
11071                 name location,
11072                 XMLATTRIBUTES(
11073                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11074                     id AS ident,
11075                     holdable,
11076                     opac_visible,
11077                     label_prefix AS prefix,
11078                     label_suffix AS suffix
11079                 ),
11080                 name
11081             )
11082       FROM  asset.copy_location
11083       WHERE id = $1;
11084 $F$ LANGUAGE SQL STABLE;
11085
11086 CREATE OR REPLACE FUNCTION unapi.ccs ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11087     SELECT  XMLELEMENT(
11088                 name status,
11089                 XMLATTRIBUTES(
11090                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11091                     id AS ident,
11092                     holdable,
11093                     opac_visible
11094                 ),
11095                 name
11096             )
11097       FROM  config.copy_status
11098       WHERE id = $1;
11099 $F$ LANGUAGE SQL STABLE;
11100
11101 CREATE OR REPLACE FUNCTION unapi.acpn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11102         SELECT  XMLELEMENT(
11103                     name copy_note,
11104                     XMLATTRIBUTES(
11105                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11106                         create_date AS date,
11107                         title
11108                     ),
11109                     value
11110                 )
11111           FROM  asset.copy_note
11112           WHERE id = $1;
11113 $F$ LANGUAGE SQL STABLE;
11114
11115 CREATE OR REPLACE FUNCTION unapi.ascecm ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11116         SELECT  XMLELEMENT(
11117                     name statcat,
11118                     XMLATTRIBUTES(
11119                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11120                         sc.name,
11121                         sc.opac_visible
11122                     ),
11123                     asce.value
11124                 )
11125           FROM  asset.stat_cat_entry asce
11126                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
11127           WHERE asce.id = $1;
11128 $F$ LANGUAGE SQL STABLE;
11129
11130 CREATE OR REPLACE FUNCTION unapi.bmp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11131         SELECT  XMLELEMENT(
11132                     name monograph_part,
11133                     XMLATTRIBUTES(
11134                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11135                         'tag:open-ils.org:U2@bmp/' || id AS id,
11136                         id AS ident,
11137                         label,
11138                         label_sortkey,
11139                         'tag:open-ils.org:U2@bre/' || record AS record
11140                     ),
11141                     CASE 
11142                         WHEN ('acp' = ANY ($4)) THEN
11143                             XMLELEMENT( name copies,
11144                                 (SELECT XMLAGG(acp) FROM (
11145                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
11146                                       FROM  asset.copy cp
11147                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
11148                                       WHERE cpm.part = $1
11149                                           AND cp.deleted IS FALSE
11150                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
11151                                       LIMIT ($7 -> 'acp')::INT
11152                                       OFFSET ($8 -> 'acp')::INT
11153
11154                                 )x)
11155                             )
11156                         ELSE NULL
11157                     END,
11158                     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
11159                 )
11160           FROM  biblio.monograph_part
11161           WHERE id = $1
11162           GROUP BY id, label, label_sortkey, record;
11163 $F$ LANGUAGE SQL STABLE;
11164
11165 CREATE OR REPLACE FUNCTION unapi.acp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11166         SELECT  XMLELEMENT(
11167                     name copy,
11168                     XMLATTRIBUTES(
11169                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11170                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11171                         create_date, edit_date, copy_number, circulate, deposit,
11172                         ref, holdable, deleted, deposit_amount, price, barcode,
11173                         circ_modifier, circ_as_type, opac_visible, age_protect
11174                     ),
11175                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11176                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11177                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11178                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11179                     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,
11180                     CASE 
11181                         WHEN ('acpn' = ANY ($4)) THEN
11182                             XMLELEMENT( name copy_notes,
11183                                 (SELECT XMLAGG(acpn) FROM (
11184                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11185                                       FROM  asset.copy_note
11186                                       WHERE owning_copy = cp.id AND pub
11187                                 )x)
11188                             )
11189                         ELSE NULL
11190                     END,
11191                     CASE 
11192                         WHEN ('ascecm' = ANY ($4)) THEN
11193                             XMLELEMENT( name statcats,
11194                                 (SELECT XMLAGG(ascecm) FROM (
11195                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11196                                       FROM  asset.stat_cat_entry_copy_map
11197                                       WHERE owning_copy = cp.id
11198                                 )x)
11199                             )
11200                         ELSE NULL
11201                     END,
11202                     CASE
11203                         WHEN ('bre' = ANY ($4)) THEN
11204                             XMLELEMENT( name foreign_records,
11205                                 (SELECT XMLAGG(bre) FROM (
11206                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11207                                       FROM  biblio.peer_bib_copy_map
11208                                       WHERE target_copy = cp.id
11209                                 )x)
11210
11211                             )
11212                         ELSE NULL
11213                     END,
11214                     CASE 
11215                         WHEN ('bmp' = ANY ($4)) THEN
11216                             XMLELEMENT( name monograph_parts,
11217                                 (SELECT XMLAGG(bmp) FROM (
11218                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11219                                       FROM  asset.copy_part_map
11220                                       WHERE target_copy = cp.id
11221                                 )x)
11222                             )
11223                         ELSE NULL
11224                     END,
11225                     CASE 
11226                         WHEN ('circ' = ANY ($4)) THEN
11227                             XMLELEMENT( name current_circulation,
11228                                 (SELECT XMLAGG(circ) FROM (
11229                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11230                                       FROM  action.circulation
11231                                       WHERE target_copy = cp.id
11232                                             AND checkin_time IS NULL
11233                                 )x)
11234                             )
11235                         ELSE NULL
11236                     END
11237                 )
11238           FROM  asset.copy cp
11239           WHERE id = $1
11240               AND cp.deleted IS FALSE
11241           GROUP BY id, status, location, circ_lib, call_number, create_date,
11242               edit_date, copy_number, circulate, deposit, ref, holdable,
11243               deleted, deposit_amount, price, barcode, circ_modifier,
11244               circ_as_type, opac_visible, age_protect;
11245 $F$ LANGUAGE SQL STABLE;
11246
11247 CREATE OR REPLACE FUNCTION unapi.sunit ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11248         SELECT  XMLELEMENT(
11249                     name serial_unit,
11250                     XMLATTRIBUTES(
11251                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11252                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11253                         create_date, edit_date, copy_number, circulate, deposit,
11254                         ref, holdable, deleted, deposit_amount, price, barcode,
11255                         circ_modifier, circ_as_type, opac_visible, age_protect,
11256                         status_changed_time, floating, mint_condition,
11257                         detailed_contents, sort_key, summary_contents, cost 
11258                     ),
11259                     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),
11260                     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),
11261                     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),
11262                     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),
11263                     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,
11264                     XMLELEMENT( name copy_notes,
11265                         CASE 
11266                             WHEN ('acpn' = ANY ($4)) THEN
11267                                 (SELECT XMLAGG(acpn) FROM (
11268                                     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)
11269                                       FROM  asset.copy_note
11270                                       WHERE owning_copy = cp.id AND pub
11271                                 )x)
11272                             ELSE NULL
11273                         END
11274                     ),
11275                     XMLELEMENT( name statcats,
11276                         CASE 
11277                             WHEN ('ascecm' = ANY ($4)) THEN
11278                                 (SELECT XMLAGG(ascecm) FROM (
11279                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11280                                       FROM  asset.stat_cat_entry_copy_map
11281                                       WHERE owning_copy = cp.id
11282                                 )x)
11283                             ELSE NULL
11284                         END
11285                     ),
11286                     XMLELEMENT( name foreign_records,
11287                         CASE
11288                             WHEN ('bre' = ANY ($4)) THEN
11289                                 (SELECT XMLAGG(bre) FROM (
11290                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11291                                       FROM  biblio.peer_bib_copy_map
11292                                       WHERE target_copy = cp.id
11293                                 )x)
11294                             ELSE NULL
11295                         END
11296                     ),
11297                     CASE 
11298                         WHEN ('bmp' = ANY ($4)) THEN
11299                             XMLELEMENT( name monograph_parts,
11300                                 (SELECT XMLAGG(bmp) FROM (
11301                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11302                                       FROM  asset.copy_part_map
11303                                       WHERE target_copy = cp.id
11304                                 )x)
11305                             )
11306                         ELSE NULL
11307                     END,
11308                     CASE 
11309                         WHEN ('circ' = ANY ($4)) THEN
11310                             XMLELEMENT( name current_circulation,
11311                                 (SELECT XMLAGG(circ) FROM (
11312                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11313                                       FROM  action.circulation
11314                                       WHERE target_copy = cp.id
11315                                             AND checkin_time IS NULL
11316                                 )x)
11317                             )
11318                         ELSE NULL
11319                     END
11320                 )
11321           FROM  serial.unit cp
11322           WHERE id = $1
11323               AND cp.deleted IS FALSE
11324           GROUP BY id, status, location, circ_lib, call_number, create_date,
11325               edit_date, copy_number, circulate, floating, mint_condition,
11326               deposit, ref, holdable, deleted, deposit_amount, price,
11327               barcode, circ_modifier, circ_as_type, opac_visible,
11328               status_changed_time, detailed_contents, sort_key,
11329               summary_contents, cost, age_protect;
11330 $F$ LANGUAGE SQL STABLE;
11331
11332 CREATE OR REPLACE FUNCTION unapi.acn ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11333         SELECT  XMLELEMENT(
11334                     name volume,
11335                     XMLATTRIBUTES(
11336                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11337                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
11338                         acn.id AS vol_id, o.shortname AS lib,
11339                         o.opac_visible AS opac_visible,
11340                         deleted, label, label_sortkey, label_class, record
11341                     ),
11342                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
11343                     CASE 
11344                         WHEN ('acp' = ANY ($4)) THEN
11345                             CASE WHEN $6 IS NOT NULL THEN
11346                                 XMLELEMENT( name copies,
11347                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11348                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11349                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11350                                           FROM  asset.copy cp
11351                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
11352                                           WHERE cp.call_number = acn.id
11353                                               AND cp.deleted IS FALSE
11354                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11355                                           LIMIT ($7 -> 'acp')::INT
11356                                           OFFSET ($8 -> 'acp')::INT
11357                                     )x)
11358                                 )
11359                             ELSE
11360                                 XMLELEMENT( name copies,
11361                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11362                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11363                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11364                                           FROM  asset.copy cp
11365                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
11366                                           WHERE cp.call_number = acn.id
11367                                               AND cp.deleted IS FALSE
11368                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11369                                           LIMIT ($7 -> 'acp')::INT
11370                                           OFFSET ($8 -> 'acp')::INT
11371                                     )x)
11372                                 )
11373                             END
11374                         ELSE NULL
11375                     END,
11376                     XMLELEMENT(
11377                         name uris,
11378                         (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)
11379                     ),
11380                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11381                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11382                     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
11383                 ) AS x
11384           FROM  asset.call_number acn
11385                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
11386           WHERE acn.id = $1
11387               AND acn.deleted IS FALSE
11388           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
11389 $F$ LANGUAGE SQL STABLE;
11390
11391 CREATE OR REPLACE FUNCTION unapi.acnp ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11392         SELECT  XMLELEMENT(
11393                     name call_number_prefix,
11394                     XMLATTRIBUTES(
11395                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11396                         id AS ident,
11397                         label,
11398                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11399                         label_sortkey
11400                     )
11401                 )
11402           FROM  asset.call_number_prefix
11403           WHERE id = $1;
11404 $F$ LANGUAGE SQL STABLE;
11405
11406 CREATE OR REPLACE FUNCTION unapi.acns ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11407         SELECT  XMLELEMENT(
11408                     name call_number_suffix,
11409                     XMLATTRIBUTES(
11410                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11411                         id AS ident,
11412                         label,
11413                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11414                         label_sortkey
11415                     )
11416                 )
11417           FROM  asset.call_number_suffix
11418           WHERE id = $1;
11419 $F$ LANGUAGE SQL STABLE;
11420
11421 CREATE OR REPLACE FUNCTION unapi.auri ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11422         SELECT  XMLELEMENT(
11423                     name uri,
11424                     XMLATTRIBUTES(
11425                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11426                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
11427                         use_restriction,
11428                         href,
11429                         label
11430                     ),
11431                     CASE 
11432                         WHEN ('acn' = ANY ($4)) THEN
11433                             XMLELEMENT( name copies,
11434                                 (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)
11435                             )
11436                         ELSE NULL
11437                     END
11438                 ) AS x
11439           FROM  asset.uri uri
11440           WHERE uri.id = $1
11441           GROUP BY uri.id, use_restriction, href, label;
11442 $F$ LANGUAGE SQL STABLE;
11443
11444 CREATE OR REPLACE FUNCTION unapi.mra ( obj_id BIGINT, format TEXT,  ename TEXT, includes TEXT[], org TEXT, depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11445         SELECT  XMLELEMENT(
11446                     name attributes,
11447                     XMLATTRIBUTES(
11448                         CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
11449                         'tag:open-ils.org:U2@mra/' || mra.id AS id,
11450                         'tag:open-ils.org:U2@bre/' || mra.id AS record
11451                     ),
11452                     (SELECT XMLAGG(foo.y)
11453                       FROM (SELECT XMLELEMENT(
11454                                 name field,
11455                                 XMLATTRIBUTES(
11456                                     key AS name,
11457                                     cvm.value AS "coded-value",
11458                                     rad.filter,
11459                                     rad.sorter
11460                                 ),
11461                                 x.value
11462                             )
11463                            FROM EACH(mra.attrs) AS x
11464                                 JOIN config.record_attr_definition rad ON (x.key = rad.name)
11465                                 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
11466                         )foo(y)
11467                     )
11468                 )
11469           FROM  metabib.record_attr mra
11470           WHERE mra.id = $1;
11471 $F$ LANGUAGE SQL STABLE;
11472
11473 CREATE OR REPLACE FUNCTION unapi.circ (obj_id BIGINT, format TEXT, ename TEXT, includes TEXT[], org TEXT DEFAULT '-', depth INT DEFAULT NULL, slimit HSTORE DEFAULT NULL, soffset HSTORE DEFAULT NULL, include_xmlns BOOL DEFAULT TRUE ) RETURNS XML AS $F$
11474     SELECT XMLELEMENT(
11475         name circ,
11476         XMLATTRIBUTES(
11477             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11478             'tag:open-ils.org:U2@circ/' || id AS id,
11479             xact_start,
11480             due_date
11481         ),
11482         CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
11483         CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
11484     )
11485     FROM action.circulation
11486     WHERE id = $1;
11487 $F$ LANGUAGE SQL STABLE;
11488
11489 /*
11490
11491  -- Some test queries
11492
11493 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11494 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11495 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11496
11497 SELECT unapi.biblio_record_entry_feed('{1}'::BIGINT[],'mods32','{holdings_xml,acp}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://c64/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
11498
11499 SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
11500 EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
11501 EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'marcxml','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
11502 EXPLAIN ANALYZE SELECT unapi.biblio_record_entry_feed('{7209,7394}'::BIGINT[],'mods32','{holdings_xml}'::TEXT[],'SYS1',NULL,'acn=>1',NULL, NULL,NULL,NULL,NULL,'http://fulfillment2.esilibrary.com/opac/extras/unapi', '<totalResults xmlns="http://a9.com/-/spec/opensearch/1.1/">2</totalResults><startIndex xmlns="http://a9.com/-/spec/opensearch/1.1/">1</startIndex><itemsPerPage xmlns="http://a9.com/-/spec/opensearch/1.1/">10</itemsPerPage>');
11503
11504 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
11505 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
11506 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
11507 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
11508 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
11509
11510 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
11511 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
11512
11513 */
11514
11515
11516 SELECT evergreen.upgrade_deps_block_check('0692', :eg_version);
11517
11518 INSERT INTO config.org_unit_setting_type
11519     (name, label, description, grp, datatype)
11520     VALUES (
11521         'circ.fines.charge_when_closed',
11522          oils_i18n_gettext(
11523             'circ.fines.charge_when_closed',
11524             'Charge fines on overdue circulations when closed',
11525             'coust',
11526             'label'
11527         ),
11528         oils_i18n_gettext(
11529             'circ.fines.charge_when_closed',
11530             'Normally, fines are not charged when a library is closed.  When set to True, fines will be charged during scheduled closings and normal weekly closed days.',
11531             'coust',
11532             'description'
11533         ),
11534         'circ',
11535         'bool'
11536     );
11537
11538 SELECT evergreen.upgrade_deps_block_check('0694', :eg_version);
11539
11540 INSERT into config.org_unit_setting_type
11541 ( name, grp, label, description, datatype, fm_class ) VALUES
11542
11543 ( 'ui.patron.edit.au.prefix.require', 'gui',
11544     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11545         'Require prefix field on patron registration',
11546         'coust', 'label'),
11547     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11548         'The prefix field will be required on the patron registration screen.',
11549         'coust', 'description'),
11550     'bool', null)
11551         
11552 ,( 'ui.patron.edit.au.prefix.show', 'gui',
11553     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11554         'Show prefix field on patron registration',
11555         'coust', 'label'),
11556     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11557         'The prefix 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.',
11558         'coust', 'description'),
11559     'bool', null)
11560
11561 ,( 'ui.patron.edit.au.prefix.suggest', 'gui',
11562     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11563         'Suggest prefix field on patron registration',
11564         'coust', 'label'),
11565     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11566         'The prefix 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.',
11567         'coust', 'description'),
11568     'bool', null)
11569 ;               
11570
11571
11572 -- Evergreen DB patch 0695.schema.custom_toolbars.sql
11573 --
11574 -- FIXME: insert description of change, if needed
11575 --
11576
11577 -- check whether patch can be applied
11578 SELECT evergreen.upgrade_deps_block_check('0695', :eg_version);
11579
11580 CREATE TABLE actor.toolbar (
11581     id          BIGSERIAL   PRIMARY KEY,
11582     ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
11583     org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
11584     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
11585     label       TEXT        NOT NULL,
11586     layout      TEXT        NOT NULL,
11587     CONSTRAINT only_one_type CHECK (
11588         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
11589         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
11590         (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
11591     ),
11592     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
11593 );
11594 CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
11595 CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
11596 CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
11597
11598 -- this one unrelated to toolbars but is a gap in the upgrade scripts
11599 INSERT INTO permission.perm_list ( id, code, description )
11600     SELECT
11601         522,
11602         'IMPORT_AUTHORITY_MARC',
11603         oils_i18n_gettext(
11604             522,
11605             'Allows a user to create new authority records',
11606             'ppl',
11607             'description'
11608         )
11609     WHERE NOT EXISTS (
11610         SELECT 1
11611         FROM permission.perm_list
11612         WHERE
11613             id = 522
11614     );
11615
11616 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
11617     523,
11618     'ADMIN_TOOLBAR',
11619     oils_i18n_gettext(
11620         523,
11621         'Allows a user to create, edit, and delete custom toolbars',
11622         'ppl',
11623         'description'
11624     )
11625 );
11626
11627 -- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
11628 -- INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) SELECT pgt.id, perm.id, aout.depth, FALSE FROM permission.grp_tree pgt, permission.perm_list perm, actor.org_unit_type aout WHERE pgt.name = 'Staff' AND aout.name = 'Branch' AND perm.code = 'ADMIN_TOOLBAR';
11629
11630 INSERT INTO actor.toolbar(org,label,layout) VALUES
11631     ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
11632     ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
11633
11634 -- delete from permission.grp_perm_map where perm in (select id from permission.perm_list where code ~ 'TOOLBAR'); delete from permission.perm_list where code ~ 'TOOLBAR'; drop table actor.toolbar ;
11635
11636 -- Evergreen DB patch 0696.no_plperl.sql
11637 --
11638 -- FIXME: insert description of change, if needed
11639 --
11640
11641 -- check whether patch can be applied
11642 SELECT evergreen.upgrade_deps_block_check('0696', :eg_version);
11643
11644 -- Re-create these as plperlu instead of plperl
11645 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
11646     $_SHARED{"eg_audit_user"} = $_[0];
11647     $_SHARED{"eg_audit_ws"} = $_[1];
11648 $$ LANGUAGE plperlu;
11649
11650 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
11651     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
11652 $$ LANGUAGE plperlu;
11653
11654 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
11655     delete($_SHARED{"eg_audit_user"});
11656     delete($_SHARED{"eg_audit_ws"});
11657 $$ LANGUAGE plperlu;
11658
11659 -- Evergreen DB patch 0697.data.place_currently_unfillable_hold.sql
11660 --
11661 -- FIXME: insert description of change, if needed
11662 --
11663
11664 -- check whether patch can be applied
11665 SELECT evergreen.upgrade_deps_block_check('0697', :eg_version);
11666
11667 -- FIXME: add/check SQL statements to perform the upgrade
11668 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11669  ( 524, 'PLACE_UNFILLABLE_HOLD', oils_i18n_gettext( 524,
11670     'Allows a user to place a hold that cannot currently be filled.', 'ppl', 'description' ));
11671
11672 -- Evergreen DB patch 0698.hold_default_pickup.sql
11673 --
11674 -- FIXME: insert description of change, if needed
11675 --
11676
11677 -- check whether patch can be applied
11678 SELECT evergreen.upgrade_deps_block_check('0698', :eg_version);
11679
11680 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
11681     VALUES ('opac.default_pickup_location', TRUE, 'Default Hold Pickup Location', 'Default location for holds pickup', 'integer');
11682
11683 SELECT evergreen.upgrade_deps_block_check('0699', :eg_version);
11684
11685 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, grp )
11686     VALUES (
11687         'ui.hide_copy_editor_fields',
11688         oils_i18n_gettext(
11689             'ui.hide_copy_editor_fields',
11690             'GUI: Hide these fields within the Item Attribute Editor',
11691             'coust',
11692             'label'
11693         ),
11694         oils_i18n_gettext(
11695             'ui.hide_copy_editor_fields',
11696             'This setting may be best maintained with the dedicated configuration'
11697             || ' interface within the Item Attribute Editor.  However, here it'
11698             || ' shows up as comma separated list of field identifiers to hide.',
11699             'coust',
11700             'description'
11701         ),
11702         'array',
11703         'gui'
11704     );
11705
11706
11707 SELECT evergreen.upgrade_deps_block_check('0700', :eg_version);
11708 SELECT evergreen.upgrade_deps_block_check('0706', :eg_version);
11709 SELECT evergreen.upgrade_deps_block_check('0710', :eg_version);
11710
11711
11712 CREATE OR REPLACE FUNCTION evergreen.could_be_serial_holding_code(TEXT) RETURNS BOOL AS $$
11713     use JSON::XS;
11714     use MARC::Field;
11715
11716     eval {
11717         my $holding_code = (new JSON::XS)->decode(shift);
11718         new MARC::Field('999', @$holding_code);
11719     };  
11720     return $@ ? 0 : 1;
11721 $$ LANGUAGE PLPERLU;
11722
11723 -- This throws away data, but only data that causes breakage anyway.
11724 UPDATE serial.issuance SET holding_code = NULL WHERE NOT could_be_serial_holding_code(holding_code);
11725
11726 -- If we don't do this, we have unprocessed triggers and we can't alter the table
11727 SET CONSTRAINTS serial.issuance_caption_and_pattern_fkey IMMEDIATE;
11728
11729 ALTER TABLE serial.issuance
11730     DROP CONSTRAINT IF EXISTS issuance_holding_code_check;
11731
11732 ALTER TABLE serial.issuance ADD CHECK (holding_code IS NULL OR could_be_serial_holding_code(holding_code));
11733
11734 INSERT INTO config.internal_flag (name, value, enabled) VALUES (
11735     'serial.rematerialize_on_same_holding_code', NULL, FALSE
11736 );
11737
11738 INSERT INTO config.org_unit_setting_type (
11739     name, label, grp, description, datatype
11740 ) VALUES (
11741     'serial.default_display_grouping',
11742     'Default display grouping for serials distributions presented in the OPAC.',
11743     'serial',
11744     'Default display grouping for serials distributions presented in the OPAC. This can be "enum" or "chron".',
11745     'string'
11746 );
11747
11748 ALTER TABLE serial.distribution
11749     ADD COLUMN display_grouping TEXT NOT NULL DEFAULT 'chron'
11750         CHECK (display_grouping IN ('enum', 'chron'));
11751
11752 -- why didn't we just make one summary table in the first place?
11753 CREATE VIEW serial.any_summary AS
11754     SELECT
11755         'basic' AS summary_type, id, distribution,
11756         generated_coverage, textual_holdings, show_generated
11757     FROM serial.basic_summary
11758     UNION
11759     SELECT
11760         'index' AS summary_type, id, distribution,
11761         generated_coverage, textual_holdings, show_generated
11762     FROM serial.index_summary
11763     UNION
11764     SELECT
11765         'supplement' AS summary_type, id, distribution,
11766         generated_coverage, textual_holdings, show_generated
11767     FROM serial.supplement_summary ;
11768
11769
11770 -- Given the IDs of two rows in actor.org_unit, *the second being an ancestor
11771 -- of the first*, return in array form the path from the ancestor to the
11772 -- descendant, with each point in the path being an org_unit ID.  This is
11773 -- useful for sorting org_units by their position in a depth-first (display
11774 -- order) representation of the tree.
11775 --
11776 -- This breaks with the precedent set by actor.org_unit_full_path() and others,
11777 -- and gets the parameters "backwards," but otherwise this function would
11778 -- not be very usable within json_query.
11779 CREATE OR REPLACE FUNCTION actor.org_unit_simple_path(INT, INT)
11780 RETURNS INT[] AS $$
11781     WITH RECURSIVE descendant_depth(id, path) AS (
11782         SELECT  aou.id,
11783                 ARRAY[aou.id]
11784           FROM  actor.org_unit aou
11785                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11786           WHERE aou.id = $2
11787             UNION ALL
11788         SELECT  aou.id,
11789                 dd.path || ARRAY[aou.id]
11790           FROM  actor.org_unit aou
11791                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11792                 JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
11793     ) SELECT dd.path
11794         FROM actor.org_unit aou
11795         JOIN descendant_depth dd USING (id)
11796         WHERE aou.id = $1 ORDER BY dd.path;
11797 $$ LANGUAGE SQL STABLE;
11798
11799 CREATE TABLE serial.materialized_holding_code (
11800     id BIGSERIAL PRIMARY KEY,
11801     issuance INTEGER NOT NULL REFERENCES serial.issuance (id) ON DELETE CASCADE,
11802     subfield CHAR,
11803     value TEXT
11804 );
11805
11806 CREATE OR REPLACE FUNCTION serial.materialize_holding_code() RETURNS TRIGGER
11807 AS $func$ 
11808 use strict;
11809
11810 use MARC::Field;
11811 use JSON::XS;
11812
11813 if (not defined $_TD->{new}{holding_code}) {
11814     elog(WARNING, 'NULL in "holding_code" column of serial.issuance allowed for now, but may not be useful');
11815     return;
11816 }
11817
11818 # Do nothing if holding_code has not changed...
11819
11820 if ($_TD->{new}{holding_code} eq $_TD->{old}{holding_code}) {
11821     # ... unless the following internal flag is set.
11822
11823     my $flag_rv = spi_exec_query(q{
11824         SELECT * FROM config.internal_flag
11825         WHERE name = 'serial.rematerialize_on_same_holding_code' AND enabled
11826     }, 1);
11827     return unless $flag_rv->{processed};
11828 }
11829
11830
11831 my $holding_code = (new JSON::XS)->decode($_TD->{new}{holding_code});
11832
11833 my $field = new MARC::Field('999', @$holding_code); # tag doesnt matter
11834
11835 my $dstmt = spi_prepare(
11836     'DELETE FROM serial.materialized_holding_code WHERE issuance = $1',
11837     'INT'
11838 );
11839 spi_exec_prepared($dstmt, $_TD->{new}{id});
11840
11841 my $istmt = spi_prepare(
11842     q{
11843         INSERT INTO serial.materialized_holding_code (
11844             issuance, subfield, value
11845         ) VALUES ($1, $2, $3)
11846     }, qw{INT CHAR TEXT}
11847 );
11848
11849 foreach ($field->subfields) {
11850     spi_exec_prepared(
11851         $istmt,
11852         $_TD->{new}{id},
11853         $_->[0],
11854         $_->[1]
11855     );
11856 }
11857
11858 return;
11859
11860 $func$ LANGUAGE 'plperlu';
11861
11862 CREATE INDEX assist_holdings_display
11863     ON serial.materialized_holding_code (issuance, subfield);
11864
11865 CREATE TRIGGER materialize_holding_code
11866     AFTER INSERT OR UPDATE ON serial.issuance
11867     FOR EACH ROW EXECUTE PROCEDURE serial.materialize_holding_code() ;
11868
11869 -- starting here, we materialize all existing holding codes.
11870
11871 UPDATE config.internal_flag
11872     SET enabled = TRUE
11873     WHERE name = 'serial.rematerialize_on_same_holding_code';
11874
11875 UPDATE serial.issuance SET holding_code = holding_code;
11876
11877 UPDATE config.internal_flag
11878     SET enabled = FALSE
11879     WHERE name = 'serial.rematerialize_on_same_holding_code';
11880
11881 -- finish holding code materialization process
11882
11883 -- fix up missing holding_code fields from serial.issuance
11884 UPDATE serial.issuance siss
11885     SET holding_type = scap.type
11886     FROM serial.caption_and_pattern scap
11887     WHERE scap.id = siss.caption_and_pattern AND siss.holding_type IS NULL;
11888
11889
11890 -- Evergreen DB patch 0701.schema.patron_stat_category_enhancements.sql
11891 --
11892 -- Enables users to set patron statistical categories as required,
11893 -- whether or not users can input free text for the category value.
11894 -- Enables administrators to set an entry as the default for any
11895 -- given patron statistical category and org unit.
11896 --
11897
11898 -- check whether patch can be applied
11899 SELECT evergreen.upgrade_deps_block_check('0701', :eg_version);
11900
11901 -- New table
11902
11903 CREATE TABLE actor.stat_cat_entry_default (
11904     id              SERIAL  PRIMARY KEY,
11905     stat_cat_entry  INT     NOT NULL REFERENCES actor.stat_cat_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11906     stat_cat        INT     NOT NULL REFERENCES actor.stat_cat (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11907     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11908     CONSTRAINT sced_once_per_owner UNIQUE (stat_cat,owner)
11909 );
11910
11911 COMMENT ON TABLE actor.stat_cat_entry_default IS $$
11912 User Statistical Category Default Entry
11913
11914 A library may choose one of the stat_cat entries to be the
11915 default entry.
11916 $$;
11917
11918 -- Add columns to existing tables
11919
11920 -- Patron stat cat required column
11921 ALTER TABLE actor.stat_cat
11922     ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
11923
11924 -- Patron stat cat allow_freetext column
11925 ALTER TABLE actor.stat_cat
11926     ADD COLUMN allow_freetext BOOL NOT NULL DEFAULT TRUE;
11927
11928 -- Add permissions
11929
11930 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11931     ( 525, 'CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 525, 
11932         'User may set a default entry in a patron statistical category', 'ppl', 'description' )),
11933     ( 526, 'UPDATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 526, 
11934         'User may reset a default entry in a patron statistical category', 'ppl', 'description' )),
11935     ( 527, 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 527, 
11936         'User may unset a default entry in a patron statistical category', 'ppl', 'description' ));
11937
11938 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
11939     SELECT
11940         pgt.id, perm.id, aout.depth, TRUE
11941     FROM
11942         permission.grp_tree pgt,
11943         permission.perm_list perm,
11944         actor.org_unit_type aout
11945     WHERE
11946         pgt.name = 'Circulation Administrator' AND
11947         aout.name = 'System' AND
11948         perm.code IN ('CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT');
11949
11950
11951 SELECT evergreen.upgrade_deps_block_check('0702', :eg_version);
11952
11953 INSERT INTO config.global_flag (name, enabled, label) 
11954     VALUES (
11955         'opac.org_unit.non_inherited_visibility',
11956         FALSE,
11957         oils_i18n_gettext(
11958             'opac.org_unit.non_inherited_visibility',
11959             'Org Units Do Not Inherit Visibility',
11960             'cgf',
11961             'label'
11962         )
11963     );
11964
11965 CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
11966
11967 CREATE TABLE actor.org_unit_custom_tree (
11968     id              SERIAL  PRIMARY KEY,
11969     active          BOOLEAN DEFAULT FALSE,
11970     purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
11971 );
11972
11973 CREATE TABLE actor.org_unit_custom_tree_node (
11974     id              SERIAL  PRIMARY KEY,
11975     tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
11976         org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
11977         parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
11978     sibling_order   INTEGER NOT NULL DEFAULT 0,
11979     CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
11980 );
11981     
11982
11983 /* UNDO
11984 BEGIN;
11985 DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
11986 DROP TABLE actor.org_unit_custom_tree_node;
11987 DROP TABLE actor.org_unit_custom_tree;
11988 DROP TYPE actor.org_unit_custom_tree_purpose;
11989 COMMIT;
11990 */
11991
11992 -- Evergreen DB patch 0704.schema.query_parser_fts.sql
11993 --
11994 -- Add pref_ou query filter for preferred library searching
11995 --
11996
11997 -- check whether patch can be applied
11998 SELECT evergreen.upgrade_deps_block_check('0704', :eg_version);
11999
12000 -- Create the new 11-parameter function, featuring param_pref_ou
12001 CREATE OR REPLACE FUNCTION search.query_parser_fts (
12002
12003     param_search_ou INT,
12004     param_depth     INT,
12005     param_query     TEXT,
12006     param_statuses  INT[],
12007     param_locations INT[],
12008     param_offset    INT,
12009     param_check     INT,
12010     param_limit     INT,
12011     metarecord      BOOL,
12012     staff           BOOL,
12013     param_pref_ou   INT DEFAULT NULL
12014 ) RETURNS SETOF search.search_result AS $func$
12015 DECLARE
12016
12017     current_res         search.search_result%ROWTYPE;
12018     search_org_list     INT[];
12019     luri_org_list       INT[];
12020     tmp_int_list        INT[];
12021
12022     check_limit         INT;
12023     core_limit          INT;
12024     core_offset         INT;
12025     tmp_int             INT;
12026
12027     core_result         RECORD;
12028     core_cursor         REFCURSOR;
12029     core_rel_query      TEXT;
12030
12031     total_count         INT := 0;
12032     check_count         INT := 0;
12033     deleted_count       INT := 0;
12034     visible_count       INT := 0;
12035     excluded_count      INT := 0;
12036
12037 BEGIN
12038
12039     check_limit := COALESCE( param_check, 1000 );
12040     core_limit  := COALESCE( param_limit, 25000 );
12041     core_offset := COALESCE( param_offset, 0 );
12042
12043     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
12044
12045     IF param_search_ou > 0 THEN
12046         IF param_depth IS NOT NULL THEN
12047             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
12048         ELSE
12049             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
12050         END IF;
12051
12052         SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
12053
12054     ELSIF param_search_ou < 0 THEN
12055         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
12056
12057         FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
12058             SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
12059             luri_org_list := luri_org_list || tmp_int_list;
12060         END LOOP;
12061
12062         SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
12063
12064     ELSIF param_search_ou = 0 THEN
12065         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
12066     END IF;
12067
12068     IF param_pref_ou IS NOT NULL THEN
12069         SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors(param_pref_ou);
12070         luri_org_list := luri_org_list || tmp_int_list;
12071     END IF;
12072
12073     OPEN core_cursor FOR EXECUTE param_query;
12074
12075     LOOP
12076
12077         FETCH core_cursor INTO core_result;
12078         EXIT WHEN NOT FOUND;
12079         EXIT WHEN total_count >= core_limit;
12080
12081         total_count := total_count + 1;
12082
12083         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
12084
12085         check_count := check_count + 1;
12086
12087         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12088         IF NOT FOUND THEN
12089             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
12090             deleted_count := deleted_count + 1;
12091             CONTINUE;
12092         END IF;
12093
12094         PERFORM 1
12095           FROM  biblio.record_entry b
12096                 JOIN config.bib_source s ON (b.source = s.id)
12097           WHERE s.transcendant
12098                 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12099
12100         IF FOUND THEN
12101             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
12102             visible_count := visible_count + 1;
12103
12104             current_res.id = core_result.id;
12105             current_res.rel = core_result.rel;
12106
12107             tmp_int := 1;
12108             IF metarecord THEN
12109                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12110             END IF;
12111
12112             IF tmp_int = 1 THEN
12113                 current_res.record = core_result.records[1];
12114             ELSE
12115                 current_res.record = NULL;
12116             END IF;
12117
12118             RETURN NEXT current_res;
12119
12120             CONTINUE;
12121         END IF;
12122
12123         PERFORM 1
12124           FROM  asset.call_number cn
12125                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
12126                 JOIN asset.uri uri ON (map.uri = uri.id)
12127           WHERE NOT cn.deleted
12128                 AND cn.label = '##URI##'
12129                 AND uri.active
12130                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
12131                 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12132                 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
12133           LIMIT 1;
12134
12135         IF FOUND THEN
12136             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
12137             visible_count := visible_count + 1;
12138
12139             current_res.id = core_result.id;
12140             current_res.rel = core_result.rel;
12141
12142             tmp_int := 1;
12143             IF metarecord THEN
12144                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12145             END IF;
12146
12147             IF tmp_int = 1 THEN
12148                 current_res.record = core_result.records[1];
12149             ELSE
12150                 current_res.record = NULL;
12151             END IF;
12152
12153             RETURN NEXT current_res;
12154
12155             CONTINUE;
12156         END IF;
12157
12158         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
12159
12160             PERFORM 1
12161               FROM  asset.call_number cn
12162                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12163               WHERE NOT cn.deleted
12164                     AND NOT cp.deleted
12165                     AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12166                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12167                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12168               LIMIT 1;
12169
12170             IF NOT FOUND THEN
12171                 PERFORM 1
12172                   FROM  biblio.peer_bib_copy_map pr
12173                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12174                   WHERE NOT cp.deleted
12175                         AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12176                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12177                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12178                   LIMIT 1;
12179
12180                 IF NOT FOUND THEN
12181                 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
12182                     excluded_count := excluded_count + 1;
12183                     CONTINUE;
12184                 END IF;
12185             END IF;
12186
12187         END IF;
12188
12189         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
12190
12191             PERFORM 1
12192               FROM  asset.call_number cn
12193                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12194               WHERE NOT cn.deleted
12195                     AND NOT cp.deleted
12196                     AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12197                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12198                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12199               LIMIT 1;
12200
12201             IF NOT FOUND THEN
12202                 PERFORM 1
12203                   FROM  biblio.peer_bib_copy_map pr
12204                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12205                   WHERE NOT cp.deleted
12206                         AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12207                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12208                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12209                   LIMIT 1;
12210
12211                 IF NOT FOUND THEN
12212                     -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
12213                     excluded_count := excluded_count + 1;
12214                     CONTINUE;
12215                 END IF;
12216             END IF;
12217
12218         END IF;
12219
12220         IF staff IS NULL OR NOT staff THEN
12221
12222             PERFORM 1
12223               FROM  asset.opac_visible_copies
12224               WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12225                     AND record IN ( SELECT * FROM unnest( core_result.records ) )
12226               LIMIT 1;
12227
12228             IF NOT FOUND THEN
12229                 PERFORM 1
12230                   FROM  biblio.peer_bib_copy_map pr
12231                         JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
12232                   WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12233                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12234                   LIMIT 1;
12235
12236                 IF NOT FOUND THEN
12237
12238                     -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12239                     excluded_count := excluded_count + 1;
12240                     CONTINUE;
12241                 END IF;
12242             END IF;
12243
12244         ELSE
12245
12246             PERFORM 1
12247               FROM  asset.call_number cn
12248                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12249               WHERE NOT cn.deleted
12250                     AND NOT cp.deleted
12251                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12252                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12253               LIMIT 1;
12254
12255             IF NOT FOUND THEN
12256
12257                 PERFORM 1
12258                   FROM  biblio.peer_bib_copy_map pr
12259                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12260                   WHERE NOT cp.deleted
12261                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12262                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12263                   LIMIT 1;
12264
12265                 IF NOT FOUND THEN
12266
12267                     PERFORM 1
12268                       FROM  asset.call_number cn
12269                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12270                       WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12271                             AND NOT cp.deleted
12272                       LIMIT 1;
12273
12274                     IF FOUND THEN
12275                         -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12276                         excluded_count := excluded_count + 1;
12277                         CONTINUE;
12278                     END IF;
12279                 END IF;
12280
12281             END IF;
12282
12283         END IF;
12284
12285         visible_count := visible_count + 1;
12286
12287         current_res.id = core_result.id;
12288         current_res.rel = core_result.rel;
12289
12290         tmp_int := 1;
12291         IF metarecord THEN
12292             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12293         END IF;
12294
12295         IF tmp_int = 1 THEN
12296             current_res.record = core_result.records[1];
12297         ELSE
12298             current_res.record = NULL;
12299         END IF;
12300
12301         RETURN NEXT current_res;
12302
12303         IF visible_count % 1000 = 0 THEN
12304             -- RAISE NOTICE ' % visible so far ... ', visible_count;
12305         END IF;
12306
12307     END LOOP;
12308
12309     current_res.id = NULL;
12310     current_res.rel = NULL;
12311     current_res.record = NULL;
12312     current_res.total = total_count;
12313     current_res.checked = check_count;
12314     current_res.deleted = deleted_count;
12315     current_res.visible = visible_count;
12316     current_res.excluded = excluded_count;
12317
12318     CLOSE core_cursor;
12319
12320     RETURN NEXT current_res;
12321
12322 END;
12323 $func$ LANGUAGE PLPGSQL;
12324
12325 -- Drop the old 10-parameter function
12326 DROP FUNCTION IF EXISTS search.query_parser_fts (
12327     INT, INT, TEXT, INT[], INT[], INT, INT, INT, BOOL, BOOL
12328 );
12329
12330 -- Evergreen DB patch 0705.data.custom-org-tree-perms.sql
12331 --
12332 -- check whether patch can be applied
12333 SELECT evergreen.upgrade_deps_block_check('0705', :eg_version);
12334
12335 INSERT INTO permission.perm_list (id, code, description)
12336     VALUES (
12337         528,
12338         'ADMIN_ORG_UNIT_CUSTOM_TREE',
12339         oils_i18n_gettext(
12340             528,
12341             'User may update custom org unit trees',
12342             'ppl',
12343             'description'
12344         )
12345     );
12346
12347 -- Evergreen DB patch 0707.schema.acq-vandelay-integration.sql
12348
12349 SELECT evergreen.upgrade_deps_block_check('0707', :eg_version);
12350
12351 -- seed data --
12352
12353 INSERT INTO permission.perm_list ( id, code, description )
12354     VALUES (
12355         529,
12356         'ADMIN_IMPORT_MATCH_SET',
12357         oils_i18n_gettext(
12358             529,
12359             'Allows a user to create/retrieve/update/delete vandelay match sets',
12360             'ppl',
12361             'description'
12362         )
12363     ), (
12364         530,
12365         'VIEW_IMPORT_MATCH_SET',
12366         oils_i18n_gettext(
12367             530,
12368             'Allows a user to view vandelay match sets',
12369             'ppl',
12370             'description'
12371         )
12372     );
12373
12374 -- This upgrade script fixed a typo in a previous one. It was corrected in the proper place in this file.
12375 -- Still, record the fact it has been "applied".
12376 SELECT evergreen.upgrade_deps_block_check('0708', :eg_version);
12377
12378 -- Evergreen DB patch 0709.data.misc_missing_perms.sql
12379
12380 SELECT evergreen.upgrade_deps_block_check('0709', :eg_version);
12381
12382 INSERT INTO permission.perm_list ( id, code, description ) 
12383     VALUES ( 
12384         531, 
12385         'ADMIN_ADDRESS_ALERT',
12386         oils_i18n_gettext( 
12387             531,
12388             'Allows a user to create/retrieve/update/delete address alerts',
12389             'ppl', 
12390             'description' 
12391         )
12392     ), ( 
12393         532, 
12394         'VIEW_ADDRESS_ALERT',
12395         oils_i18n_gettext( 
12396             532,
12397             'Allows a user to view address alerts',
12398             'ppl', 
12399             'description' 
12400         )
12401     ), ( 
12402         533, 
12403         'ADMIN_COPY_LOCATION_GROUP',
12404         oils_i18n_gettext( 
12405             533,
12406             'Allows a user to create/retrieve/update/delete copy location groups',
12407             'ppl', 
12408             'description' 
12409         )
12410     ), ( 
12411         534, 
12412         'ADMIN_USER_ACTIVITY_TYPE',
12413         oils_i18n_gettext( 
12414             534,
12415             'Allows a user to create/retrieve/update/delete user activity types',
12416             'ppl', 
12417             'description' 
12418         )
12419     );
12420
12421 -- 0715.data.add_acq_config_group
12422 SELECT evergreen.upgrade_deps_block_check('0715', :eg_version);
12423
12424 INSERT INTO config.settings_group (name, label) VALUES
12425 ('acq', oils_i18n_gettext('config.settings_group.system', 'Acquisitions', 'coust', 'label'));
12426
12427 UPDATE config.org_unit_setting_type
12428     SET grp = 'acq'
12429     WHERE name LIKE 'acq%';
12430
12431 -- Evergreen DB patch 0716.coded_value_map_id_seq_fix.sql
12432
12433 SELECT evergreen.upgrade_deps_block_check('0716', :eg_version);
12434
12435 SELECT SETVAL('config.coded_value_map_id_seq'::TEXT, (SELECT max(id) FROM config.coded_value_map));
12436
12437
12438 -- Evergreen DB patch 0717.data.safer-control-set-defaults.sql
12439
12440 SELECT evergreen.upgrade_deps_block_check('0717', :eg_version);
12441
12442 -- Allow un-mapped thesauri
12443 -- ALTER TABLE authority.thesaurus ALTER COLUMN control_set DROP NOT NULL;
12444 -- XXX The above line is now covered by changes to the
12445 -- "CREATE TABLE authority.thesaurus" statement further up.
12446
12447 -- Don't tie "No attempt to code" to LoC
12448 UPDATE authority.thesaurus SET control_set = NULL WHERE code = '|';
12449 UPDATE authority.record_entry SET control_set = NULL WHERE id IN (SELECT record FROM authority.rec_descriptor WHERE thesaurus = '|');
12450
12451
12452 COMMIT;
12453
12454 \qecho ************************************************************************
12455 \qecho The following transaction, wrapping upgrade 0672, may take a while.  If
12456 \qecho it takes an unduly long time, try it outside of a transaction.
12457 \qecho ************************************************************************
12458
12459 BEGIN;
12460
12461 -- Evergreen DB patch 0672.fix-nonfiling-titles.sql
12462 --
12463 -- Titles that begin with non-filing articles using apostrophes
12464 -- (for example, "L'armée") get spaces injected between the article
12465 -- and the subsequent text, which then breaks searching for titles
12466 -- beginning with those articles.
12467 --
12468 -- This patch adds a nonfiling title element to MODS32 that can then
12469 -- be used to retrieve the title proper without affecting the spaces
12470 -- in the title. It's what we want, what we really really want, for
12471 -- title searches.
12472 --
12473
12474
12475 -- check whether patch can be applied
12476 SELECT evergreen.upgrade_deps_block_check('0672', :eg_version);
12477
12478 -- Update the XPath definition before the titleNonfiling element exists;
12479 -- but are you really going to read through the whole XSL below before
12480 -- seeing this important bit?
12481 UPDATE config.metabib_field
12482     SET xpath = $$//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]$$,
12483         format = 'mods32'
12484     WHERE field_class = 'title' AND name = 'proper';
12485
12486 UPDATE config.xml_transform SET xslt=$$<?xml version="1.0" encoding="UTF-8"?>
12487 <xsl:stylesheet xmlns="http://www.loc.gov/mods/v3" xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xlink marc" version="1.0">
12488         <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
12489 <!--
12490 Revision 1.14 - Fixed template isValid and fields 010, 020, 022, 024, 028, and 037 to output additional identifier elements 
12491   with corresponding @type and @invalid eq 'yes' when subfields z or y (in the case of 022) exist in the MARCXML ::: 2007/01/04 17:35:20 cred
12492
12493 Revision 1.13 - Changed order of output under cartographics to reflect schema  2006/11/28 tmee
12494         
12495 Revision 1.12 - Updated to reflect MODS 3.2 Mapping  2006/10/11 tmee
12496                 
12497 Revision 1.11 - The attribute objectPart moved from <languageTerm> to <language>
12498       2006/04/08  jrad
12499
12500 Revision 1.10 MODS 3.1 revisions to language and classification elements  
12501                                 (plus ability to find marc:collection embedded in wrapper elements such as SRU zs: wrappers)
12502                                 2006/02/06  ggar
12503
12504 Revision 1.9 subfield $y was added to field 242 2004/09/02 10:57 jrad
12505
12506 Revision 1.8 Subject chopPunctuation expanded and attribute fixes 2004/08/12 jrad
12507
12508 Revision 1.7 2004/03/25 08:29 jrad
12509
12510 Revision 1.6 various validation fixes 2004/02/20 ntra
12511
12512 Revision 1.5  2003/10/02 16:18:58  ntra
12513 MODS2 to MODS3 updates, language unstacking and 
12514 de-duping, chopPunctuation expanded
12515
12516 Revision 1.3  2003/04/03 00:07:19  ntra
12517 Revision 1.3 Additional Changes not related to MODS Version 2.0 by ntra
12518
12519 Revision 1.2  2003/03/24 19:37:42  ckeith
12520 Added Log Comment
12521
12522 -->
12523         <xsl:template match="/">
12524                 <xsl:choose>
12525                         <xsl:when test="//marc:collection">
12526                                 <modsCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/v3/mods-3-2.xsd">
12527                                         <xsl:for-each select="//marc:collection/marc:record">
12528                                                 <mods version="3.2">
12529                                                         <xsl:call-template name="marcRecord"/>
12530                                                 </mods>
12531                                         </xsl:for-each>
12532                                 </modsCollection>
12533                         </xsl:when>
12534                         <xsl:otherwise>
12535                                 <mods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2" xsi:schemaLocation="http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/v3/mods-3-2.xsd">
12536                                         <xsl:for-each select="//marc:record">
12537                                                 <xsl:call-template name="marcRecord"/>
12538                                         </xsl:for-each>
12539                                 </mods>
12540                         </xsl:otherwise>
12541                 </xsl:choose>
12542         </xsl:template>
12543         <xsl:template name="marcRecord">
12544                 <xsl:variable name="leader" select="marc:leader"/>
12545                 <xsl:variable name="leader6" select="substring($leader,7,1)"/>
12546                 <xsl:variable name="leader7" select="substring($leader,8,1)"/>
12547                 <xsl:variable name="controlField008" select="marc:controlfield[@tag='008']"/>
12548                 <xsl:variable name="typeOf008">
12549                         <xsl:choose>
12550                                 <xsl:when test="$leader6='a'">
12551                                         <xsl:choose>
12552                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">BK</xsl:when>
12553                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">SE</xsl:when>
12554                                         </xsl:choose>
12555                                 </xsl:when>
12556                                 <xsl:when test="$leader6='t'">BK</xsl:when>
12557                                 <xsl:when test="$leader6='p'">MM</xsl:when>
12558                                 <xsl:when test="$leader6='m'">CF</xsl:when>
12559                                 <xsl:when test="$leader6='e' or $leader6='f'">MP</xsl:when>
12560                                 <xsl:when test="$leader6='g' or $leader6='k' or $leader6='o' or $leader6='r'">VM</xsl:when>
12561                                 <xsl:when test="$leader6='c' or $leader6='d' or $leader6='i' or $leader6='j'">MU</xsl:when>
12562                         </xsl:choose>
12563                 </xsl:variable>
12564                 <xsl:for-each select="marc:datafield[@tag='245']">
12565                         <titleInfo>
12566                                 <xsl:variable name="title">
12567                                         <xsl:choose>
12568                                                 <xsl:when test="marc:subfield[@code='b']">
12569                                                         <xsl:call-template name="specialSubfieldSelect">
12570                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12571                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12572                                                         </xsl:call-template>
12573                                                 </xsl:when>
12574                                                 <xsl:otherwise>
12575                                                         <xsl:call-template name="subfieldSelect">
12576                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12577                                                         </xsl:call-template>
12578                                                 </xsl:otherwise>
12579                                         </xsl:choose>
12580                                 </xsl:variable>
12581                                 <xsl:variable name="titleChop">
12582                                         <xsl:call-template name="chopPunctuation">
12583                                                 <xsl:with-param name="chopString">
12584                                                         <xsl:value-of select="$title"/>
12585                                                 </xsl:with-param>
12586                                         </xsl:call-template>
12587                                 </xsl:variable>
12588                                 <xsl:choose>
12589                                         <xsl:when test="@ind2>0">
12590                                                 <nonSort>
12591                                                         <xsl:value-of select="substring($titleChop,1,@ind2)"/>
12592                                                 </nonSort>
12593                                                 <title>
12594                                                         <xsl:value-of select="substring($titleChop,@ind2+1)"/>
12595                                                 </title>
12596                                         </xsl:when>
12597                                         <xsl:otherwise>
12598                                                 <title>
12599                                                         <xsl:value-of select="$titleChop"/>
12600                                                 </title>
12601                                         </xsl:otherwise>
12602                                 </xsl:choose>
12603                                 <xsl:if test="marc:subfield[@code='b']">
12604                                         <subTitle>
12605                                                 <xsl:call-template name="chopPunctuation">
12606                                                         <xsl:with-param name="chopString">
12607                                                                 <xsl:call-template name="specialSubfieldSelect">
12608                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12609                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12610                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12611                                                                 </xsl:call-template>
12612                                                         </xsl:with-param>
12613                                                 </xsl:call-template>
12614                                         </subTitle>
12615                                 </xsl:if>
12616                                 <xsl:call-template name="part"></xsl:call-template>
12617                         </titleInfo>
12618                         <!-- A form of title that ignores non-filing characters; useful
12619                                  for not converting "L'Oreal" into "L' Oreal" at index time -->
12620                         <titleNonfiling>
12621                                 <xsl:variable name="title">
12622                                         <xsl:choose>
12623                                                 <xsl:when test="marc:subfield[@code='b']">
12624                                                         <xsl:call-template name="specialSubfieldSelect">
12625                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12626                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12627                                                         </xsl:call-template>
12628                                                 </xsl:when>
12629                                                 <xsl:otherwise>
12630                                                         <xsl:call-template name="subfieldSelect">
12631                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12632                                                         </xsl:call-template>
12633                                                 </xsl:otherwise>
12634                                         </xsl:choose>
12635                                 </xsl:variable>
12636                                 <title>
12637                                         <xsl:value-of select="$title"/>
12638                                 </title>
12639                                 <xsl:if test="marc:subfield[@code='b']">
12640                                         <subTitle>
12641                                                 <xsl:call-template name="chopPunctuation">
12642                                                         <xsl:with-param name="chopString">
12643                                                                 <xsl:call-template name="specialSubfieldSelect">
12644                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12645                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12646                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12647                                                                 </xsl:call-template>
12648                                                         </xsl:with-param>
12649                                                 </xsl:call-template>
12650                                         </subTitle>
12651                                 </xsl:if>
12652                                 <xsl:call-template name="part"></xsl:call-template>
12653                         </titleNonfiling>
12654                 </xsl:for-each>
12655                 <xsl:for-each select="marc:datafield[@tag='210']">
12656                         <titleInfo type="abbreviated">
12657                                 <title>
12658                                         <xsl:call-template name="chopPunctuation">
12659                                                 <xsl:with-param name="chopString">
12660                                                         <xsl:call-template name="subfieldSelect">
12661                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12662                                                         </xsl:call-template>
12663                                                 </xsl:with-param>
12664                                         </xsl:call-template>
12665                                 </title>
12666                                 <xsl:call-template name="subtitle"/>
12667                         </titleInfo>
12668                 </xsl:for-each>
12669                 <xsl:for-each select="marc:datafield[@tag='242']">
12670                         <titleInfo type="translated">
12671                                 <!--09/01/04 Added subfield $y-->
12672                                 <xsl:for-each select="marc:subfield[@code='y']">
12673                                         <xsl:attribute name="lang">
12674                                                 <xsl:value-of select="text()"/>
12675                                         </xsl:attribute>
12676                                 </xsl:for-each>
12677                                 <title>
12678                                         <xsl:call-template name="chopPunctuation">
12679                                                 <xsl:with-param name="chopString">
12680                                                         <xsl:call-template name="subfieldSelect">
12681                                                                 <!-- 1/04 removed $h, b -->
12682                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12683                                                         </xsl:call-template>
12684                                                 </xsl:with-param>
12685                                         </xsl:call-template>
12686                                 </title>
12687                                 <!-- 1/04 fix -->
12688                                 <xsl:call-template name="subtitle"/>
12689                                 <xsl:call-template name="part"/>
12690                         </titleInfo>
12691                 </xsl:for-each>
12692                 <xsl:for-each select="marc:datafield[@tag='246']">
12693                         <titleInfo type="alternative">
12694                                 <xsl:for-each select="marc:subfield[@code='i']">
12695                                         <xsl:attribute name="displayLabel">
12696                                                 <xsl:value-of select="text()"/>
12697                                         </xsl:attribute>
12698                                 </xsl:for-each>
12699                                 <title>
12700                                         <xsl:call-template name="chopPunctuation">
12701                                                 <xsl:with-param name="chopString">
12702                                                         <xsl:call-template name="subfieldSelect">
12703                                                                 <!-- 1/04 removed $h, $b -->
12704                                                                 <xsl:with-param name="codes">af</xsl:with-param>
12705                                                         </xsl:call-template>
12706                                                 </xsl:with-param>
12707                                         </xsl:call-template>
12708                                 </title>
12709                                 <xsl:call-template name="subtitle"/>
12710                                 <xsl:call-template name="part"/>
12711                         </titleInfo>
12712                 </xsl:for-each>
12713                 <xsl:for-each select="marc:datafield[@tag='130']|marc:datafield[@tag='240']|marc:datafield[@tag='730'][@ind2!='2']">
12714                         <titleInfo type="uniform">
12715                                 <title>
12716                                         <xsl:variable name="str">
12717                                                 <xsl:for-each select="marc:subfield">
12718                                                         <xsl:if test="(contains('adfklmor',@code) and (not(../marc:subfield[@code='n' or @code='p']) or (following-sibling::marc:subfield[@code='n' or @code='p'])))">
12719                                                                 <xsl:value-of select="text()"/>
12720                                                                 <xsl:text> </xsl:text>
12721                                                         </xsl:if>
12722                                                 </xsl:for-each>
12723                                         </xsl:variable>
12724                                         <xsl:call-template name="chopPunctuation">
12725                                                 <xsl:with-param name="chopString">
12726                                                         <xsl:value-of select="substring($str,1,string-length($str)-1)"/>
12727                                                 </xsl:with-param>
12728                                         </xsl:call-template>
12729                                 </title>
12730                                 <xsl:call-template name="part"/>
12731                         </titleInfo>
12732                 </xsl:for-each>
12733                 <xsl:for-each select="marc:datafield[@tag='740'][@ind2!='2']">
12734                         <titleInfo type="alternative">
12735                                 <title>
12736                                         <xsl:call-template name="chopPunctuation">
12737                                                 <xsl:with-param name="chopString">
12738                                                         <xsl:call-template name="subfieldSelect">
12739                                                                 <xsl:with-param name="codes">ah</xsl:with-param>
12740                                                         </xsl:call-template>
12741                                                 </xsl:with-param>
12742                                         </xsl:call-template>
12743                                 </title>
12744                                 <xsl:call-template name="part"/>
12745                         </titleInfo>
12746                 </xsl:for-each>
12747                 <xsl:for-each select="marc:datafield[@tag='100']">
12748                         <name type="personal">
12749                                 <xsl:call-template name="nameABCDQ"/>
12750                                 <xsl:call-template name="affiliation"/>
12751                                 <role>
12752                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12753                                 </role>
12754                                 <xsl:call-template name="role"/>
12755                         </name>
12756                 </xsl:for-each>
12757                 <xsl:for-each select="marc:datafield[@tag='110']">
12758                         <name type="corporate">
12759                                 <xsl:call-template name="nameABCDN"/>
12760                                 <role>
12761                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12762                                 </role>
12763                                 <xsl:call-template name="role"/>
12764                         </name>
12765                 </xsl:for-each>
12766                 <xsl:for-each select="marc:datafield[@tag='111']">
12767                         <name type="conference">
12768                                 <xsl:call-template name="nameACDEQ"/>
12769                                 <role>
12770                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12771                                 </role>
12772                                 <xsl:call-template name="role"/>
12773                         </name>
12774                 </xsl:for-each>
12775                 <xsl:for-each select="marc:datafield[@tag='700'][not(marc:subfield[@code='t'])]">
12776                         <name type="personal">
12777                                 <xsl:call-template name="nameABCDQ"/>
12778                                 <xsl:call-template name="affiliation"/>
12779                                 <xsl:call-template name="role"/>
12780                         </name>
12781                 </xsl:for-each>
12782                 <xsl:for-each select="marc:datafield[@tag='710'][not(marc:subfield[@code='t'])]">
12783                         <name type="corporate">
12784                                 <xsl:call-template name="nameABCDN"/>
12785                                 <xsl:call-template name="role"/>
12786                         </name>
12787                 </xsl:for-each>
12788                 <xsl:for-each select="marc:datafield[@tag='711'][not(marc:subfield[@code='t'])]">
12789                         <name type="conference">
12790                                 <xsl:call-template name="nameACDEQ"/>
12791                                 <xsl:call-template name="role"/>
12792                         </name>
12793                 </xsl:for-each>
12794                 <xsl:for-each select="marc:datafield[@tag='720'][not(marc:subfield[@code='t'])]">
12795                         <name>
12796                                 <xsl:if test="@ind1=1">
12797                                         <xsl:attribute name="type">
12798                                                 <xsl:text>personal</xsl:text>
12799                                         </xsl:attribute>
12800                                 </xsl:if>
12801                                 <namePart>
12802                                         <xsl:value-of select="marc:subfield[@code='a']"/>
12803                                 </namePart>
12804                                 <xsl:call-template name="role"/>
12805                         </name>
12806                 </xsl:for-each>
12807                 <typeOfResource>
12808                         <xsl:if test="$leader7='c'">
12809                                 <xsl:attribute name="collection">yes</xsl:attribute>
12810                         </xsl:if>
12811                         <xsl:if test="$leader6='d' or $leader6='f' or $leader6='p' or $leader6='t'">
12812                                 <xsl:attribute name="manuscript">yes</xsl:attribute>
12813                         </xsl:if>
12814                         <xsl:choose>
12815                                 <xsl:when test="$leader6='a' or $leader6='t'">text</xsl:when>
12816                                 <xsl:when test="$leader6='e' or $leader6='f'">cartographic</xsl:when>
12817                                 <xsl:when test="$leader6='c' or $leader6='d'">notated music</xsl:when>
12818                                 <xsl:when test="$leader6='i'">sound recording-nonmusical</xsl:when>
12819                                 <xsl:when test="$leader6='j'">sound recording-musical</xsl:when>
12820                                 <xsl:when test="$leader6='k'">still image</xsl:when>
12821                                 <xsl:when test="$leader6='g'">moving image</xsl:when>
12822                                 <xsl:when test="$leader6='r'">three dimensional object</xsl:when>
12823                                 <xsl:when test="$leader6='m'">software, multimedia</xsl:when>
12824                                 <xsl:when test="$leader6='p'">mixed material</xsl:when>
12825                         </xsl:choose>
12826                 </typeOfResource>
12827                 <xsl:if test="substring($controlField008,26,1)='d'">
12828                         <genre authority="marc">globe</genre>
12829                 </xsl:if>
12830                 <xsl:if test="marc:controlfield[@tag='007'][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
12831                         <genre authority="marc">remote sensing image</genre>
12832                 </xsl:if>
12833                 <xsl:if test="$typeOf008='MP'">
12834                         <xsl:variable name="controlField008-25" select="substring($controlField008,26,1)"></xsl:variable>
12835                         <xsl:choose>
12836                                 <xsl:when test="$controlField008-25='a' or $controlField008-25='b' or $controlField008-25='c' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
12837                                         <genre authority="marc">map</genre>
12838                                 </xsl:when>
12839                                 <xsl:when test="$controlField008-25='e' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
12840                                         <genre authority="marc">atlas</genre>
12841                                 </xsl:when>
12842                         </xsl:choose>
12843                 </xsl:if>
12844                 <xsl:if test="$typeOf008='SE'">
12845                         <xsl:variable name="controlField008-21" select="substring($controlField008,22,1)"></xsl:variable>
12846                         <xsl:choose>
12847                                 <xsl:when test="$controlField008-21='d'">
12848                                         <genre authority="marc">database</genre>
12849                                 </xsl:when>
12850                                 <xsl:when test="$controlField008-21='l'">
12851                                         <genre authority="marc">loose-leaf</genre>
12852                                 </xsl:when>
12853                                 <xsl:when test="$controlField008-21='m'">
12854                                         <genre authority="marc">series</genre>
12855                                 </xsl:when>
12856                                 <xsl:when test="$controlField008-21='n'">
12857                                         <genre authority="marc">newspaper</genre>
12858                                 </xsl:when>
12859                                 <xsl:when test="$controlField008-21='p'">
12860                                         <genre authority="marc">periodical</genre>
12861                                 </xsl:when>
12862                                 <xsl:when test="$controlField008-21='w'">
12863                                         <genre authority="marc">web site</genre>
12864                                 </xsl:when>
12865                         </xsl:choose>
12866                 </xsl:if>
12867                 <xsl:if test="$typeOf008='BK' or $typeOf008='SE'">
12868                         <xsl:variable name="controlField008-24" select="substring($controlField008,25,4)"></xsl:variable>
12869                         <xsl:choose>
12870                                 <xsl:when test="contains($controlField008-24,'a')">
12871                                         <genre authority="marc">abstract or summary</genre>
12872                                 </xsl:when>
12873                                 <xsl:when test="contains($controlField008-24,'b')">
12874                                         <genre authority="marc">bibliography</genre>
12875                                 </xsl:when>
12876                                 <xsl:when test="contains($controlField008-24,'c')">
12877                                         <genre authority="marc">catalog</genre>
12878                                 </xsl:when>
12879                                 <xsl:when test="contains($controlField008-24,'d')">
12880                                         <genre authority="marc">dictionary</genre>
12881                                 </xsl:when>
12882                                 <xsl:when test="contains($controlField008-24,'e')">
12883                                         <genre authority="marc">encyclopedia</genre>
12884                                 </xsl:when>
12885                                 <xsl:when test="contains($controlField008-24,'f')">
12886                                         <genre authority="marc">handbook</genre>
12887                                 </xsl:when>
12888                                 <xsl:when test="contains($controlField008-24,'g')">
12889                                         <genre authority="marc">legal article</genre>
12890                                 </xsl:when>
12891                                 <xsl:when test="contains($controlField008-24,'i')">
12892                                         <genre authority="marc">index</genre>
12893                                 </xsl:when>
12894                                 <xsl:when test="contains($controlField008-24,'k')">
12895                                         <genre authority="marc">discography</genre>
12896                                 </xsl:when>
12897                                 <xsl:when test="contains($controlField008-24,'l')">
12898                                         <genre authority="marc">legislation</genre>
12899                                 </xsl:when>
12900                                 <xsl:when test="contains($controlField008-24,'m')">
12901                                         <genre authority="marc">theses</genre>
12902                                 </xsl:when>
12903                                 <xsl:when test="contains($controlField008-24,'n')">
12904                                         <genre authority="marc">survey of literature</genre>
12905                                 </xsl:when>
12906                                 <xsl:when test="contains($controlField008-24,'o')">
12907                                         <genre authority="marc">review</genre>
12908                                 </xsl:when>
12909                                 <xsl:when test="contains($controlField008-24,'p')">
12910                                         <genre authority="marc">programmed text</genre>
12911                                 </xsl:when>
12912                                 <xsl:when test="contains($controlField008-24,'q')">
12913                                         <genre authority="marc">filmography</genre>
12914                                 </xsl:when>
12915                                 <xsl:when test="contains($controlField008-24,'r')">
12916                                         <genre authority="marc">directory</genre>
12917                                 </xsl:when>
12918                                 <xsl:when test="contains($controlField008-24,'s')">
12919                                         <genre authority="marc">statistics</genre>
12920                                 </xsl:when>
12921                                 <xsl:when test="contains($controlField008-24,'t')">
12922                                         <genre authority="marc">technical report</genre>
12923                                 </xsl:when>
12924                                 <xsl:when test="contains($controlField008-24,'v')">
12925                                         <genre authority="marc">legal case and case notes</genre>
12926                                 </xsl:when>
12927                                 <xsl:when test="contains($controlField008-24,'w')">
12928                                         <genre authority="marc">law report or digest</genre>
12929                                 </xsl:when>
12930                                 <xsl:when test="contains($controlField008-24,'z')">
12931                                         <genre authority="marc">treaty</genre>
12932                                 </xsl:when>
12933                         </xsl:choose>
12934                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
12935                         <xsl:choose>
12936                                 <xsl:when test="$controlField008-29='1'">
12937                                         <genre authority="marc">conference publication</genre>
12938                                 </xsl:when>
12939                         </xsl:choose>
12940                 </xsl:if>
12941                 <xsl:if test="$typeOf008='CF'">
12942                         <xsl:variable name="controlField008-26" select="substring($controlField008,27,1)"></xsl:variable>
12943                         <xsl:choose>
12944                                 <xsl:when test="$controlField008-26='a'">
12945                                         <genre authority="marc">numeric data</genre>
12946                                 </xsl:when>
12947                                 <xsl:when test="$controlField008-26='e'">
12948                                         <genre authority="marc">database</genre>
12949                                 </xsl:when>
12950                                 <xsl:when test="$controlField008-26='f'">
12951                                         <genre authority="marc">font</genre>
12952                                 </xsl:when>
12953                                 <xsl:when test="$controlField008-26='g'">
12954                                         <genre authority="marc">game</genre>
12955                                 </xsl:when>
12956                         </xsl:choose>
12957                 </xsl:if>
12958                 <xsl:if test="$typeOf008='BK'">
12959                         <xsl:if test="substring($controlField008,25,1)='j'">
12960                                 <genre authority="marc">patent</genre>
12961                         </xsl:if>
12962                         <xsl:if test="substring($controlField008,31,1)='1'">
12963                                 <genre authority="marc">festschrift</genre>
12964                         </xsl:if>
12965                         <xsl:variable name="controlField008-34" select="substring($controlField008,35,1)"></xsl:variable>
12966                         <xsl:if test="$controlField008-34='a' or $controlField008-34='b' or $controlField008-34='c' or $controlField008-34='d'">
12967                                 <genre authority="marc">biography</genre>
12968                         </xsl:if>
12969                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
12970                         <xsl:choose>
12971                                 <xsl:when test="$controlField008-33='e'">
12972                                         <genre authority="marc">essay</genre>
12973                                 </xsl:when>
12974                                 <xsl:when test="$controlField008-33='d'">
12975                                         <genre authority="marc">drama</genre>
12976                                 </xsl:when>
12977                                 <xsl:when test="$controlField008-33='c'">
12978                                         <genre authority="marc">comic strip</genre>
12979                                 </xsl:when>
12980                                 <xsl:when test="$controlField008-33='l'">
12981                                         <genre authority="marc">fiction</genre>
12982                                 </xsl:when>
12983                                 <xsl:when test="$controlField008-33='h'">
12984                                         <genre authority="marc">humor, satire</genre>
12985                                 </xsl:when>
12986                                 <xsl:when test="$controlField008-33='i'">
12987                                         <genre authority="marc">letter</genre>
12988                                 </xsl:when>
12989                                 <xsl:when test="$controlField008-33='f'">
12990                                         <genre authority="marc">novel</genre>
12991                                 </xsl:when>
12992                                 <xsl:when test="$controlField008-33='j'">
12993                                         <genre authority="marc">short story</genre>
12994                                 </xsl:when>
12995                                 <xsl:when test="$controlField008-33='s'">
12996                                         <genre authority="marc">speech</genre>
12997                                 </xsl:when>
12998                         </xsl:choose>
12999                 </xsl:if>
13000                 <xsl:if test="$typeOf008='MU'">
13001                         <xsl:variable name="controlField008-30-31" select="substring($controlField008,31,2)"></xsl:variable>
13002                         <xsl:if test="contains($controlField008-30-31,'b')">
13003                                 <genre authority="marc">biography</genre>
13004                         </xsl:if>
13005                         <xsl:if test="contains($controlField008-30-31,'c')">
13006                                 <genre authority="marc">conference publication</genre>
13007                         </xsl:if>
13008                         <xsl:if test="contains($controlField008-30-31,'d')">
13009                                 <genre authority="marc">drama</genre>
13010                         </xsl:if>
13011                         <xsl:if test="contains($controlField008-30-31,'e')">
13012                                 <genre authority="marc">essay</genre>
13013                         </xsl:if>
13014                         <xsl:if test="contains($controlField008-30-31,'f')">
13015                                 <genre authority="marc">fiction</genre>
13016                         </xsl:if>
13017                         <xsl:if test="contains($controlField008-30-31,'o')">
13018                                 <genre authority="marc">folktale</genre>
13019                         </xsl:if>
13020                         <xsl:if test="contains($controlField008-30-31,'h')">
13021                                 <genre authority="marc">history</genre>
13022                         </xsl:if>
13023                         <xsl:if test="contains($controlField008-30-31,'k')">
13024                                 <genre authority="marc">humor, satire</genre>
13025                         </xsl:if>
13026                         <xsl:if test="contains($controlField008-30-31,'m')">
13027                                 <genre authority="marc">memoir</genre>
13028                         </xsl:if>
13029                         <xsl:if test="contains($controlField008-30-31,'p')">
13030                                 <genre authority="marc">poetry</genre>
13031                         </xsl:if>
13032                         <xsl:if test="contains($controlField008-30-31,'r')">
13033                                 <genre authority="marc">rehearsal</genre>
13034                         </xsl:if>
13035                         <xsl:if test="contains($controlField008-30-31,'g')">
13036                                 <genre authority="marc">reporting</genre>
13037                         </xsl:if>
13038                         <xsl:if test="contains($controlField008-30-31,'s')">
13039                                 <genre authority="marc">sound</genre>
13040                         </xsl:if>
13041                         <xsl:if test="contains($controlField008-30-31,'l')">
13042                                 <genre authority="marc">speech</genre>
13043                         </xsl:if>
13044                 </xsl:if>
13045                 <xsl:if test="$typeOf008='VM'">
13046                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
13047                         <xsl:choose>
13048                                 <xsl:when test="$controlField008-33='a'">
13049                                         <genre authority="marc">art original</genre>
13050                                 </xsl:when>
13051                                 <xsl:when test="$controlField008-33='b'">
13052                                         <genre authority="marc">kit</genre>
13053                                 </xsl:when>
13054                                 <xsl:when test="$controlField008-33='c'">
13055                                         <genre authority="marc">art reproduction</genre>
13056                                 </xsl:when>
13057                                 <xsl:when test="$controlField008-33='d'">
13058                                         <genre authority="marc">diorama</genre>
13059                                 </xsl:when>
13060                                 <xsl:when test="$controlField008-33='f'">
13061                                         <genre authority="marc">filmstrip</genre>
13062                                 </xsl:when>
13063                                 <xsl:when test="$controlField008-33='g'">
13064                                         <genre authority="marc">legal article</genre>
13065                                 </xsl:when>
13066                                 <xsl:when test="$controlField008-33='i'">
13067                                         <genre authority="marc">picture</genre>
13068                                 </xsl:when>
13069                                 <xsl:when test="$controlField008-33='k'">
13070                                         <genre authority="marc">graphic</genre>
13071                                 </xsl:when>
13072                                 <xsl:when test="$controlField008-33='l'">
13073                                         <genre authority="marc">technical drawing</genre>
13074                                 </xsl:when>
13075                                 <xsl:when test="$controlField008-33='m'">
13076                                         <genre authority="marc">motion picture</genre>
13077                                 </xsl:when>
13078                                 <xsl:when test="$controlField008-33='n'">
13079                                         <genre authority="marc">chart</genre>
13080                                 </xsl:when>
13081                                 <xsl:when test="$controlField008-33='o'">
13082                                         <genre authority="marc">flash card</genre>
13083                                 </xsl:when>
13084                                 <xsl:when test="$controlField008-33='p'">
13085                                         <genre authority="marc">microscope slide</genre>
13086                                 </xsl:when>
13087                                 <xsl:when test="$controlField008-33='q' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13088                                         <genre authority="marc">model</genre>
13089                                 </xsl:when>
13090                                 <xsl:when test="$controlField008-33='r'">
13091                                         <genre authority="marc">realia</genre>
13092                                 </xsl:when>
13093                                 <xsl:when test="$controlField008-33='s'">
13094                                         <genre authority="marc">slide</genre>
13095                                 </xsl:when>
13096                                 <xsl:when test="$controlField008-33='t'">
13097                                         <genre authority="marc">transparency</genre>
13098                                 </xsl:when>
13099                                 <xsl:when test="$controlField008-33='v'">
13100                                         <genre authority="marc">videorecording</genre>
13101                                 </xsl:when>
13102                                 <xsl:when test="$controlField008-33='w'">
13103                                         <genre authority="marc">toy</genre>
13104                                 </xsl:when>
13105                         </xsl:choose>
13106                 </xsl:if>
13107                 <xsl:for-each select="marc:datafield[@tag=655]">
13108                         <genre authority="marc">
13109                                 <xsl:attribute name="authority">
13110                                         <xsl:value-of select="marc:subfield[@code='2']"/>
13111                                 </xsl:attribute>
13112                                 <xsl:call-template name="subfieldSelect">
13113                                         <xsl:with-param name="codes">abvxyz</xsl:with-param>
13114                                         <xsl:with-param name="delimeter">-</xsl:with-param>
13115                                 </xsl:call-template>
13116                         </genre>
13117                 </xsl:for-each>
13118                 <originInfo>
13119                         <xsl:variable name="MARCpublicationCode" select="normalize-space(substring($controlField008,16,3))"></xsl:variable>
13120                         <xsl:if test="translate($MARCpublicationCode,'|','')">
13121                                 <place>
13122                                         <placeTerm>
13123                                                 <xsl:attribute name="type">code</xsl:attribute>
13124                                                 <xsl:attribute name="authority">marccountry</xsl:attribute>
13125                                                 <xsl:value-of select="$MARCpublicationCode"/>
13126                                         </placeTerm>
13127                                 </place>
13128                         </xsl:if>
13129                         <xsl:for-each select="marc:datafield[@tag=044]/marc:subfield[@code='c']">
13130                                 <place>
13131                                         <placeTerm>
13132                                                 <xsl:attribute name="type">code</xsl:attribute>
13133                                                 <xsl:attribute name="authority">iso3166</xsl:attribute>
13134                                                 <xsl:value-of select="."/>
13135                                         </placeTerm>
13136                                 </place>
13137                         </xsl:for-each>
13138                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='a']">
13139                                 <place>
13140                                         <placeTerm>
13141                                                 <xsl:attribute name="type">text</xsl:attribute>
13142                                                 <xsl:call-template name="chopPunctuationFront">
13143                                                         <xsl:with-param name="chopString">
13144                                                                 <xsl:call-template name="chopPunctuation">
13145                                                                         <xsl:with-param name="chopString" select="."/>
13146                                                                 </xsl:call-template>
13147                                                         </xsl:with-param>
13148                                                 </xsl:call-template>
13149                                         </placeTerm>
13150                                 </place>
13151                         </xsl:for-each>
13152                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='m']">
13153                                 <dateValid point="start">
13154                                         <xsl:value-of select="."/>
13155                                 </dateValid>
13156                         </xsl:for-each>
13157                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='n']">
13158                                 <dateValid point="end">
13159                                         <xsl:value-of select="."/>
13160                                 </dateValid>
13161                         </xsl:for-each>
13162                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='j']">
13163                                 <dateModified>
13164                                         <xsl:value-of select="."/>
13165                                 </dateModified>
13166                         </xsl:for-each>
13167                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='b' or @code='c' or @code='g']">
13168                                 <xsl:choose>
13169                                         <xsl:when test="@code='b'">
13170                                                 <publisher>
13171                                                         <xsl:call-template name="chopPunctuation">
13172                                                                 <xsl:with-param name="chopString" select="."/>
13173                                                                 <xsl:with-param name="punctuation">
13174                                                                         <xsl:text>:,;/ </xsl:text>
13175                                                                 </xsl:with-param>
13176                                                         </xsl:call-template>
13177                                                 </publisher>
13178                                         </xsl:when>
13179                                         <xsl:when test="@code='c'">
13180                                                 <dateIssued>
13181                                                         <xsl:call-template name="chopPunctuation">
13182                                                                 <xsl:with-param name="chopString" select="."/>
13183                                                         </xsl:call-template>
13184                                                 </dateIssued>
13185                                         </xsl:when>
13186                                         <xsl:when test="@code='g'">
13187                                                 <dateCreated>
13188                                                         <xsl:value-of select="."/>
13189                                                 </dateCreated>
13190                                         </xsl:when>
13191                                 </xsl:choose>
13192                         </xsl:for-each>
13193                         <xsl:variable name="dataField260c">
13194                                 <xsl:call-template name="chopPunctuation">
13195                                         <xsl:with-param name="chopString" select="marc:datafield[@tag=260]/marc:subfield[@code='c']"></xsl:with-param>
13196                                 </xsl:call-template>
13197                         </xsl:variable>
13198                         <xsl:variable name="controlField008-7-10" select="normalize-space(substring($controlField008, 8, 4))"></xsl:variable>
13199                         <xsl:variable name="controlField008-11-14" select="normalize-space(substring($controlField008, 12, 4))"></xsl:variable>
13200                         <xsl:variable name="controlField008-6" select="normalize-space(substring($controlField008, 7, 1))"></xsl:variable>
13201                         <xsl:if test="$controlField008-6='e' or $controlField008-6='p' or $controlField008-6='r' or $controlField008-6='t' or $controlField008-6='s'">
13202                                 <xsl:if test="$controlField008-7-10 and ($controlField008-7-10 != $dataField260c)">
13203                                         <dateIssued encoding="marc">
13204                                                 <xsl:value-of select="$controlField008-7-10"/>
13205                                         </dateIssued>
13206                                 </xsl:if>
13207                         </xsl:if>
13208                         <xsl:if test="$controlField008-6='c' or $controlField008-6='d' or $controlField008-6='i' or $controlField008-6='k' or $controlField008-6='m' or $controlField008-6='q' or $controlField008-6='u'">
13209                                 <xsl:if test="$controlField008-7-10">
13210                                         <dateIssued encoding="marc" point="start">
13211                                                 <xsl:value-of select="$controlField008-7-10"/>
13212                                         </dateIssued>
13213                                 </xsl:if>
13214                         </xsl:if>
13215                         <xsl:if test="$controlField008-6='c' or $controlField008-6='d' or $controlField008-6='i' or $controlField008-6='k' or $controlField008-6='m' or $controlField008-6='q' or $controlField008-6='u'">
13216                                 <xsl:if test="$controlField008-11-14">
13217                                         <dateIssued encoding="marc" point="end">
13218                                                 <xsl:value-of select="$controlField008-11-14"/>
13219                                         </dateIssued>
13220                                 </xsl:if>
13221                         </xsl:if>
13222                         <xsl:if test="$controlField008-6='q'">
13223                                 <xsl:if test="$controlField008-7-10">
13224                                         <dateIssued encoding="marc" point="start" qualifier="questionable">
13225                                                 <xsl:value-of select="$controlField008-7-10"/>
13226                                         </dateIssued>
13227                                 </xsl:if>
13228                         </xsl:if>
13229                         <xsl:if test="$controlField008-6='q'">
13230                                 <xsl:if test="$controlField008-11-14">
13231                                         <dateIssued encoding="marc" point="end" qualifier="questionable">
13232                                                 <xsl:value-of select="$controlField008-11-14"/>
13233                                         </dateIssued>
13234                                 </xsl:if>
13235                         </xsl:if>
13236                         <xsl:if test="$controlField008-6='t'">
13237                                 <xsl:if test="$controlField008-11-14">
13238                                         <copyrightDate encoding="marc">
13239                                                 <xsl:value-of select="$controlField008-11-14"/>
13240                                         </copyrightDate>
13241                                 </xsl:if>
13242                         </xsl:if>
13243                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=0 or @ind1=1]/marc:subfield[@code='a']">
13244                                 <dateCaptured encoding="iso8601">
13245                                         <xsl:value-of select="."/>
13246                                 </dateCaptured>
13247                         </xsl:for-each>
13248                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][1]">
13249                                 <dateCaptured encoding="iso8601" point="start">
13250                                         <xsl:value-of select="."/>
13251                                 </dateCaptured>
13252                         </xsl:for-each>
13253                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][2]">
13254                                 <dateCaptured encoding="iso8601" point="end">
13255                                         <xsl:value-of select="."/>
13256                                 </dateCaptured>
13257                         </xsl:for-each>
13258                         <xsl:for-each select="marc:datafield[@tag=250]/marc:subfield[@code='a']">
13259                                 <edition>
13260                                         <xsl:value-of select="."/>
13261                                 </edition>
13262                         </xsl:for-each>
13263                         <xsl:for-each select="marc:leader">
13264                                 <issuance>
13265                                         <xsl:choose>
13266                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">monographic</xsl:when>
13267                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">continuing</xsl:when>
13268                                         </xsl:choose>
13269                                 </issuance>
13270                         </xsl:for-each>
13271                         <xsl:for-each select="marc:datafield[@tag=310]|marc:datafield[@tag=321]">
13272                                 <frequency>
13273                                         <xsl:call-template name="subfieldSelect">
13274                                                 <xsl:with-param name="codes">ab</xsl:with-param>
13275                                         </xsl:call-template>
13276                                 </frequency>
13277                         </xsl:for-each>
13278                 </originInfo>
13279                 <xsl:variable name="controlField008-35-37" select="normalize-space(translate(substring($controlField008,36,3),'|#',''))"></xsl:variable>
13280                 <xsl:if test="$controlField008-35-37">
13281                         <language>
13282                                 <languageTerm authority="iso639-2b" type="code">
13283                                         <xsl:value-of select="substring($controlField008,36,3)"/>
13284                                 </languageTerm>
13285                         </language>
13286                 </xsl:if>
13287                 <xsl:for-each select="marc:datafield[@tag=041]">
13288                         <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='d' or @code='e' or @code='f' or @code='g' or @code='h']">
13289                                 <xsl:variable name="langCodes" select="."/>
13290                                 <xsl:choose>
13291                                         <xsl:when test="../marc:subfield[@code='2']='rfc3066'">
13292                                                 <!-- not stacked but could be repeated -->
13293                                                 <xsl:call-template name="rfcLanguages">
13294                                                         <xsl:with-param name="nodeNum">
13295                                                                 <xsl:value-of select="1"/>
13296                                                         </xsl:with-param>
13297                                                         <xsl:with-param name="usedLanguages">
13298                                                                 <xsl:text></xsl:text>
13299                                                         </xsl:with-param>
13300                                                         <xsl:with-param name="controlField008-35-37">
13301                                                                 <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13302                                                         </xsl:with-param>
13303                                                 </xsl:call-template>
13304                                         </xsl:when>
13305                                         <xsl:otherwise>
13306                                                 <!-- iso -->
13307                                                 <xsl:variable name="allLanguages">
13308                                                         <xsl:copy-of select="$langCodes"></xsl:copy-of>
13309                                                 </xsl:variable>
13310                                                 <xsl:variable name="currentLanguage">
13311                                                         <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13312                                                 </xsl:variable>
13313                                                 <xsl:call-template name="isoLanguage">
13314                                                         <xsl:with-param name="currentLanguage">
13315                                                                 <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13316                                                         </xsl:with-param>
13317                                                         <xsl:with-param name="remainingLanguages">
13318                                                                 <xsl:value-of select="substring($allLanguages,4,string-length($allLanguages)-3)"></xsl:value-of>
13319                                                         </xsl:with-param>
13320                                                         <xsl:with-param name="usedLanguages">
13321                                                                 <xsl:if test="$controlField008-35-37">
13322                                                                         <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13323                                                                 </xsl:if>
13324                                                         </xsl:with-param>
13325                                                 </xsl:call-template>
13326                                         </xsl:otherwise>
13327                                 </xsl:choose>
13328                         </xsl:for-each>
13329                 </xsl:for-each>
13330                 <xsl:variable name="physicalDescription">
13331                         <!--3.2 change tmee 007/11 -->
13332                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='a']">
13333                                 <digitalOrigin>reformatted digital</digitalOrigin>
13334                         </xsl:if>
13335                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='b']">
13336                                 <digitalOrigin>digitized microfilm</digitalOrigin>
13337                         </xsl:if>
13338                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='d']">
13339                                 <digitalOrigin>digitized other analog</digitalOrigin>
13340                         </xsl:if>
13341                         <xsl:variable name="controlField008-23" select="substring($controlField008,24,1)"></xsl:variable>
13342                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
13343                         <xsl:variable name="check008-23">
13344                                 <xsl:if test="$typeOf008='BK' or $typeOf008='MU' or $typeOf008='SE' or $typeOf008='MM'">
13345                                         <xsl:value-of select="true()"></xsl:value-of>
13346                                 </xsl:if>
13347                         </xsl:variable>
13348                         <xsl:variable name="check008-29">
13349                                 <xsl:if test="$typeOf008='MP' or $typeOf008='VM'">
13350                                         <xsl:value-of select="true()"></xsl:value-of>
13351                                 </xsl:if>
13352                         </xsl:variable>
13353                         <xsl:choose>
13354                                 <xsl:when test="($check008-23 and $controlField008-23='f') or ($check008-29 and $controlField008-29='f')">
13355                                         <form authority="marcform">braille</form>
13356                                 </xsl:when>
13357                                 <xsl:when test="($controlField008-23=' ' and ($leader6='c' or $leader6='d')) or (($typeOf008='BK' or $typeOf008='SE') and ($controlField008-23=' ' or $controlField008='r'))">
13358                                         <form authority="marcform">print</form>
13359                                 </xsl:when>
13360                                 <xsl:when test="$leader6 = 'm' or ($check008-23 and $controlField008-23='s') or ($check008-29 and $controlField008-29='s')">
13361                                         <form authority="marcform">electronic</form>
13362                                 </xsl:when>
13363                                 <xsl:when test="($check008-23 and $controlField008-23='b') or ($check008-29 and $controlField008-29='b')">
13364                                         <form authority="marcform">microfiche</form>
13365                                 </xsl:when>
13366                                 <xsl:when test="($check008-23 and $controlField008-23='a') or ($check008-29 and $controlField008-29='a')">
13367                                         <form authority="marcform">microfilm</form>
13368                                 </xsl:when>
13369                         </xsl:choose>
13370                         <!-- 1/04 fix -->
13371                         <xsl:if test="marc:datafield[@tag=130]/marc:subfield[@code='h']">
13372                                 <form authority="gmd">
13373                                         <xsl:call-template name="chopBrackets">
13374                                                 <xsl:with-param name="chopString">
13375                                                         <xsl:value-of select="marc:datafield[@tag=130]/marc:subfield[@code='h']"></xsl:value-of>
13376                                                 </xsl:with-param>
13377                                         </xsl:call-template>
13378                                 </form>
13379                         </xsl:if>
13380                         <xsl:if test="marc:datafield[@tag=240]/marc:subfield[@code='h']">
13381                                 <form authority="gmd">
13382                                         <xsl:call-template name="chopBrackets">
13383                                                 <xsl:with-param name="chopString">
13384                                                         <xsl:value-of select="marc:datafield[@tag=240]/marc:subfield[@code='h']"></xsl:value-of>
13385                                                 </xsl:with-param>
13386                                         </xsl:call-template>
13387                                 </form>
13388                         </xsl:if>
13389                         <xsl:if test="marc:datafield[@tag=242]/marc:subfield[@code='h']">
13390                                 <form authority="gmd">
13391                                         <xsl:call-template name="chopBrackets">
13392                                                 <xsl:with-param name="chopString">
13393                                                         <xsl:value-of select="marc:datafield[@tag=242]/marc:subfield[@code='h']"></xsl:value-of>
13394                                                 </xsl:with-param>
13395                                         </xsl:call-template>
13396                                 </form>
13397                         </xsl:if>
13398                         <xsl:if test="marc:datafield[@tag=245]/marc:subfield[@code='h']">
13399                                 <form authority="gmd">
13400                                         <xsl:call-template name="chopBrackets">
13401                                                 <xsl:with-param name="chopString">
13402                                                         <xsl:value-of select="marc:datafield[@tag=245]/marc:subfield[@code='h']"></xsl:value-of>
13403                                                 </xsl:with-param>
13404                                         </xsl:call-template>
13405                                 </form>
13406                         </xsl:if>
13407                         <xsl:if test="marc:datafield[@tag=246]/marc:subfield[@code='h']">
13408                                 <form authority="gmd">
13409                                         <xsl:call-template name="chopBrackets">
13410                                                 <xsl:with-param name="chopString">
13411                                                         <xsl:value-of select="marc:datafield[@tag=246]/marc:subfield[@code='h']"></xsl:value-of>
13412                                                 </xsl:with-param>
13413                                         </xsl:call-template>
13414                                 </form>
13415                         </xsl:if>
13416                         <xsl:if test="marc:datafield[@tag=730]/marc:subfield[@code='h']">
13417                                 <form authority="gmd">
13418                                         <xsl:call-template name="chopBrackets">
13419                                                 <xsl:with-param name="chopString">
13420                                                         <xsl:value-of select="marc:datafield[@tag=730]/marc:subfield[@code='h']"></xsl:value-of>
13421                                                 </xsl:with-param>
13422                                         </xsl:call-template>
13423                                 </form>
13424                         </xsl:if>
13425                         <xsl:for-each select="marc:datafield[@tag=256]/marc:subfield[@code='a']">
13426                                 <form>
13427                                         <xsl:value-of select="."></xsl:value-of>
13428                                 </form>
13429                         </xsl:for-each>
13430                         <xsl:for-each select="marc:controlfield[@tag=007][substring(text(),1,1)='c']">
13431                                 <xsl:choose>
13432                                         <xsl:when test="substring(text(),14,1)='a'">
13433                                                 <reformattingQuality>access</reformattingQuality>
13434                                         </xsl:when>
13435                                         <xsl:when test="substring(text(),14,1)='p'">
13436                                                 <reformattingQuality>preservation</reformattingQuality>
13437                                         </xsl:when>
13438                                         <xsl:when test="substring(text(),14,1)='r'">
13439                                                 <reformattingQuality>replacement</reformattingQuality>
13440                                         </xsl:when>
13441                                 </xsl:choose>
13442                         </xsl:for-each>
13443                         <!--3.2 change tmee 007/01 -->
13444                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='b']">
13445                                 <form authority="smd">chip cartridge</form>
13446                         </xsl:if>
13447                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='c']">
13448                                 <form authority="smd">computer optical disc cartridge</form>
13449                         </xsl:if>
13450                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='j']">
13451                                 <form authority="smd">magnetic disc</form>
13452                         </xsl:if>
13453                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='m']">
13454                                 <form authority="smd">magneto-optical disc</form>
13455                         </xsl:if>
13456                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='o']">
13457                                 <form authority="smd">optical disc</form>
13458                         </xsl:if>
13459                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='r']">
13460                                 <form authority="smd">remote</form>
13461                         </xsl:if>
13462                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='a']">
13463                                 <form authority="smd">tape cartridge</form>
13464                         </xsl:if>
13465                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='f']">
13466                                 <form authority="smd">tape cassette</form>
13467                         </xsl:if>
13468                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='h']">
13469                                 <form authority="smd">tape reel</form>
13470                         </xsl:if>
13471                         
13472                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='a']">
13473                                 <form authority="smd">celestial globe</form>
13474                         </xsl:if>
13475                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='e']">
13476                                 <form authority="smd">earth moon globe</form>
13477                         </xsl:if>
13478                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='b']">
13479                                 <form authority="smd">planetary or lunar globe</form>
13480                         </xsl:if>
13481                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='c']">
13482                                 <form authority="smd">terrestrial globe</form>
13483                         </xsl:if>
13484                         
13485                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='o'][substring(text(),2,1)='o']">
13486                                 <form authority="smd">kit</form>
13487                         </xsl:if>
13488                         
13489                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
13490                                 <form authority="smd">atlas</form>
13491                         </xsl:if>
13492                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='g']">
13493                                 <form authority="smd">diagram</form>
13494                         </xsl:if>
13495                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
13496                                 <form authority="smd">map</form>
13497                         </xsl:if>
13498                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13499                                 <form authority="smd">model</form>
13500                         </xsl:if>
13501                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='k']">
13502                                 <form authority="smd">profile</form>
13503                         </xsl:if>
13504                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
13505                                 <form authority="smd">remote-sensing image</form>
13506                         </xsl:if>
13507                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='s']">
13508                                 <form authority="smd">section</form>
13509                         </xsl:if>
13510                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='y']">
13511                                 <form authority="smd">view</form>
13512                         </xsl:if>
13513                         
13514                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='a']">
13515                                 <form authority="smd">aperture card</form>
13516                         </xsl:if>
13517                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='e']">
13518                                 <form authority="smd">microfiche</form>
13519                         </xsl:if>
13520                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='f']">
13521                                 <form authority="smd">microfiche cassette</form>
13522                         </xsl:if>
13523                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='b']">
13524                                 <form authority="smd">microfilm cartridge</form>
13525                         </xsl:if>
13526                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='c']">
13527                                 <form authority="smd">microfilm cassette</form>
13528                         </xsl:if>
13529                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='d']">
13530                                 <form authority="smd">microfilm reel</form>
13531                         </xsl:if>
13532                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='g']">
13533                                 <form authority="smd">microopaque</form>
13534                         </xsl:if>
13535                         
13536                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='c']">
13537                                 <form authority="smd">film cartridge</form>
13538                         </xsl:if>
13539                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='f']">
13540                                 <form authority="smd">film cassette</form>
13541                         </xsl:if>
13542                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='r']">
13543                                 <form authority="smd">film reel</form>
13544                         </xsl:if>
13545                         
13546                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='n']">
13547                                 <form authority="smd">chart</form>
13548                         </xsl:if>
13549                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='c']">
13550                                 <form authority="smd">collage</form>
13551                         </xsl:if>
13552                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='d']">
13553                                 <form authority="smd">drawing</form>
13554                         </xsl:if>
13555                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='o']">
13556                                 <form authority="smd">flash card</form>
13557                         </xsl:if>
13558                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='e']">
13559                                 <form authority="smd">painting</form>
13560                         </xsl:if>
13561                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='f']">
13562                                 <form authority="smd">photomechanical print</form>
13563                         </xsl:if>
13564                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='g']">
13565                                 <form authority="smd">photonegative</form>
13566                         </xsl:if>
13567                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='h']">
13568                                 <form authority="smd">photoprint</form>
13569                         </xsl:if>
13570                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='i']">
13571                                 <form authority="smd">picture</form>
13572                         </xsl:if>
13573                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='j']">
13574                                 <form authority="smd">print</form>
13575                         </xsl:if>
13576                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='l']">
13577                                 <form authority="smd">technical drawing</form>
13578                         </xsl:if>
13579                         
13580                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='q'][substring(text(),2,1)='q']">
13581                                 <form authority="smd">notated music</form>
13582                         </xsl:if>
13583                         
13584                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='d']">
13585                                 <form authority="smd">filmslip</form>
13586                         </xsl:if>
13587                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='c']">
13588                                 <form authority="smd">filmstrip cartridge</form>
13589                         </xsl:if>
13590                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='o']">
13591                                 <form authority="smd">filmstrip roll</form>
13592                         </xsl:if>
13593                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='f']">
13594                                 <form authority="smd">other filmstrip type</form>
13595                         </xsl:if>
13596                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='s']">
13597                                 <form authority="smd">slide</form>
13598                         </xsl:if>
13599                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='t']">
13600                                 <form authority="smd">transparency</form>
13601                         </xsl:if>
13602                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='r'][substring(text(),2,1)='r']">
13603                                 <form authority="smd">remote-sensing image</form>
13604                         </xsl:if>
13605                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='e']">
13606                                 <form authority="smd">cylinder</form>
13607                         </xsl:if>
13608                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='q']">
13609                                 <form authority="smd">roll</form>
13610                         </xsl:if>
13611                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='g']">
13612                                 <form authority="smd">sound cartridge</form>
13613                         </xsl:if>
13614                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='s']">
13615                                 <form authority="smd">sound cassette</form>
13616                         </xsl:if>
13617                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='d']">
13618                                 <form authority="smd">sound disc</form>
13619                         </xsl:if>
13620                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='t']">
13621                                 <form authority="smd">sound-tape reel</form>
13622                         </xsl:if>
13623                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='i']">
13624                                 <form authority="smd">sound-track film</form>
13625                         </xsl:if>
13626                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='w']">
13627                                 <form authority="smd">wire recording</form>
13628                         </xsl:if>
13629                         
13630                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='c']">
13631                                 <form authority="smd">braille</form>
13632                         </xsl:if>
13633                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='b']">
13634                                 <form authority="smd">combination</form>
13635                         </xsl:if>
13636                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='a']">
13637                                 <form authority="smd">moon</form>
13638                         </xsl:if>
13639                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='d']">
13640                                 <form authority="smd">tactile, with no writing system</form>
13641                         </xsl:if>
13642                         
13643                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='c']">
13644                                 <form authority="smd">braille</form>
13645                         </xsl:if>
13646                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='b']">
13647                                 <form authority="smd">large print</form>
13648                         </xsl:if>
13649                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='a']">
13650                                 <form authority="smd">regular print</form>
13651                         </xsl:if>
13652                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='d']">
13653                                 <form authority="smd">text in looseleaf binder</form>
13654                         </xsl:if>
13655                         
13656                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='c']">
13657                                 <form authority="smd">videocartridge</form>
13658                         </xsl:if>
13659                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='f']">
13660                                 <form authority="smd">videocassette</form>
13661                         </xsl:if>
13662                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='d']">
13663                                 <form authority="smd">videodisc</form>
13664                         </xsl:if>
13665                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='r']">
13666                                 <form authority="smd">videoreel</form>
13667                         </xsl:if>
13668                         
13669                         <xsl:for-each select="marc:datafield[@tag=856]/marc:subfield[@code='q'][string-length(.)>1]">
13670                                 <internetMediaType>
13671                                         <xsl:value-of select="."></xsl:value-of>
13672                                 </internetMediaType>
13673                         </xsl:for-each>
13674                         <xsl:for-each select="marc:datafield[@tag=300]">
13675                                 <extent>
13676                                         <xsl:call-template name="subfieldSelect">
13677                                                 <xsl:with-param name="codes">abce</xsl:with-param>
13678                                         </xsl:call-template>
13679                                 </extent>
13680                         </xsl:for-each>
13681                 </xsl:variable>
13682                 <xsl:if test="string-length(normalize-space($physicalDescription))">
13683                         <physicalDescription>
13684                                 <xsl:copy-of select="$physicalDescription"></xsl:copy-of>
13685                         </physicalDescription>
13686                 </xsl:if>
13687                 <xsl:for-each select="marc:datafield[@tag=520]">
13688                         <abstract>
13689                                 <xsl:call-template name="uri"></xsl:call-template>
13690                                 <xsl:call-template name="subfieldSelect">
13691                                         <xsl:with-param name="codes">ab</xsl:with-param>
13692                                 </xsl:call-template>
13693                         </abstract>
13694                 </xsl:for-each>
13695                 <xsl:for-each select="marc:datafield[@tag=505]">
13696                         <tableOfContents>
13697                                 <xsl:call-template name="uri"></xsl:call-template>
13698                                 <xsl:call-template name="subfieldSelect">
13699                                         <xsl:with-param name="codes">agrt</xsl:with-param>
13700                                 </xsl:call-template>
13701                         </tableOfContents>
13702                 </xsl:for-each>
13703                 <xsl:for-each select="marc:datafield[@tag=521]">
13704                         <targetAudience>
13705                                 <xsl:call-template name="subfieldSelect">
13706                                         <xsl:with-param name="codes">ab</xsl:with-param>
13707                                 </xsl:call-template>
13708                         </targetAudience>
13709                 </xsl:for-each>
13710                 <xsl:if test="$typeOf008='BK' or $typeOf008='CF' or $typeOf008='MU' or $typeOf008='VM'">
13711                         <xsl:variable name="controlField008-22" select="substring($controlField008,23,1)"></xsl:variable>
13712                         <xsl:choose>
13713                                 <!-- 01/04 fix -->
13714                                 <xsl:when test="$controlField008-22='d'">
13715                                         <targetAudience authority="marctarget">adolescent</targetAudience>
13716                                 </xsl:when>
13717                                 <xsl:when test="$controlField008-22='e'">
13718                                         <targetAudience authority="marctarget">adult</targetAudience>
13719                                 </xsl:when>
13720                                 <xsl:when test="$controlField008-22='g'">
13721                                         <targetAudience authority="marctarget">general</targetAudience>
13722                                 </xsl:when>
13723                                 <xsl:when test="$controlField008-22='b' or $controlField008-22='c' or $controlField008-22='j'">
13724                                         <targetAudience authority="marctarget">juvenile</targetAudience>
13725                                 </xsl:when>
13726                                 <xsl:when test="$controlField008-22='a'">
13727                                         <targetAudience authority="marctarget">preschool</targetAudience>
13728                                 </xsl:when>
13729                                 <xsl:when test="$controlField008-22='f'">
13730                                         <targetAudience authority="marctarget">specialized</targetAudience>
13731                                 </xsl:when>
13732                         </xsl:choose>
13733                 </xsl:if>
13734                 <xsl:for-each select="marc:datafield[@tag=245]/marc:subfield[@code='c']">
13735                         <note type="statement of responsibility">
13736                                 <xsl:value-of select="."></xsl:value-of>
13737                         </note>
13738                 </xsl:for-each>
13739                 <xsl:for-each select="marc:datafield[@tag=500]">
13740                         <note>
13741                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13742                                 <xsl:call-template name="uri"></xsl:call-template>
13743                         </note>
13744                 </xsl:for-each>
13745                 
13746                 <!--3.2 change tmee additional note fields-->
13747                 
13748                 <xsl:for-each select="marc:datafield[@tag=506]">
13749                         <note type="restrictions">
13750                                 <xsl:call-template name="uri"></xsl:call-template>
13751                                 <xsl:variable name="str">
13752                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13753                                                 <xsl:value-of select="."></xsl:value-of>
13754                                                 <xsl:text> </xsl:text>
13755                                         </xsl:for-each>
13756                                 </xsl:variable>
13757                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13758                         </note>
13759                 </xsl:for-each>
13760                 
13761                 <xsl:for-each select="marc:datafield[@tag=510]">
13762                         <note  type="citation/reference">
13763                                 <xsl:call-template name="uri"></xsl:call-template>
13764                                 <xsl:variable name="str">
13765                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13766                                                 <xsl:value-of select="."></xsl:value-of>
13767                                                 <xsl:text> </xsl:text>
13768                                         </xsl:for-each>
13769                                 </xsl:variable>
13770                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13771                         </note>
13772                 </xsl:for-each>
13773                 
13774                         
13775                 <xsl:for-each select="marc:datafield[@tag=511]">
13776                         <note type="performers">
13777                                 <xsl:call-template name="uri"></xsl:call-template>
13778                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13779                         </note>
13780                 </xsl:for-each>
13781                 <xsl:for-each select="marc:datafield[@tag=518]">
13782                         <note type="venue">
13783                                 <xsl:call-template name="uri"></xsl:call-template>
13784                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13785                         </note>
13786                 </xsl:for-each>
13787                 
13788                 <xsl:for-each select="marc:datafield[@tag=530]">
13789                         <note  type="additional physical form">
13790                                 <xsl:call-template name="uri"></xsl:call-template>
13791                                 <xsl:variable name="str">
13792                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13793                                                 <xsl:value-of select="."></xsl:value-of>
13794                                                 <xsl:text> </xsl:text>
13795                                         </xsl:for-each>
13796                                 </xsl:variable>
13797                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13798                         </note>
13799                 </xsl:for-each>
13800                 
13801                 <xsl:for-each select="marc:datafield[@tag=533]">
13802                         <note  type="reproduction">
13803                                 <xsl:call-template name="uri"></xsl:call-template>
13804                                 <xsl:variable name="str">
13805                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13806                                                 <xsl:value-of select="."></xsl:value-of>
13807                                                 <xsl:text> </xsl:text>
13808                                         </xsl:for-each>
13809                                 </xsl:variable>
13810                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13811                         </note>
13812                 </xsl:for-each>
13813                 
13814                 <xsl:for-each select="marc:datafield[@tag=534]">
13815                         <note  type="original version">
13816                                 <xsl:call-template name="uri"></xsl:call-template>
13817                                 <xsl:variable name="str">
13818                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13819                                                 <xsl:value-of select="."></xsl:value-of>
13820                                                 <xsl:text> </xsl:text>
13821                                         </xsl:for-each>
13822                                 </xsl:variable>
13823                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13824                         </note>
13825                 </xsl:for-each>
13826                 
13827                 <xsl:for-each select="marc:datafield[@tag=538]">
13828                         <note  type="system details">
13829                                 <xsl:call-template name="uri"></xsl:call-template>
13830                                 <xsl:variable name="str">
13831                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13832                                                 <xsl:value-of select="."></xsl:value-of>
13833                                                 <xsl:text> </xsl:text>
13834                                         </xsl:for-each>
13835                                 </xsl:variable>
13836                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13837                         </note>
13838                 </xsl:for-each>
13839                 
13840                 <xsl:for-each select="marc:datafield[@tag=583]">
13841                         <note type="action">
13842                                 <xsl:call-template name="uri"></xsl:call-template>
13843                                 <xsl:variable name="str">
13844                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13845                                                 <xsl:value-of select="."></xsl:value-of>
13846                                                 <xsl:text> </xsl:text>
13847                                         </xsl:for-each>
13848                                 </xsl:variable>
13849                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13850                         </note>
13851                 </xsl:for-each>
13852                 
13853
13854                 
13855                 
13856                 
13857                 <xsl:for-each select="marc:datafield[@tag=501 or @tag=502 or @tag=504 or @tag=507 or @tag=508 or  @tag=513 or @tag=514 or @tag=515 or @tag=516 or @tag=522 or @tag=524 or @tag=525 or @tag=526 or @tag=535 or @tag=536 or @tag=540 or @tag=541 or @tag=544 or @tag=545 or @tag=546 or @tag=547 or @tag=550 or @tag=552 or @tag=555 or @tag=556 or @tag=561 or @tag=562 or @tag=565 or @tag=567 or @tag=580 or @tag=581 or @tag=584 or @tag=585 or @tag=586]">
13858                         <note>
13859                                 <xsl:call-template name="uri"></xsl:call-template>
13860                                 <xsl:variable name="str">
13861                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13862                                                 <xsl:value-of select="."></xsl:value-of>
13863                                                 <xsl:text> </xsl:text>
13864                                         </xsl:for-each>
13865                                 </xsl:variable>
13866                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13867                         </note>
13868                 </xsl:for-each>
13869                 <xsl:for-each select="marc:datafield[@tag=034][marc:subfield[@code='d' or @code='e' or @code='f' or @code='g']]">
13870                         <subject>
13871                                 <cartographics>
13872                                         <coordinates>
13873                                                 <xsl:call-template name="subfieldSelect">
13874                                                         <xsl:with-param name="codes">defg</xsl:with-param>
13875                                                 </xsl:call-template>
13876                                         </coordinates>
13877                                 </cartographics>
13878                         </subject>
13879                 </xsl:for-each>
13880                 <xsl:for-each select="marc:datafield[@tag=043]">
13881                         <subject>
13882                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13883                                         <geographicCode>
13884                                                 <xsl:attribute name="authority">
13885                                                         <xsl:if test="@code='a'">
13886                                                                 <xsl:text>marcgac</xsl:text>
13887                                                         </xsl:if>
13888                                                         <xsl:if test="@code='b'">
13889                                                                 <xsl:value-of select="following-sibling::marc:subfield[@code=2]"></xsl:value-of>
13890                                                         </xsl:if>
13891                                                         <xsl:if test="@code='c'">
13892                                                                 <xsl:text>iso3166</xsl:text>
13893                                                         </xsl:if>
13894                                                 </xsl:attribute>
13895                                                 <xsl:value-of select="self::marc:subfield"></xsl:value-of>
13896                                         </geographicCode>
13897                                 </xsl:for-each>
13898                         </subject>
13899                 </xsl:for-each>
13900                 <!-- tmee 2006/11/27 -->
13901                 <xsl:for-each select="marc:datafield[@tag=255]">
13902                         <subject>
13903                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13904                                 <cartographics>
13905                                         <xsl:if test="@code='a'">
13906                                                 <scale>
13907                                                         <xsl:value-of select="."></xsl:value-of>
13908                                                 </scale>
13909                                         </xsl:if>
13910                                         <xsl:if test="@code='b'">
13911                                                 <projection>
13912                                                         <xsl:value-of select="."></xsl:value-of>
13913                                                 </projection>
13914                                         </xsl:if>
13915                                         <xsl:if test="@code='c'">
13916                                                 <coordinates>
13917                                                         <xsl:value-of select="."></xsl:value-of>
13918                                                 </coordinates>
13919                                         </xsl:if>
13920                                 </cartographics>
13921                                 </xsl:for-each>
13922                         </subject>
13923                 </xsl:for-each>
13924                                 
13925                 <xsl:apply-templates select="marc:datafield[653 >= @tag and @tag >= 600]"></xsl:apply-templates>
13926                 <xsl:apply-templates select="marc:datafield[@tag=656]"></xsl:apply-templates>
13927                 <xsl:for-each select="marc:datafield[@tag=752]">
13928                         <subject>
13929                                 <hierarchicalGeographic>
13930                                         <xsl:for-each select="marc:subfield[@code='a']">
13931                                                 <country>
13932                                                         <xsl:call-template name="chopPunctuation">
13933                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13934                                                         </xsl:call-template>
13935                                                 </country>
13936                                         </xsl:for-each>
13937                                         <xsl:for-each select="marc:subfield[@code='b']">
13938                                                 <state>
13939                                                         <xsl:call-template name="chopPunctuation">
13940                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13941                                                         </xsl:call-template>
13942                                                 </state>
13943                                         </xsl:for-each>
13944                                         <xsl:for-each select="marc:subfield[@code='c']">
13945                                                 <county>
13946                                                         <xsl:call-template name="chopPunctuation">
13947                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13948                                                         </xsl:call-template>
13949                                                 </county>
13950                                         </xsl:for-each>
13951                                         <xsl:for-each select="marc:subfield[@code='d']">
13952                                                 <city>
13953                                                         <xsl:call-template name="chopPunctuation">
13954                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13955                                                         </xsl:call-template>
13956                                                 </city>
13957                                         </xsl:for-each>
13958                                 </hierarchicalGeographic>
13959                         </subject>
13960                 </xsl:for-each>
13961                 <xsl:for-each select="marc:datafield[@tag=045][marc:subfield[@code='b']]">
13962                         <subject>
13963                                 <xsl:choose>
13964                                         <xsl:when test="@ind1=2">
13965                                                 <temporal encoding="iso8601" point="start">
13966                                                         <xsl:call-template name="chopPunctuation">
13967                                                                 <xsl:with-param name="chopString">
13968                                                                         <xsl:value-of select="marc:subfield[@code='b'][1]"></xsl:value-of>
13969                                                                 </xsl:with-param>
13970                                                         </xsl:call-template>
13971                                                 </temporal>
13972                                                 <temporal encoding="iso8601" point="end">
13973                                                         <xsl:call-template name="chopPunctuation">
13974                                                                 <xsl:with-param name="chopString">
13975                                                                         <xsl:value-of select="marc:subfield[@code='b'][2]"></xsl:value-of>
13976                                                                 </xsl:with-param>
13977                                                         </xsl:call-template>
13978                                                 </temporal>
13979                                         </xsl:when>
13980                                         <xsl:otherwise>
13981                                                 <xsl:for-each select="marc:subfield[@code='b']">
13982                                                         <temporal encoding="iso8601">
13983                                                                 <xsl:call-template name="chopPunctuation">
13984                                                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
13985                                                                 </xsl:call-template>
13986                                                         </temporal>
13987                                                 </xsl:for-each>
13988                                         </xsl:otherwise>
13989                                 </xsl:choose>
13990                         </subject>
13991                 </xsl:for-each>
13992                 <xsl:for-each select="marc:datafield[@tag=050]">
13993                         <xsl:for-each select="marc:subfield[@code='b']">
13994                                 <classification authority="lcc">
13995                                         <xsl:if test="../marc:subfield[@code='3']">
13996                                                 <xsl:attribute name="displayLabel">
13997                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
13998                                                 </xsl:attribute>
13999                                         </xsl:if>
14000                                         <xsl:value-of select="preceding-sibling::marc:subfield[@code='a'][1]"></xsl:value-of>
14001                                         <xsl:text> </xsl:text>
14002                                         <xsl:value-of select="text()"></xsl:value-of>
14003                                 </classification>
14004                         </xsl:for-each>
14005                         <xsl:for-each select="marc:subfield[@code='a'][not(following-sibling::marc:subfield[@code='b'])]">
14006                                 <classification authority="lcc">
14007                                         <xsl:if test="../marc:subfield[@code='3']">
14008                                                 <xsl:attribute name="displayLabel">
14009                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
14010                                                 </xsl:attribute>
14011                                         </xsl:if>
14012                                         <xsl:value-of select="text()"></xsl:value-of>
14013                                 </classification>
14014                         </xsl:for-each>
14015                 </xsl:for-each>
14016                 <xsl:for-each select="marc:datafield[@tag=082]">
14017                         <classification authority="ddc">
14018                                 <xsl:if test="marc:subfield[@code='2']">
14019                                         <xsl:attribute name="edition">
14020                                                 <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14021                                         </xsl:attribute>
14022                                 </xsl:if>
14023                                 <xsl:call-template name="subfieldSelect">
14024                                         <xsl:with-param name="codes">ab</xsl:with-param>
14025                                 </xsl:call-template>
14026                         </classification>
14027                 </xsl:for-each>
14028                 <xsl:for-each select="marc:datafield[@tag=080]">
14029                         <classification authority="udc">
14030                                 <xsl:call-template name="subfieldSelect">
14031                                         <xsl:with-param name="codes">abx</xsl:with-param>
14032                                 </xsl:call-template>
14033                         </classification>
14034                 </xsl:for-each>
14035                 <xsl:for-each select="marc:datafield[@tag=060]">
14036                         <classification authority="nlm">
14037                                 <xsl:call-template name="subfieldSelect">
14038                                         <xsl:with-param name="codes">ab</xsl:with-param>
14039                                 </xsl:call-template>
14040                         </classification>
14041                 </xsl:for-each>
14042                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=0]">
14043                         <classification authority="sudocs">
14044                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14045                         </classification>
14046                 </xsl:for-each>
14047                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=1]">
14048                         <classification authority="candoc">
14049                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14050                         </classification>
14051                 </xsl:for-each>
14052                 <xsl:for-each select="marc:datafield[@tag=086]">
14053                         <classification>
14054                                 <xsl:attribute name="authority">
14055                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14056                                 </xsl:attribute>
14057                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14058                         </classification>
14059                 </xsl:for-each>
14060                 <xsl:for-each select="marc:datafield[@tag=084]">
14061                         <classification>
14062                                 <xsl:attribute name="authority">
14063                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14064                                 </xsl:attribute>
14065                                 <xsl:call-template name="subfieldSelect">
14066                                         <xsl:with-param name="codes">ab</xsl:with-param>
14067                                 </xsl:call-template>
14068                         </classification>
14069                 </xsl:for-each>
14070                 <xsl:for-each select="marc:datafield[@tag=440]">
14071                         <relatedItem type="series">
14072                                 <titleInfo>
14073                                         <title>
14074                                                 <xsl:call-template name="chopPunctuation">
14075                                                         <xsl:with-param name="chopString">
14076                                                                 <xsl:call-template name="subfieldSelect">
14077                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14078                                                                 </xsl:call-template>
14079                                                         </xsl:with-param>
14080                                                 </xsl:call-template>
14081                                         </title>
14082                                         <xsl:call-template name="part"></xsl:call-template>
14083                                 </titleInfo>
14084                         </relatedItem>
14085                 </xsl:for-each>
14086                 <xsl:for-each select="marc:datafield[@tag=490][@ind1=0]">
14087                         <relatedItem type="series">
14088                                 <titleInfo>
14089                                         <title>
14090                                                 <xsl:call-template name="chopPunctuation">
14091                                                         <xsl:with-param name="chopString">
14092                                                                 <xsl:call-template name="subfieldSelect">
14093                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14094                                                                 </xsl:call-template>
14095                                                         </xsl:with-param>
14096                                                 </xsl:call-template>
14097                                         </title>
14098                                         <xsl:call-template name="part"></xsl:call-template>
14099                                 </titleInfo>
14100                         </relatedItem>
14101                 </xsl:for-each>
14102                 <xsl:for-each select="marc:datafield[@tag=510]">
14103                         <relatedItem type="isReferencedBy">
14104                                 <note>
14105                                         <xsl:call-template name="subfieldSelect">
14106                                                 <xsl:with-param name="codes">abcx3</xsl:with-param>
14107                                         </xsl:call-template>
14108                                 </note>
14109                         </relatedItem>
14110                 </xsl:for-each>
14111                 <xsl:for-each select="marc:datafield[@tag=534]">
14112                         <relatedItem type="original">
14113                                 <xsl:call-template name="relatedTitle"></xsl:call-template>
14114                                 <xsl:call-template name="relatedName"></xsl:call-template>
14115                                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
14116                                         <originInfo>
14117                                                 <xsl:for-each select="marc:subfield[@code='c']">
14118                                                         <publisher>
14119                                                                 <xsl:value-of select="."></xsl:value-of>
14120                                                         </publisher>
14121                                                 </xsl:for-each>
14122                                                 <xsl:for-each select="marc:subfield[@code='b']">
14123                                                         <edition>
14124                                                                 <xsl:value-of select="."></xsl:value-of>
14125                                                         </edition>
14126                                                 </xsl:for-each>
14127                                         </originInfo>
14128                                 </xsl:if>
14129                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14130                                 <xsl:for-each select="marc:subfield[@code='z']">
14131                                         <identifier type="isbn">
14132                                                 <xsl:value-of select="."></xsl:value-of>
14133                                         </identifier>
14134                                 </xsl:for-each>
14135                                 <xsl:call-template name="relatedNote"></xsl:call-template>
14136                         </relatedItem>
14137                 </xsl:for-each>
14138                 <xsl:for-each select="marc:datafield[@tag=700][marc:subfield[@code='t']]">
14139                         <relatedItem>
14140                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14141                                 <titleInfo>
14142                                         <title>
14143                                                 <xsl:call-template name="chopPunctuation">
14144                                                         <xsl:with-param name="chopString">
14145                                                                 <xsl:call-template name="specialSubfieldSelect">
14146                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14147                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14148                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14149                                                                 </xsl:call-template>
14150                                                         </xsl:with-param>
14151                                                 </xsl:call-template>
14152                                         </title>
14153                                         <xsl:call-template name="part"></xsl:call-template>
14154                                 </titleInfo>
14155                                 <name type="personal">
14156                                         <namePart>
14157                                                 <xsl:call-template name="specialSubfieldSelect">
14158                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14159                                                         <xsl:with-param name="axis">t</xsl:with-param>
14160                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14161                                                 </xsl:call-template>
14162                                         </namePart>
14163                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14164                                         <xsl:call-template name="nameDate"></xsl:call-template>
14165                                         <xsl:call-template name="role"></xsl:call-template>
14166                                 </name>
14167                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14168                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14169                         </relatedItem>
14170                 </xsl:for-each>
14171                 <xsl:for-each select="marc:datafield[@tag=710][marc:subfield[@code='t']]">
14172                         <relatedItem>
14173                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14174                                 <titleInfo>
14175                                         <title>
14176                                                 <xsl:call-template name="chopPunctuation">
14177                                                         <xsl:with-param name="chopString">
14178                                                                 <xsl:call-template name="specialSubfieldSelect">
14179                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14180                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14181                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14182                                                                 </xsl:call-template>
14183                                                         </xsl:with-param>
14184                                                 </xsl:call-template>
14185                                         </title>
14186                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14187                                 </titleInfo>
14188                                 <name type="corporate">
14189                                         <xsl:for-each select="marc:subfield[@code='a']">
14190                                                 <namePart>
14191                                                         <xsl:value-of select="."></xsl:value-of>
14192                                                 </namePart>
14193                                         </xsl:for-each>
14194                                         <xsl:for-each select="marc:subfield[@code='b']">
14195                                                 <namePart>
14196                                                         <xsl:value-of select="."></xsl:value-of>
14197                                                 </namePart>
14198                                         </xsl:for-each>
14199                                         <xsl:variable name="tempNamePart">
14200                                                 <xsl:call-template name="specialSubfieldSelect">
14201                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14202                                                         <xsl:with-param name="axis">t</xsl:with-param>
14203                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14204                                                 </xsl:call-template>
14205                                         </xsl:variable>
14206                                         <xsl:if test="normalize-space($tempNamePart)">
14207                                                 <namePart>
14208                                                         <xsl:value-of select="$tempNamePart"></xsl:value-of>
14209                                                 </namePart>
14210                                         </xsl:if>
14211                                         <xsl:call-template name="role"></xsl:call-template>
14212                                 </name>
14213                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14214                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14215                         </relatedItem>
14216                 </xsl:for-each>
14217                 <xsl:for-each select="marc:datafield[@tag=711][marc:subfield[@code='t']]">
14218                         <relatedItem>
14219                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14220                                 <titleInfo>
14221                                         <title>
14222                                                 <xsl:call-template name="chopPunctuation">
14223                                                         <xsl:with-param name="chopString">
14224                                                                 <xsl:call-template name="specialSubfieldSelect">
14225                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14226                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14227                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14228                                                                 </xsl:call-template>
14229                                                         </xsl:with-param>
14230                                                 </xsl:call-template>
14231                                         </title>
14232                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14233                                 </titleInfo>
14234                                 <name type="conference">
14235                                         <namePart>
14236                                                 <xsl:call-template name="specialSubfieldSelect">
14237                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14238                                                         <xsl:with-param name="axis">t</xsl:with-param>
14239                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14240                                                 </xsl:call-template>
14241                                         </namePart>
14242                                 </name>
14243                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14244                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14245                         </relatedItem>
14246                 </xsl:for-each>
14247                 <xsl:for-each select="marc:datafield[@tag=730][@ind2=2]">
14248                         <relatedItem>
14249                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14250                                 <titleInfo>
14251                                         <title>
14252                                                 <xsl:call-template name="chopPunctuation">
14253                                                         <xsl:with-param name="chopString">
14254                                                                 <xsl:call-template name="subfieldSelect">
14255                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14256                                                                 </xsl:call-template>
14257                                                         </xsl:with-param>
14258                                                 </xsl:call-template>
14259                                         </title>
14260                                         <xsl:call-template name="part"></xsl:call-template>
14261                                 </titleInfo>
14262                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14263                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14264                         </relatedItem>
14265                 </xsl:for-each>
14266                 <xsl:for-each select="marc:datafield[@tag=740][@ind2=2]">
14267                         <relatedItem>
14268                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14269                                 <titleInfo>
14270                                         <title>
14271                                                 <xsl:call-template name="chopPunctuation">
14272                                                         <xsl:with-param name="chopString">
14273                                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14274                                                         </xsl:with-param>
14275                                                 </xsl:call-template>
14276                                         </title>
14277                                         <xsl:call-template name="part"></xsl:call-template>
14278                                 </titleInfo>
14279                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14280                         </relatedItem>
14281                 </xsl:for-each>
14282                 <xsl:for-each select="marc:datafield[@tag=760]|marc:datafield[@tag=762]">
14283                         <relatedItem type="series">
14284                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14285                         </relatedItem>
14286                 </xsl:for-each>
14287                 <xsl:for-each select="marc:datafield[@tag=765]|marc:datafield[@tag=767]|marc:datafield[@tag=777]|marc:datafield[@tag=787]">
14288                         <relatedItem>
14289                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14290                         </relatedItem>
14291                 </xsl:for-each>
14292                 <xsl:for-each select="marc:datafield[@tag=775]">
14293                         <relatedItem type="otherVersion">
14294                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14295                         </relatedItem>
14296                 </xsl:for-each>
14297                 <xsl:for-each select="marc:datafield[@tag=770]|marc:datafield[@tag=774]">
14298                         <relatedItem type="constituent">
14299                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14300                         </relatedItem>
14301                 </xsl:for-each>
14302                 <xsl:for-each select="marc:datafield[@tag=772]|marc:datafield[@tag=773]">
14303                         <relatedItem type="host">
14304                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14305                         </relatedItem>
14306                 </xsl:for-each>
14307                 <xsl:for-each select="marc:datafield[@tag=776]">
14308                         <relatedItem type="otherFormat">
14309                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14310                         </relatedItem>
14311                 </xsl:for-each>
14312                 <xsl:for-each select="marc:datafield[@tag=780]">
14313                         <relatedItem type="preceding">
14314                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14315                         </relatedItem>
14316                 </xsl:for-each>
14317                 <xsl:for-each select="marc:datafield[@tag=785]">
14318                         <relatedItem type="succeeding">
14319                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14320                         </relatedItem>
14321                 </xsl:for-each>
14322                 <xsl:for-each select="marc:datafield[@tag=786]">
14323                         <relatedItem type="original">
14324                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14325                         </relatedItem>
14326                 </xsl:for-each>
14327                 <xsl:for-each select="marc:datafield[@tag=800]">
14328                         <relatedItem type="series">
14329                                 <titleInfo>
14330                                         <title>
14331                                                 <xsl:call-template name="chopPunctuation">
14332                                                         <xsl:with-param name="chopString">
14333                                                                 <xsl:call-template name="specialSubfieldSelect">
14334                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14335                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14336                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14337                                                                 </xsl:call-template>
14338                                                         </xsl:with-param>
14339                                                 </xsl:call-template>
14340                                         </title>
14341                                         <xsl:call-template name="part"></xsl:call-template>
14342                                 </titleInfo>
14343                                 <name type="personal">
14344                                         <namePart>
14345                                                 <xsl:call-template name="chopPunctuation">
14346                                                         <xsl:with-param name="chopString">
14347                                                                 <xsl:call-template name="specialSubfieldSelect">
14348                                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14349                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14350                                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14351                                                                 </xsl:call-template>
14352                                                         </xsl:with-param>
14353                                                 </xsl:call-template>
14354                                         </namePart>
14355                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14356                                         <xsl:call-template name="nameDate"></xsl:call-template>
14357                                         <xsl:call-template name="role"></xsl:call-template>
14358                                 </name>
14359                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14360                         </relatedItem>
14361                 </xsl:for-each>
14362                 <xsl:for-each select="marc:datafield[@tag=810]">
14363                         <relatedItem type="series">
14364                                 <titleInfo>
14365                                         <title>
14366                                                 <xsl:call-template name="chopPunctuation">
14367                                                         <xsl:with-param name="chopString">
14368                                                                 <xsl:call-template name="specialSubfieldSelect">
14369                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14370                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14371                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14372                                                                 </xsl:call-template>
14373                                                         </xsl:with-param>
14374                                                 </xsl:call-template>
14375                                         </title>
14376                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14377                                 </titleInfo>
14378                                 <name type="corporate">
14379                                         <xsl:for-each select="marc:subfield[@code='a']">
14380                                                 <namePart>
14381                                                         <xsl:value-of select="."></xsl:value-of>
14382                                                 </namePart>
14383                                         </xsl:for-each>
14384                                         <xsl:for-each select="marc:subfield[@code='b']">
14385                                                 <namePart>
14386                                                         <xsl:value-of select="."></xsl:value-of>
14387                                                 </namePart>
14388                                         </xsl:for-each>
14389                                         <namePart>
14390                                                 <xsl:call-template name="specialSubfieldSelect">
14391                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14392                                                         <xsl:with-param name="axis">t</xsl:with-param>
14393                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14394                                                 </xsl:call-template>
14395                                         </namePart>
14396                                         <xsl:call-template name="role"></xsl:call-template>
14397                                 </name>
14398                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14399                         </relatedItem>
14400                 </xsl:for-each>
14401                 <xsl:for-each select="marc:datafield[@tag=811]">
14402                         <relatedItem type="series">
14403                                 <titleInfo>
14404                                         <title>
14405                                                 <xsl:call-template name="chopPunctuation">
14406                                                         <xsl:with-param name="chopString">
14407                                                                 <xsl:call-template name="specialSubfieldSelect">
14408                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14409                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14410                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14411                                                                 </xsl:call-template>
14412                                                         </xsl:with-param>
14413                                                 </xsl:call-template>
14414                                         </title>
14415                                         <xsl:call-template name="relatedPartNumName"/>
14416                                 </titleInfo>
14417                                 <name type="conference">
14418                                         <namePart>
14419                                                 <xsl:call-template name="specialSubfieldSelect">
14420                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14421                                                         <xsl:with-param name="axis">t</xsl:with-param>
14422                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14423                                                 </xsl:call-template>
14424                                         </namePart>
14425                                         <xsl:call-template name="role"/>
14426                                 </name>
14427                                 <xsl:call-template name="relatedForm"/>
14428                         </relatedItem>
14429                 </xsl:for-each>
14430                 <xsl:for-each select="marc:datafield[@tag='830']">
14431                         <relatedItem type="series">
14432                                 <titleInfo>
14433                                         <title>
14434                                                 <xsl:call-template name="chopPunctuation">
14435                                                         <xsl:with-param name="chopString">
14436                                                                 <xsl:call-template name="subfieldSelect">
14437                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14438                                                                 </xsl:call-template>
14439                                                         </xsl:with-param>
14440                                                 </xsl:call-template>
14441                                         </title>
14442                                         <xsl:call-template name="part"/>
14443                                 </titleInfo>
14444                                 <xsl:call-template name="relatedForm"/>
14445                         </relatedItem>
14446                 </xsl:for-each>
14447                 <xsl:for-each select="marc:datafield[@tag='856'][@ind2='2']/marc:subfield[@code='q']">
14448                         <relatedItem>
14449                                 <internetMediaType>
14450                                         <xsl:value-of select="."/>
14451                                 </internetMediaType>
14452                         </relatedItem>
14453                 </xsl:for-each>
14454                 <xsl:for-each select="marc:datafield[@tag='020']">
14455                         <xsl:call-template name="isInvalid">
14456                                 <xsl:with-param name="type">isbn</xsl:with-param>
14457                         </xsl:call-template>
14458                         <xsl:if test="marc:subfield[@code='a']">
14459                                 <identifier type="isbn">
14460                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14461                                 </identifier>
14462                         </xsl:if>
14463                 </xsl:for-each>
14464                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='0']">
14465                         <xsl:call-template name="isInvalid">
14466                                 <xsl:with-param name="type">isrc</xsl:with-param>
14467                         </xsl:call-template>
14468                         <xsl:if test="marc:subfield[@code='a']">
14469                                 <identifier type="isrc">
14470                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14471                                 </identifier>
14472                         </xsl:if>
14473                 </xsl:for-each>
14474                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='2']">
14475                         <xsl:call-template name="isInvalid">
14476                                 <xsl:with-param name="type">ismn</xsl:with-param>
14477                         </xsl:call-template>
14478                         <xsl:if test="marc:subfield[@code='a']">
14479                                 <identifier type="ismn">
14480                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14481                                 </identifier>
14482                         </xsl:if>
14483                 </xsl:for-each>
14484                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='4']">
14485                         <xsl:call-template name="isInvalid">
14486                                 <xsl:with-param name="type">sici</xsl:with-param>
14487                         </xsl:call-template>
14488                         <identifier type="sici">
14489                                 <xsl:call-template name="subfieldSelect">
14490                                         <xsl:with-param name="codes">ab</xsl:with-param>
14491                                 </xsl:call-template>
14492                         </identifier>
14493                 </xsl:for-each>
14494                 <xsl:for-each select="marc:datafield[@tag='022']">
14495                         <xsl:call-template name="isInvalid">
14496                                 <xsl:with-param name="type">issn</xsl:with-param>
14497                         </xsl:call-template>
14498                         <identifier type="issn">
14499                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14500                         </identifier>
14501                 </xsl:for-each>
14502                 <xsl:for-each select="marc:datafield[@tag='010']">
14503                         <xsl:call-template name="isInvalid">
14504                                 <xsl:with-param name="type">lccn</xsl:with-param>
14505                         </xsl:call-template>
14506                         <identifier type="lccn">
14507                                 <xsl:value-of select="normalize-space(marc:subfield[@code='a'])"/>
14508                         </identifier>
14509                 </xsl:for-each>
14510                 <xsl:for-each select="marc:datafield[@tag='028']">
14511                         <identifier>
14512                                 <xsl:attribute name="type">
14513                                         <xsl:choose>
14514                                                 <xsl:when test="@ind1='0'">issue number</xsl:when>
14515                                                 <xsl:when test="@ind1='1'">matrix number</xsl:when>
14516                                                 <xsl:when test="@ind1='2'">music plate</xsl:when>
14517                                                 <xsl:when test="@ind1='3'">music publisher</xsl:when>
14518                                                 <xsl:when test="@ind1='4'">videorecording identifier</xsl:when>
14519                                         </xsl:choose>
14520                                 </xsl:attribute>
14521                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 028 -->
14522                                 <xsl:call-template name="subfieldSelect">
14523                                         <xsl:with-param name="codes">
14524                                                 <xsl:choose>
14525                                                         <xsl:when test="@ind1='0'">ba</xsl:when>
14526                                                         <xsl:otherwise>ab</xsl:otherwise>
14527                                                 </xsl:choose>
14528                                         </xsl:with-param>
14529                                 </xsl:call-template>
14530                         </identifier>
14531                 </xsl:for-each>
14532                 <xsl:for-each select="marc:datafield[@tag='037']">
14533                         <identifier type="stock number">
14534                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 037 -->
14535                                 <xsl:call-template name="subfieldSelect">
14536                                         <xsl:with-param name="codes">ab</xsl:with-param>
14537                                 </xsl:call-template>
14538                         </identifier>
14539                 </xsl:for-each>
14540                 <xsl:for-each select="marc:datafield[@tag='856'][marc:subfield[@code='u']]">
14541                         <identifier>
14542                                 <xsl:attribute name="type">
14543                                         <xsl:choose>
14544                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:doi') or starts-with(marc:subfield[@code='u'],'doi')">doi</xsl:when>
14545                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl') or starts-with(marc:subfield[@code='u'],'http://hdl.loc.gov')">hdl</xsl:when>
14546                                                 <xsl:otherwise>uri</xsl:otherwise>
14547                                         </xsl:choose>
14548                                 </xsl:attribute>
14549                                 <xsl:choose>
14550                                         <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl') or starts-with(marc:subfield[@code='u'],'http://hdl.loc.gov') ">
14551                                                 <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14552                                         </xsl:when>
14553                                         <xsl:otherwise>
14554                                                 <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14555                                         </xsl:otherwise>
14556                                 </xsl:choose>
14557                         </identifier>
14558                         <xsl:if test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl')">
14559                                 <identifier type="hdl">
14560                                         <xsl:if test="marc:subfield[@code='y' or @code='3' or @code='z']">
14561                                                 <xsl:attribute name="displayLabel">
14562                                                         <xsl:call-template name="subfieldSelect">
14563                                                                 <xsl:with-param name="codes">y3z</xsl:with-param>
14564                                                         </xsl:call-template>
14565                                                 </xsl:attribute>
14566                                         </xsl:if>
14567                                         <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14568                                 </identifier>
14569                         </xsl:if>
14570                 </xsl:for-each>
14571                 <xsl:for-each select="marc:datafield[@tag=024][@ind1=1]">
14572                         <identifier type="upc">
14573                                 <xsl:call-template name="isInvalid"/>
14574                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14575                         </identifier>
14576                 </xsl:for-each>
14577                 <!-- 1/04 fix added $y -->
14578                 <xsl:for-each select="marc:datafield[@tag=856][marc:subfield[@code='u']]">
14579                         <location>
14580                                 <url>
14581                                         <xsl:if test="marc:subfield[@code='y' or @code='3']">
14582                                                 <xsl:attribute name="displayLabel">
14583                                                         <xsl:call-template name="subfieldSelect">
14584                                                                 <xsl:with-param name="codes">y3</xsl:with-param>
14585                                                         </xsl:call-template>
14586                                                 </xsl:attribute>
14587                                         </xsl:if>
14588                                         <xsl:if test="marc:subfield[@code='z' ]">
14589                                                 <xsl:attribute name="note">
14590                                                         <xsl:call-template name="subfieldSelect">
14591                                                                 <xsl:with-param name="codes">z</xsl:with-param>
14592                                                         </xsl:call-template>
14593                                                 </xsl:attribute>
14594                                         </xsl:if>
14595                                         <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14596
14597                                 </url>
14598                         </location>
14599                 </xsl:for-each>
14600                         
14601                         <!-- 3.2 change tmee 856z  -->
14602
14603                 
14604                 <xsl:for-each select="marc:datafield[@tag=852]">
14605                         <location>
14606                                 <physicalLocation>
14607                                         <xsl:call-template name="displayLabel"></xsl:call-template>
14608                                         <xsl:call-template name="subfieldSelect">
14609                                                 <xsl:with-param name="codes">abje</xsl:with-param>
14610                                         </xsl:call-template>
14611                                 </physicalLocation>
14612                         </location>
14613                 </xsl:for-each>
14614                 <xsl:for-each select="marc:datafield[@tag=506]">
14615                         <accessCondition type="restrictionOnAccess">
14616                                 <xsl:call-template name="subfieldSelect">
14617                                         <xsl:with-param name="codes">abcd35</xsl:with-param>
14618                                 </xsl:call-template>
14619                         </accessCondition>
14620                 </xsl:for-each>
14621                 <xsl:for-each select="marc:datafield[@tag=540]">
14622                         <accessCondition type="useAndReproduction">
14623                                 <xsl:call-template name="subfieldSelect">
14624                                         <xsl:with-param name="codes">abcde35</xsl:with-param>
14625                                 </xsl:call-template>
14626                         </accessCondition>
14627                 </xsl:for-each>
14628                 <recordInfo>
14629                         <xsl:for-each select="marc:datafield[@tag=040]">
14630                                 <recordContentSource authority="marcorg">
14631                                         <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14632                                 </recordContentSource>
14633                         </xsl:for-each>
14634                         <xsl:for-each select="marc:controlfield[@tag=008]">
14635                                 <recordCreationDate encoding="marc">
14636                                         <xsl:value-of select="substring(.,1,6)"></xsl:value-of>
14637                                 </recordCreationDate>
14638                         </xsl:for-each>
14639                         <xsl:for-each select="marc:controlfield[@tag=005]">
14640                                 <recordChangeDate encoding="iso8601">
14641                                         <xsl:value-of select="."></xsl:value-of>
14642                                 </recordChangeDate>
14643                         </xsl:for-each>
14644                         <xsl:for-each select="marc:controlfield[@tag=001]">
14645                                 <recordIdentifier>
14646                                         <xsl:if test="../marc:controlfield[@tag=003]">
14647                                                 <xsl:attribute name="source">
14648                                                         <xsl:value-of select="../marc:controlfield[@tag=003]"></xsl:value-of>
14649                                                 </xsl:attribute>
14650                                         </xsl:if>
14651                                         <xsl:value-of select="."></xsl:value-of>
14652                                 </recordIdentifier>
14653                         </xsl:for-each>
14654                         <xsl:for-each select="marc:datafield[@tag=040]/marc:subfield[@code='b']">
14655                                 <languageOfCataloging>
14656                                         <languageTerm authority="iso639-2b" type="code">
14657                                                 <xsl:value-of select="."></xsl:value-of>
14658                                         </languageTerm>
14659                                 </languageOfCataloging>
14660                         </xsl:for-each>
14661                 </recordInfo>
14662         </xsl:template>
14663         <xsl:template name="displayForm">
14664                 <xsl:for-each select="marc:subfield[@code='c']">
14665                         <displayForm>
14666                                 <xsl:value-of select="."></xsl:value-of>
14667                         </displayForm>
14668                 </xsl:for-each>
14669         </xsl:template>
14670         <xsl:template name="affiliation">
14671                 <xsl:for-each select="marc:subfield[@code='u']">
14672                         <affiliation>
14673                                 <xsl:value-of select="."></xsl:value-of>
14674                         </affiliation>
14675                 </xsl:for-each>
14676         </xsl:template>
14677         <xsl:template name="uri">
14678                 <xsl:for-each select="marc:subfield[@code='u']">
14679                         <xsl:attribute name="xlink:href">
14680                                 <xsl:value-of select="."></xsl:value-of>
14681                         </xsl:attribute>
14682                 </xsl:for-each>
14683         </xsl:template>
14684         <xsl:template name="role">
14685                 <xsl:for-each select="marc:subfield[@code='e']">
14686                         <role>
14687                                 <roleTerm type="text">
14688                                         <xsl:value-of select="."></xsl:value-of>
14689                                 </roleTerm>
14690                         </role>
14691                 </xsl:for-each>
14692                 <xsl:for-each select="marc:subfield[@code='4']">
14693                         <role>
14694                                 <roleTerm authority="marcrelator" type="code">
14695                                         <xsl:value-of select="."></xsl:value-of>
14696                                 </roleTerm>
14697                         </role>
14698                 </xsl:for-each>
14699         </xsl:template>
14700         <xsl:template name="part">
14701                 <xsl:variable name="partNumber">
14702                         <xsl:call-template name="specialSubfieldSelect">
14703                                 <xsl:with-param name="axis">n</xsl:with-param>
14704                                 <xsl:with-param name="anyCodes">n</xsl:with-param>
14705                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14706                         </xsl:call-template>
14707                 </xsl:variable>
14708                 <xsl:variable name="partName">
14709                         <xsl:call-template name="specialSubfieldSelect">
14710                                 <xsl:with-param name="axis">p</xsl:with-param>
14711                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14712                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14713                         </xsl:call-template>
14714                 </xsl:variable>
14715                 <xsl:if test="string-length(normalize-space($partNumber))">
14716                         <partNumber>
14717                                 <xsl:call-template name="chopPunctuation">
14718                                         <xsl:with-param name="chopString" select="$partNumber"></xsl:with-param>
14719                                 </xsl:call-template>
14720                         </partNumber>
14721                 </xsl:if>
14722                 <xsl:if test="string-length(normalize-space($partName))">
14723                         <partName>
14724                                 <xsl:call-template name="chopPunctuation">
14725                                         <xsl:with-param name="chopString" select="$partName"></xsl:with-param>
14726                                 </xsl:call-template>
14727                         </partName>
14728                 </xsl:if>
14729         </xsl:template>
14730         <xsl:template name="relatedPart">
14731                 <xsl:if test="@tag=773">
14732                         <xsl:for-each select="marc:subfield[@code='g']">
14733                                 <part>
14734                                         <text>
14735                                                 <xsl:value-of select="."></xsl:value-of>
14736                                         </text>
14737                                 </part>
14738                         </xsl:for-each>
14739                         <xsl:for-each select="marc:subfield[@code='q']">
14740                                 <part>
14741                                         <xsl:call-template name="parsePart"></xsl:call-template>
14742                                 </part>
14743                         </xsl:for-each>
14744                 </xsl:if>
14745         </xsl:template>
14746         <xsl:template name="relatedPartNumName">
14747                 <xsl:variable name="partNumber">
14748                         <xsl:call-template name="specialSubfieldSelect">
14749                                 <xsl:with-param name="axis">g</xsl:with-param>
14750                                 <xsl:with-param name="anyCodes">g</xsl:with-param>
14751                                 <xsl:with-param name="afterCodes">pst</xsl:with-param>
14752                         </xsl:call-template>
14753                 </xsl:variable>
14754                 <xsl:variable name="partName">
14755                         <xsl:call-template name="specialSubfieldSelect">
14756                                 <xsl:with-param name="axis">p</xsl:with-param>
14757                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14758                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14759                         </xsl:call-template>
14760                 </xsl:variable>
14761                 <xsl:if test="string-length(normalize-space($partNumber))">
14762                         <partNumber>
14763                                 <xsl:value-of select="$partNumber"></xsl:value-of>
14764                         </partNumber>
14765                 </xsl:if>
14766                 <xsl:if test="string-length(normalize-space($partName))">
14767                         <partName>
14768                                 <xsl:value-of select="$partName"></xsl:value-of>
14769                         </partName>
14770                 </xsl:if>
14771         </xsl:template>
14772         <xsl:template name="relatedName">
14773                 <xsl:for-each select="marc:subfield[@code='a']">
14774                         <name>
14775                                 <namePart>
14776                                         <xsl:value-of select="."></xsl:value-of>
14777                                 </namePart>
14778                         </name>
14779                 </xsl:for-each>
14780         </xsl:template>
14781         <xsl:template name="relatedForm">
14782                 <xsl:for-each select="marc:subfield[@code='h']">
14783                         <physicalDescription>
14784                                 <form>
14785                                         <xsl:value-of select="."></xsl:value-of>
14786                                 </form>
14787                         </physicalDescription>
14788                 </xsl:for-each>
14789         </xsl:template>
14790         <xsl:template name="relatedExtent">
14791                 <xsl:for-each select="marc:subfield[@code='h']">
14792                         <physicalDescription>
14793                                 <extent>
14794                                         <xsl:value-of select="."></xsl:value-of>
14795                                 </extent>
14796                         </physicalDescription>
14797                 </xsl:for-each>
14798         </xsl:template>
14799         <xsl:template name="relatedNote">
14800                 <xsl:for-each select="marc:subfield[@code='n']">
14801                         <note>
14802                                 <xsl:value-of select="."></xsl:value-of>
14803                         </note>
14804                 </xsl:for-each>
14805         </xsl:template>
14806         <xsl:template name="relatedSubject">
14807                 <xsl:for-each select="marc:subfield[@code='j']">
14808                         <subject>
14809                                 <temporal encoding="iso8601">
14810                                         <xsl:call-template name="chopPunctuation">
14811                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14812                                         </xsl:call-template>
14813                                 </temporal>
14814                         </subject>
14815                 </xsl:for-each>
14816         </xsl:template>
14817         <xsl:template name="relatedIdentifierISSN">
14818                 <xsl:for-each select="marc:subfield[@code='x']">
14819                         <identifier type="issn">
14820                                 <xsl:value-of select="."></xsl:value-of>
14821                         </identifier>
14822                 </xsl:for-each>
14823         </xsl:template>
14824         <xsl:template name="relatedIdentifierLocal">
14825                 <xsl:for-each select="marc:subfield[@code='w']">
14826                         <identifier type="local">
14827                                 <xsl:value-of select="."></xsl:value-of>
14828                         </identifier>
14829                 </xsl:for-each>
14830         </xsl:template>
14831         <xsl:template name="relatedIdentifier">
14832                 <xsl:for-each select="marc:subfield[@code='o']">
14833                         <identifier>
14834                                 <xsl:value-of select="."></xsl:value-of>
14835                         </identifier>
14836                 </xsl:for-each>
14837         </xsl:template>
14838         <xsl:template name="relatedItem76X-78X">
14839                 <xsl:call-template name="displayLabel"></xsl:call-template>
14840                 <xsl:call-template name="relatedTitle76X-78X"></xsl:call-template>
14841                 <xsl:call-template name="relatedName"></xsl:call-template>
14842                 <xsl:call-template name="relatedOriginInfo"></xsl:call-template>
14843                 <xsl:call-template name="relatedLanguage"></xsl:call-template>
14844                 <xsl:call-template name="relatedExtent"></xsl:call-template>
14845                 <xsl:call-template name="relatedNote"></xsl:call-template>
14846                 <xsl:call-template name="relatedSubject"></xsl:call-template>
14847                 <xsl:call-template name="relatedIdentifier"></xsl:call-template>
14848                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14849                 <xsl:call-template name="relatedIdentifierLocal"></xsl:call-template>
14850                 <xsl:call-template name="relatedPart"></xsl:call-template>
14851         </xsl:template>
14852         <xsl:template name="subjectGeographicZ">
14853                 <geographic>
14854                         <xsl:call-template name="chopPunctuation">
14855                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14856                         </xsl:call-template>
14857                 </geographic>
14858         </xsl:template>
14859         <xsl:template name="subjectTemporalY">
14860                 <temporal>
14861                         <xsl:call-template name="chopPunctuation">
14862                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14863                         </xsl:call-template>
14864                 </temporal>
14865         </xsl:template>
14866         <xsl:template name="subjectTopic">
14867                 <topic>
14868                         <xsl:call-template name="chopPunctuation">
14869                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14870                         </xsl:call-template>
14871                 </topic>
14872         </xsl:template> 
14873         <!-- 3.2 change tmee 6xx $v genre -->
14874         <xsl:template name="subjectGenre">
14875                 <genre>
14876                         <xsl:call-template name="chopPunctuation">
14877                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14878                         </xsl:call-template>
14879                 </genre>
14880         </xsl:template>
14881         
14882         <xsl:template name="nameABCDN">
14883                 <xsl:for-each select="marc:subfield[@code='a']">
14884                         <namePart>
14885                                 <xsl:call-template name="chopPunctuation">
14886                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
14887                                 </xsl:call-template>
14888                         </namePart>
14889                 </xsl:for-each>
14890                 <xsl:for-each select="marc:subfield[@code='b']">
14891                         <namePart>
14892                                 <xsl:value-of select="."></xsl:value-of>
14893                         </namePart>
14894                 </xsl:for-each>
14895                 <xsl:if test="marc:subfield[@code='c'] or marc:subfield[@code='d'] or marc:subfield[@code='n']">
14896                         <namePart>
14897                                 <xsl:call-template name="subfieldSelect">
14898                                         <xsl:with-param name="codes">cdn</xsl:with-param>
14899                                 </xsl:call-template>
14900                         </namePart>
14901                 </xsl:if>
14902         </xsl:template>
14903         <xsl:template name="nameABCDQ">
14904                 <namePart>
14905                         <xsl:call-template name="chopPunctuation">
14906                                 <xsl:with-param name="chopString">
14907                                         <xsl:call-template name="subfieldSelect">
14908                                                 <xsl:with-param name="codes">aq</xsl:with-param>
14909                                         </xsl:call-template>
14910                                 </xsl:with-param>
14911                                 <xsl:with-param name="punctuation">
14912                                         <xsl:text>:,;/ </xsl:text>
14913                                 </xsl:with-param>
14914                         </xsl:call-template>
14915                 </namePart>
14916                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
14917                 <xsl:call-template name="nameDate"></xsl:call-template>
14918         </xsl:template>
14919         <xsl:template name="nameACDEQ">
14920                 <namePart>
14921                         <xsl:call-template name="subfieldSelect">
14922                                 <xsl:with-param name="codes">acdeq</xsl:with-param>
14923                         </xsl:call-template>
14924                 </namePart>
14925         </xsl:template>
14926         <xsl:template name="constituentOrRelatedType">
14927                 <xsl:if test="@ind2=2">
14928                         <xsl:attribute name="type">constituent</xsl:attribute>
14929                 </xsl:if>
14930         </xsl:template>
14931         <xsl:template name="relatedTitle">
14932                 <xsl:for-each select="marc:subfield[@code='t']">
14933                         <titleInfo>
14934                                 <title>
14935                                         <xsl:call-template name="chopPunctuation">
14936                                                 <xsl:with-param name="chopString">
14937                                                         <xsl:value-of select="."></xsl:value-of>
14938                                                 </xsl:with-param>
14939                                         </xsl:call-template>
14940                                 </title>
14941                         </titleInfo>
14942                 </xsl:for-each>
14943         </xsl:template>
14944         <xsl:template name="relatedTitle76X-78X">
14945                 <xsl:for-each select="marc:subfield[@code='t']">
14946                         <titleInfo>
14947                                 <title>
14948                                         <xsl:call-template name="chopPunctuation">
14949                                                 <xsl:with-param name="chopString">
14950                                                         <xsl:value-of select="."></xsl:value-of>
14951                                                 </xsl:with-param>
14952                                         </xsl:call-template>
14953                                 </title>
14954                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14955                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14956                                 </xsl:if>
14957                         </titleInfo>
14958                 </xsl:for-each>
14959                 <xsl:for-each select="marc:subfield[@code='p']">
14960                         <titleInfo type="abbreviated">
14961                                 <title>
14962                                         <xsl:call-template name="chopPunctuation">
14963                                                 <xsl:with-param name="chopString">
14964                                                         <xsl:value-of select="."></xsl:value-of>
14965                                                 </xsl:with-param>
14966                                         </xsl:call-template>
14967                                 </title>
14968                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14969                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14970                                 </xsl:if>
14971                         </titleInfo>
14972                 </xsl:for-each>
14973                 <xsl:for-each select="marc:subfield[@code='s']">
14974                         <titleInfo type="uniform">
14975                                 <title>
14976                                         <xsl:call-template name="chopPunctuation">
14977                                                 <xsl:with-param name="chopString">
14978                                                         <xsl:value-of select="."></xsl:value-of>
14979                                                 </xsl:with-param>
14980                                         </xsl:call-template>
14981                                 </title>
14982                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14983                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14984                                 </xsl:if>
14985                         </titleInfo>
14986                 </xsl:for-each>
14987         </xsl:template>
14988         <xsl:template name="relatedOriginInfo">
14989                 <xsl:if test="marc:subfield[@code='b' or @code='d'] or marc:subfield[@code='f']">
14990                         <originInfo>
14991                                 <xsl:if test="@tag=775">
14992                                         <xsl:for-each select="marc:subfield[@code='f']">
14993                                                 <place>
14994                                                         <placeTerm>
14995                                                                 <xsl:attribute name="type">code</xsl:attribute>
14996                                                                 <xsl:attribute name="authority">marcgac</xsl:attribute>
14997                                                                 <xsl:value-of select="."></xsl:value-of>
14998                                                         </placeTerm>
14999                                                 </place>
15000                                         </xsl:for-each>
15001                                 </xsl:if>
15002                                 <xsl:for-each select="marc:subfield[@code='d']">
15003                                         <publisher>
15004                                                 <xsl:value-of select="."></xsl:value-of>
15005                                         </publisher>
15006                                 </xsl:for-each>
15007                                 <xsl:for-each select="marc:subfield[@code='b']">
15008                                         <edition>
15009                                                 <xsl:value-of select="."></xsl:value-of>
15010                                         </edition>
15011                                 </xsl:for-each>
15012                         </originInfo>
15013                 </xsl:if>
15014         </xsl:template>
15015         <xsl:template name="relatedLanguage">
15016                 <xsl:for-each select="marc:subfield[@code='e']">
15017                         <xsl:call-template name="getLanguage">
15018                                 <xsl:with-param name="langString">
15019                                         <xsl:value-of select="."></xsl:value-of>
15020                                 </xsl:with-param>
15021                         </xsl:call-template>
15022                 </xsl:for-each>
15023         </xsl:template>
15024         <xsl:template name="nameDate">
15025                 <xsl:for-each select="marc:subfield[@code='d']">
15026                         <namePart type="date">
15027                                 <xsl:call-template name="chopPunctuation">
15028                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
15029                                 </xsl:call-template>
15030                         </namePart>
15031                 </xsl:for-each>
15032         </xsl:template>
15033         <xsl:template name="subjectAuthority">
15034                 <xsl:if test="@ind2!=4">
15035                         <xsl:if test="@ind2!=' '">
15036                                 <xsl:if test="@ind2!=8">
15037                                         <xsl:if test="@ind2!=9">
15038                                                 <xsl:attribute name="authority">
15039                                                         <xsl:choose>
15040                                                                 <xsl:when test="@ind2=0">lcsh</xsl:when>
15041                                                                 <xsl:when test="@ind2=1">lcshac</xsl:when>
15042                                                                 <xsl:when test="@ind2=2">mesh</xsl:when>
15043                                                                 <!-- 1/04 fix -->
15044                                                                 <xsl:when test="@ind2=3">nal</xsl:when>
15045                                                                 <xsl:when test="@ind2=5">csh</xsl:when>
15046                                                                 <xsl:when test="@ind2=6">rvm</xsl:when>
15047                                                                 <xsl:when test="@ind2=7">
15048                                                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
15049                                                                 </xsl:when>
15050                                                         </xsl:choose>
15051                                                 </xsl:attribute>
15052                                         </xsl:if>
15053                                 </xsl:if>
15054                         </xsl:if>
15055                 </xsl:if>
15056         </xsl:template>
15057         <xsl:template name="subjectAnyOrder">
15058                 <xsl:for-each select="marc:subfield[@code='v' or @code='x' or @code='y' or @code='z']">
15059                         <xsl:choose>
15060                                 <xsl:when test="@code='v'">
15061                                         <xsl:call-template name="subjectGenre"></xsl:call-template>
15062                                 </xsl:when>
15063                                 <xsl:when test="@code='x'">
15064                                         <xsl:call-template name="subjectTopic"></xsl:call-template>
15065                                 </xsl:when>
15066                                 <xsl:when test="@code='y'">
15067                                         <xsl:call-template name="subjectTemporalY"></xsl:call-template>
15068                                 </xsl:when>
15069                                 <xsl:when test="@code='z'">
15070                                         <xsl:call-template name="subjectGeographicZ"></xsl:call-template>
15071                                 </xsl:when>
15072                         </xsl:choose>
15073                 </xsl:for-each>
15074         </xsl:template>
15075         <xsl:template name="specialSubfieldSelect">
15076                 <xsl:param name="anyCodes"></xsl:param>
15077                 <xsl:param name="axis"></xsl:param>
15078                 <xsl:param name="beforeCodes"></xsl:param>
15079                 <xsl:param name="afterCodes"></xsl:param>
15080                 <xsl:variable name="str">
15081                         <xsl:for-each select="marc:subfield">
15082                                 <xsl:if test="contains($anyCodes, @code)      or (contains($beforeCodes,@code) and following-sibling::marc:subfield[@code=$axis])      or (contains($afterCodes,@code) and preceding-sibling::marc:subfield[@code=$axis])">
15083                                         <xsl:value-of select="text()"></xsl:value-of>
15084                                         <xsl:text> </xsl:text>
15085                                 </xsl:if>
15086                         </xsl:for-each>
15087                 </xsl:variable>
15088                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
15089         </xsl:template>
15090         
15091         <!-- 3.2 change tmee 6xx $v genre -->
15092         <xsl:template match="marc:datafield[@tag=600]">
15093                 <subject>
15094                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15095                         <name type="personal">
15096                                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
15097                                 <namePart>
15098                                         <xsl:call-template name="chopPunctuation">
15099                                                 <xsl:with-param name="chopString">
15100                                                         <xsl:call-template name="subfieldSelect">
15101                                                                 <xsl:with-param name="codes">aq</xsl:with-param>
15102                                                         </xsl:call-template>
15103                                                 </xsl:with-param>
15104                                         </xsl:call-template>
15105                                 </namePart>
15106                                 <xsl:call-template name="nameDate"></xsl:call-template>
15107                                 <xsl:call-template name="affiliation"></xsl:call-template>
15108                                 <xsl:call-template name="role"></xsl:call-template>
15109                         </name>
15110                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15111                 </subject>
15112         </xsl:template>
15113         <xsl:template match="marc:datafield[@tag=610]">
15114                 <subject>
15115                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15116                         <name type="corporate">
15117                                 <xsl:for-each select="marc:subfield[@code='a']">
15118                                         <namePart>
15119                                                 <xsl:value-of select="."></xsl:value-of>
15120                                         </namePart>
15121                                 </xsl:for-each>
15122                                 <xsl:for-each select="marc:subfield[@code='b']">
15123                                         <namePart>
15124                                                 <xsl:value-of select="."></xsl:value-of>
15125                                         </namePart>
15126                                 </xsl:for-each>
15127                                 <xsl:if test="marc:subfield[@code='c' or @code='d' or @code='n' or @code='p']">
15128                                         <namePart>
15129                                                 <xsl:call-template name="subfieldSelect">
15130                                                         <xsl:with-param name="codes">cdnp</xsl:with-param>
15131                                                 </xsl:call-template>
15132                                         </namePart>
15133                                 </xsl:if>
15134                                 <xsl:call-template name="role"></xsl:call-template>
15135                         </name>
15136                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15137                 </subject>
15138         </xsl:template>
15139         <xsl:template match="marc:datafield[@tag=611]">
15140                 <subject>
15141                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15142                         <name type="conference">
15143                                 <namePart>
15144                                         <xsl:call-template name="subfieldSelect">
15145                                                 <xsl:with-param name="codes">abcdeqnp</xsl:with-param>
15146                                         </xsl:call-template>
15147                                 </namePart>
15148                                 <xsl:for-each select="marc:subfield[@code='4']">
15149                                         <role>
15150                                                 <roleTerm authority="marcrelator" type="code">
15151                                                         <xsl:value-of select="."></xsl:value-of>
15152                                                 </roleTerm>
15153                                         </role>
15154                                 </xsl:for-each>
15155                         </name>
15156                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15157                 </subject>
15158         </xsl:template>
15159         <xsl:template match="marc:datafield[@tag=630]">
15160                 <subject>
15161                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15162                         <titleInfo>
15163                                 <title>
15164                                         <xsl:call-template name="chopPunctuation">
15165                                                 <xsl:with-param name="chopString">
15166                                                         <xsl:call-template name="subfieldSelect">
15167                                                                 <xsl:with-param name="codes">adfhklor</xsl:with-param>
15168                                                         </xsl:call-template>
15169                                                 </xsl:with-param>
15170                                         </xsl:call-template>
15171                                         <xsl:call-template name="part"></xsl:call-template>
15172                                 </title>
15173                         </titleInfo>
15174                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15175                 </subject>
15176         </xsl:template>
15177         <xsl:template match="marc:datafield[@tag=650]">
15178                 <subject>
15179                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15180                         <topic>
15181                                 <xsl:call-template name="chopPunctuation">
15182                                         <xsl:with-param name="chopString">
15183                                                 <xsl:call-template name="subfieldSelect">
15184                                                         <xsl:with-param name="codes">abcd</xsl:with-param>
15185                                                 </xsl:call-template>
15186                                         </xsl:with-param>
15187                                 </xsl:call-template>
15188                         </topic>
15189                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15190                 </subject>
15191         </xsl:template>
15192         <xsl:template match="marc:datafield[@tag=651]">
15193                 <subject>
15194                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15195                         <xsl:for-each select="marc:subfield[@code='a']">
15196                                 <geographic>
15197                                         <xsl:call-template name="chopPunctuation">
15198                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
15199                                         </xsl:call-template>
15200                                 </geographic>
15201                         </xsl:for-each>
15202                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15203                 </subject>
15204         </xsl:template>
15205         <xsl:template match="marc:datafield[@tag=653]">
15206                 <subject>
15207                         <xsl:for-each select="marc:subfield[@code='a']">
15208                                 <topic>
15209                                         <xsl:value-of select="."></xsl:value-of>
15210                                 </topic>
15211                         </xsl:for-each>
15212                 </subject>
15213         </xsl:template>
15214         <xsl:template match="marc:datafield[@tag=656]">
15215                 <subject>
15216                         <xsl:if test="marc:subfield[@code=2]">
15217                                 <xsl:attribute name="authority">
15218                                         <xsl:value-of select="marc:subfield[@code=2]"></xsl:value-of>
15219                                 </xsl:attribute>
15220                         </xsl:if>
15221                         <occupation>
15222                                 <xsl:call-template name="chopPunctuation">
15223                                         <xsl:with-param name="chopString">
15224                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
15225                                         </xsl:with-param>
15226                                 </xsl:call-template>
15227                         </occupation>
15228                 </subject>
15229         </xsl:template>
15230         <xsl:template name="termsOfAddress">
15231                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
15232                         <namePart type="termsOfAddress">
15233                                 <xsl:call-template name="chopPunctuation">
15234                                         <xsl:with-param name="chopString">
15235                                                 <xsl:call-template name="subfieldSelect">
15236                                                         <xsl:with-param name="codes">bc</xsl:with-param>
15237                                                 </xsl:call-template>
15238                                         </xsl:with-param>
15239                                 </xsl:call-template>
15240                         </namePart>
15241                 </xsl:if>
15242         </xsl:template>
15243         <xsl:template name="displayLabel">
15244                 <xsl:if test="marc:subfield[@code='i']">
15245                         <xsl:attribute name="displayLabel">
15246                                 <xsl:value-of select="marc:subfield[@code='i']"></xsl:value-of>
15247                         </xsl:attribute>
15248                 </xsl:if>
15249                 <xsl:if test="marc:subfield[@code='3']">
15250                         <xsl:attribute name="displayLabel">
15251                                 <xsl:value-of select="marc:subfield[@code='3']"></xsl:value-of>
15252                         </xsl:attribute>
15253                 </xsl:if>
15254         </xsl:template>
15255         <xsl:template name="isInvalid">
15256                 <xsl:param name="type"/>
15257                 <xsl:if test="marc:subfield[@code='z'] or marc:subfield[@code='y']">
15258                         <identifier>
15259                                 <xsl:attribute name="type">
15260                                         <xsl:value-of select="$type"/>
15261                                 </xsl:attribute>
15262                                 <xsl:attribute name="invalid">
15263                                         <xsl:text>yes</xsl:text>
15264                                 </xsl:attribute>
15265                                 <xsl:if test="marc:subfield[@code='z']">
15266                                         <xsl:value-of select="marc:subfield[@code='z']"/>
15267                                 </xsl:if>
15268                                 <xsl:if test="marc:subfield[@code='y']">
15269                                         <xsl:value-of select="marc:subfield[@code='y']"/>
15270                                 </xsl:if>
15271                         </identifier>
15272                 </xsl:if>
15273         </xsl:template>
15274         <xsl:template name="subtitle">
15275                 <xsl:if test="marc:subfield[@code='b']">
15276                         <subTitle>
15277                                 <xsl:call-template name="chopPunctuation">
15278                                         <xsl:with-param name="chopString">
15279                                                 <xsl:value-of select="marc:subfield[@code='b']"/>
15280                                                 <!--<xsl:call-template name="subfieldSelect">
15281                                                         <xsl:with-param name="codes">b</xsl:with-param>                                                                 
15282                                                 </xsl:call-template>-->
15283                                         </xsl:with-param>
15284                                 </xsl:call-template>
15285                         </subTitle>
15286                 </xsl:if>
15287         </xsl:template>
15288         <xsl:template name="script">
15289                 <xsl:param name="scriptCode"></xsl:param>
15290                 <xsl:attribute name="script">
15291                         <xsl:choose>
15292                                 <xsl:when test="$scriptCode='(3'">Arabic</xsl:when>
15293                                 <xsl:when test="$scriptCode='(B'">Latin</xsl:when>
15294                                 <xsl:when test="$scriptCode='$1'">Chinese, Japanese, Korean</xsl:when>
15295                                 <xsl:when test="$scriptCode='(N'">Cyrillic</xsl:when>
15296                                 <xsl:when test="$scriptCode='(2'">Hebrew</xsl:when>
15297                                 <xsl:when test="$scriptCode='(S'">Greek</xsl:when>
15298                         </xsl:choose>
15299                 </xsl:attribute>
15300         </xsl:template>
15301         <xsl:template name="parsePart">
15302                 <!-- assumes 773$q= 1:2:3<4
15303                      with up to 3 levels and one optional start page
15304                 -->
15305                 <xsl:variable name="level1">
15306                         <xsl:choose>
15307                                 <xsl:when test="contains(text(),':')">
15308                                         <!-- 1:2 -->
15309                                         <xsl:value-of select="substring-before(text(),':')"></xsl:value-of>
15310                                 </xsl:when>
15311                                 <xsl:when test="not(contains(text(),':'))">
15312                                         <!-- 1 or 1<3 -->
15313                                         <xsl:if test="contains(text(),'&lt;')">
15314                                                 <!-- 1<3 -->
15315                                                 <xsl:value-of select="substring-before(text(),'&lt;')"></xsl:value-of>
15316                                         </xsl:if>
15317                                         <xsl:if test="not(contains(text(),'&lt;'))">
15318                                                 <!-- 1 -->
15319                                                 <xsl:value-of select="text()"></xsl:value-of>
15320                                         </xsl:if>
15321                                 </xsl:when>
15322                         </xsl:choose>
15323                 </xsl:variable>
15324                 <xsl:variable name="sici2">
15325                         <xsl:choose>
15326                                 <xsl:when test="starts-with(substring-after(text(),$level1),':')">
15327                                         <xsl:value-of select="substring(substring-after(text(),$level1),2)"></xsl:value-of>
15328                                 </xsl:when>
15329                                 <xsl:otherwise>
15330                                         <xsl:value-of select="substring-after(text(),$level1)"></xsl:value-of>
15331                                 </xsl:otherwise>
15332                         </xsl:choose>
15333                 </xsl:variable>
15334                 <xsl:variable name="level2">
15335                         <xsl:choose>
15336                                 <xsl:when test="contains($sici2,':')">
15337                                         <!--  2:3<4  -->
15338                                         <xsl:value-of select="substring-before($sici2,':')"></xsl:value-of>
15339                                 </xsl:when>
15340                                 <xsl:when test="contains($sici2,'&lt;')">
15341                                         <!-- 1: 2<4 -->
15342                                         <xsl:value-of select="substring-before($sici2,'&lt;')"></xsl:value-of>
15343                                 </xsl:when>
15344                                 <xsl:otherwise>
15345                                         <xsl:value-of select="$sici2"></xsl:value-of>
15346                                         <!-- 1:2 -->
15347                                 </xsl:otherwise>
15348                         </xsl:choose>
15349                 </xsl:variable>
15350                 <xsl:variable name="sici3">
15351                         <xsl:choose>
15352                                 <xsl:when test="starts-with(substring-after($sici2,$level2),':')">
15353                                         <xsl:value-of select="substring(substring-after($sici2,$level2),2)"></xsl:value-of>
15354                                 </xsl:when>
15355                                 <xsl:otherwise>
15356                                         <xsl:value-of select="substring-after($sici2,$level2)"></xsl:value-of>
15357                                 </xsl:otherwise>
15358                         </xsl:choose>
15359                 </xsl:variable>
15360                 <xsl:variable name="level3">
15361                         <xsl:choose>
15362                                 <xsl:when test="contains($sici3,'&lt;')">
15363                                         <!-- 2<4 -->
15364                                         <xsl:value-of select="substring-before($sici3,'&lt;')"></xsl:value-of>
15365                                 </xsl:when>
15366                                 <xsl:otherwise>
15367                                         <xsl:value-of select="$sici3"></xsl:value-of>
15368                                         <!-- 3 -->
15369                                 </xsl:otherwise>
15370                         </xsl:choose>
15371                 </xsl:variable>
15372                 <xsl:variable name="page">
15373                         <xsl:if test="contains(text(),'&lt;')">
15374                                 <xsl:value-of select="substring-after(text(),'&lt;')"></xsl:value-of>
15375                         </xsl:if>
15376                 </xsl:variable>
15377                 <xsl:if test="$level1">
15378                         <detail level="1">
15379                                 <number>
15380                                         <xsl:value-of select="$level1"></xsl:value-of>
15381                                 </number>
15382                         </detail>
15383                 </xsl:if>
15384                 <xsl:if test="$level2">
15385                         <detail level="2">
15386                                 <number>
15387                                         <xsl:value-of select="$level2"></xsl:value-of>
15388                                 </number>
15389                         </detail>
15390                 </xsl:if>
15391                 <xsl:if test="$level3">
15392                         <detail level="3">
15393                                 <number>
15394                                         <xsl:value-of select="$level3"></xsl:value-of>
15395                                 </number>
15396                         </detail>
15397                 </xsl:if>
15398                 <xsl:if test="$page">
15399                         <extent unit="page">
15400                                 <start>
15401                                         <xsl:value-of select="$page"></xsl:value-of>
15402                                 </start>
15403                         </extent>
15404                 </xsl:if>
15405         </xsl:template>
15406         <xsl:template name="getLanguage">
15407                 <xsl:param name="langString"></xsl:param>
15408                 <xsl:param name="controlField008-35-37"></xsl:param>
15409                 <xsl:variable name="length" select="string-length($langString)"></xsl:variable>
15410                 <xsl:choose>
15411                         <xsl:when test="$length=0"></xsl:when>
15412                         <xsl:when test="$controlField008-35-37=substring($langString,1,3)">
15413                                 <xsl:call-template name="getLanguage">
15414                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15415                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15416                                 </xsl:call-template>
15417                         </xsl:when>
15418                         <xsl:otherwise>
15419                                 <language>
15420                                         <languageTerm authority="iso639-2b" type="code">
15421                                                 <xsl:value-of select="substring($langString,1,3)"></xsl:value-of>
15422                                         </languageTerm>
15423                                 </language>
15424                                 <xsl:call-template name="getLanguage">
15425                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15426                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15427                                 </xsl:call-template>
15428                         </xsl:otherwise>
15429                 </xsl:choose>
15430         </xsl:template>
15431         <xsl:template name="isoLanguage">
15432                 <xsl:param name="currentLanguage"></xsl:param>
15433                 <xsl:param name="usedLanguages"></xsl:param>
15434                 <xsl:param name="remainingLanguages"></xsl:param>
15435                 <xsl:choose>
15436                         <xsl:when test="string-length($currentLanguage)=0"></xsl:when>
15437                         <xsl:when test="not(contains($usedLanguages, $currentLanguage))">
15438                                 <language>
15439                                         <xsl:if test="@code!='a'">
15440                                                 <xsl:attribute name="objectPart">
15441                                                         <xsl:choose>
15442                                                                 <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15443                                                                 <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15444                                                                 <xsl:when test="@code='e'">libretto</xsl:when>
15445                                                                 <xsl:when test="@code='f'">table of contents</xsl:when>
15446                                                                 <xsl:when test="@code='g'">accompanying material</xsl:when>
15447                                                                 <xsl:when test="@code='h'">translation</xsl:when>
15448                                                         </xsl:choose>
15449                                                 </xsl:attribute>
15450                                         </xsl:if>
15451                                         <languageTerm authority="iso639-2b" type="code">
15452                                                 <xsl:value-of select="$currentLanguage"></xsl:value-of>
15453                                         </languageTerm>
15454                                 </language>
15455                                 <xsl:call-template name="isoLanguage">
15456                                         <xsl:with-param name="currentLanguage">
15457                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15458                                         </xsl:with-param>
15459                                         <xsl:with-param name="usedLanguages">
15460                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15461                                         </xsl:with-param>
15462                                         <xsl:with-param name="remainingLanguages">
15463                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15464                                         </xsl:with-param>
15465                                 </xsl:call-template>
15466                         </xsl:when>
15467                         <xsl:otherwise>
15468                                 <xsl:call-template name="isoLanguage">
15469                                         <xsl:with-param name="currentLanguage">
15470                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15471                                         </xsl:with-param>
15472                                         <xsl:with-param name="usedLanguages">
15473                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15474                                         </xsl:with-param>
15475                                         <xsl:with-param name="remainingLanguages">
15476                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15477                                         </xsl:with-param>
15478                                 </xsl:call-template>
15479                         </xsl:otherwise>
15480                 </xsl:choose>
15481         </xsl:template>
15482         <xsl:template name="chopBrackets">
15483                 <xsl:param name="chopString"></xsl:param>
15484                 <xsl:variable name="string">
15485                         <xsl:call-template name="chopPunctuation">
15486                                 <xsl:with-param name="chopString" select="$chopString"></xsl:with-param>
15487                         </xsl:call-template>
15488                 </xsl:variable>
15489                 <xsl:if test="substring($string, 1,1)='['">
15490                         <xsl:value-of select="substring($string,2, string-length($string)-2)"></xsl:value-of>
15491                 </xsl:if>
15492                 <xsl:if test="substring($string, 1,1)!='['">
15493                         <xsl:value-of select="$string"></xsl:value-of>
15494                 </xsl:if>
15495         </xsl:template>
15496         <xsl:template name="rfcLanguages">
15497                 <xsl:param name="nodeNum"></xsl:param>
15498                 <xsl:param name="usedLanguages"></xsl:param>
15499                 <xsl:param name="controlField008-35-37"></xsl:param>
15500                 <xsl:variable name="currentLanguage" select="."></xsl:variable>
15501                 <xsl:choose>
15502                         <xsl:when test="not($currentLanguage)"></xsl:when>
15503                         <xsl:when test="$currentLanguage!=$controlField008-35-37 and $currentLanguage!='rfc3066'">
15504                                 <xsl:if test="not(contains($usedLanguages,$currentLanguage))">
15505                                         <language>
15506                                                 <xsl:if test="@code!='a'">
15507                                                         <xsl:attribute name="objectPart">
15508                                                                 <xsl:choose>
15509                                                                         <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15510                                                                         <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15511                                                                         <xsl:when test="@code='e'">libretto</xsl:when>
15512                                                                         <xsl:when test="@code='f'">table of contents</xsl:when>
15513                                                                         <xsl:when test="@code='g'">accompanying material</xsl:when>
15514                                                                         <xsl:when test="@code='h'">translation</xsl:when>
15515                                                                 </xsl:choose>
15516                                                         </xsl:attribute>
15517                                                 </xsl:if>
15518                                                 <languageTerm authority="rfc3066" type="code">
15519                                                         <xsl:value-of select="$currentLanguage"/>
15520                                                 </languageTerm>
15521                                         </language>
15522                                 </xsl:if>
15523                         </xsl:when>
15524                         <xsl:otherwise>
15525                         </xsl:otherwise>
15526                 </xsl:choose>
15527         </xsl:template>
15528         <xsl:template name="datafield">
15529                 <xsl:param name="tag"/>
15530                 <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param>
15531                 <xsl:param name="ind2"><xsl:text> </xsl:text></xsl:param>
15532                 <xsl:param name="subfields"/>
15533                 <xsl:element name="marc:datafield">
15534                         <xsl:attribute name="tag">
15535                                 <xsl:value-of select="$tag"/>
15536                         </xsl:attribute>
15537                         <xsl:attribute name="ind1">
15538                                 <xsl:value-of select="$ind1"/>
15539                         </xsl:attribute>
15540                         <xsl:attribute name="ind2">
15541                                 <xsl:value-of select="$ind2"/>
15542                         </xsl:attribute>
15543                         <xsl:copy-of select="$subfields"/>
15544                 </xsl:element>
15545         </xsl:template>
15546
15547         <xsl:template name="subfieldSelect">
15548                 <xsl:param name="codes"/>
15549                 <xsl:param name="delimeter"><xsl:text> </xsl:text></xsl:param>
15550                 <xsl:variable name="str">
15551                         <xsl:for-each select="marc:subfield">
15552                                 <xsl:if test="contains($codes, @code)">
15553                                         <xsl:value-of select="text()"/><xsl:value-of select="$delimeter"/>
15554                                 </xsl:if>
15555                         </xsl:for-each>
15556                 </xsl:variable>
15557                 <xsl:value-of select="substring($str,1,string-length($str)-string-length($delimeter))"/>
15558         </xsl:template>
15559
15560         <xsl:template name="buildSpaces">
15561                 <xsl:param name="spaces"/>
15562                 <xsl:param name="char"><xsl:text> </xsl:text></xsl:param>
15563                 <xsl:if test="$spaces>0">
15564                         <xsl:value-of select="$char"/>
15565                         <xsl:call-template name="buildSpaces">
15566                                 <xsl:with-param name="spaces" select="$spaces - 1"/>
15567                                 <xsl:with-param name="char" select="$char"/>
15568                         </xsl:call-template>
15569                 </xsl:if>
15570         </xsl:template>
15571
15572         <xsl:template name="chopPunctuation">
15573                 <xsl:param name="chopString"/>
15574                 <xsl:param name="punctuation"><xsl:text>.:,;/ </xsl:text></xsl:param>
15575                 <xsl:variable name="length" select="string-length($chopString)"/>
15576                 <xsl:choose>
15577                         <xsl:when test="$length=0"/>
15578                         <xsl:when test="contains($punctuation, substring($chopString,$length,1))">
15579                                 <xsl:call-template name="chopPunctuation">
15580                                         <xsl:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
15581                                         <xsl:with-param name="punctuation" select="$punctuation"/>
15582                                 </xsl:call-template>
15583                         </xsl:when>
15584                         <xsl:when test="not($chopString)"/>
15585                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15586                 </xsl:choose>
15587         </xsl:template>
15588
15589         <xsl:template name="chopPunctuationFront">
15590                 <xsl:param name="chopString"/>
15591                 <xsl:variable name="length" select="string-length($chopString)"/>
15592                 <xsl:choose>
15593                         <xsl:when test="$length=0"/>
15594                         <xsl:when test="contains('.:,;/[ ', substring($chopString,1,1))">
15595                                 <xsl:call-template name="chopPunctuationFront">
15596                                         <xsl:with-param name="chopString" select="substring($chopString,2,$length - 1)"/>
15597                                 </xsl:call-template>
15598                         </xsl:when>
15599                         <xsl:when test="not($chopString)"/>
15600                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15601                 </xsl:choose>
15602         </xsl:template>
15603 </xsl:stylesheet>$$ WHERE name = 'mods32';
15604
15605 -- Currently, the only difference from naco_normalize is that search_normalize
15606 -- turns apostrophes into spaces, while naco_normalize collapses them.
15607 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
15608
15609     use strict;
15610     use Unicode::Normalize;
15611     use Encode;
15612
15613     my $str = decode_utf8(shift);
15614     my $sf = shift;
15615
15616     # Apply NACO normalization to input string; based on
15617     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
15618     #
15619     # Note that unlike a strict reading of the NACO normalization rules,
15620     # output is returned as lowercase instead of uppercase for compatibility
15621     # with previous versions of the Evergreen naco_normalize routine.
15622
15623     # Convert to upper-case first; even though final output will be lowercase, doing this will
15624     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
15625     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
15626     $str = uc $str;
15627
15628     # remove non-filing strings
15629     $str =~ s/\x{0098}.*?\x{009C}//g;
15630
15631     $str = NFKD($str);
15632
15633     # additional substitutions - 3.6.
15634     $str =~ s/\x{00C6}/AE/g;
15635     $str =~ s/\x{00DE}/TH/g;
15636     $str =~ s/\x{0152}/OE/g;
15637     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;
15638
15639     # transformations based on Unicode category codes
15640     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
15641
15642         if ($sf && $sf =~ /^a/o) {
15643                 my $commapos = index($str, ',');
15644                 if ($commapos > -1) {
15645                         if ($commapos != length($str) - 1) {
15646                 $str =~ s/,/\x07/; # preserve first comma
15647                         }
15648                 }
15649         }
15650
15651     # since we've stripped out the control characters, we can now
15652     # use a few as placeholders temporarily
15653     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
15654     $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;
15655     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
15656
15657     # decimal digits
15658     $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/;
15659
15660     # intentionally skipping step 8 of the NACO algorithm; if the string
15661     # gets normalized away, that's fine.
15662
15663     # leading and trailing spaces
15664     $str =~ s/\s+/ /g;
15665     $str =~ s/^\s+//;
15666     $str =~ s/\s+$//g;
15667
15668     return lc $str;
15669 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
15670
15671 CREATE OR REPLACE FUNCTION public.search_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
15672         SELECT public.search_normalize($1,'a');
15673 $func$ LANGUAGE SQL STRICT IMMUTABLE;
15674
15675 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT ) RETURNS TEXT AS $func$
15676         SELECT public.search_normalize($1,'');
15677 $func$ LANGUAGE 'sql' STRICT IMMUTABLE;
15678
15679 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15680         'Search Normalize',
15681         'Apply search normalization rules to the extracted text. A less extreme version of NACO normalization.',
15682         'search_normalize',
15683         0
15684 );
15685
15686 UPDATE config.metabib_field_index_norm_map
15687     SET norm = (
15688         SELECT id FROM config.index_normalizer WHERE func = 'search_normalize'
15689     )
15690     WHERE norm = (
15691         SELECT id FROM config.index_normalizer WHERE func = 'naco_normalize'
15692     )
15693 ;
15694
15695
15696 -- This could take a long time if you have a very non-English bib database
15697 -- Run it outside of a transaction to avoid lock escalation
15698 SELECT metabib.reingest_metabib_field_entries(record)
15699     FROM metabib.full_rec
15700     WHERE tag = '245'
15701     AND subfield = 'a'
15702     AND value LIKE '%''%'
15703 ;
15704
15705 COMMIT;
15706
15707 -- This is split out because it takes forever to run on large bib collections.
15708 \qecho ************************************************************************
15709 \qecho The following transaction, wrapping upgrades 0679 and 0680, may take a
15710 \qecho *really* long time, and you might be able to run it by itself in
15711 \qecho parallel with other operations using a separate session.
15712 \qecho ************************************************************************
15713
15714 BEGIN;
15715 SELECT evergreen.upgrade_deps_block_check('0679', :eg_version);
15716
15717 -- Address typo in column name
15718 ALTER TABLE config.metabib_class ADD COLUMN buoyant BOOL DEFAULT FALSE NOT NULL;
15719 UPDATE config.metabib_class SET buoyant = bouyant;
15720 ALTER TABLE config.metabib_class DROP COLUMN bouyant;
15721
15722 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
15723 DECLARE
15724     normalizer      RECORD;
15725     value           TEXT := '';
15726 BEGIN
15727
15728     value := NEW.value;
15729
15730     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15731         FOR normalizer IN
15732             SELECT  n.func AS func,
15733                     n.param_count AS param_count,
15734                     m.params AS params
15735               FROM  config.index_normalizer n
15736                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15737               WHERE field = NEW.field AND m.pos < 0
15738               ORDER BY m.pos LOOP
15739                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15740                     quote_literal( value ) ||
15741                     CASE
15742                         WHEN normalizer.param_count > 0
15743                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15744                             ELSE ''
15745                         END ||
15746                     ')' INTO value;
15747
15748         END LOOP;
15749
15750         NEW.value := value;
15751     END IF;
15752
15753     IF NEW.index_vector = ''::tsvector THEN
15754         RETURN NEW;
15755     END IF;
15756
15757     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15758         FOR normalizer IN
15759             SELECT  n.func AS func,
15760                     n.param_count AS param_count,
15761                     m.params AS params
15762               FROM  config.index_normalizer n
15763                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15764               WHERE field = NEW.field AND m.pos >= 0
15765               ORDER BY m.pos LOOP
15766                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15767                     quote_literal( value ) ||
15768                     CASE
15769                         WHEN normalizer.param_count > 0
15770                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15771                             ELSE ''
15772                         END ||
15773                     ')' INTO value;
15774
15775         END LOOP;
15776     END IF;
15777
15778     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
15779         value :=  ARRAY_TO_STRING(
15780             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15781         );
15782         value := public.search_normalize(value);
15783     END IF;
15784
15785     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
15786
15787     RETURN NEW;
15788 END;
15789 $$ LANGUAGE PLPGSQL;
15790
15791 -- Given a string such as a user might type into a search box, prepare
15792 -- two changed variants for TO_TSQUERY(). See
15793 -- http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
15794 -- The first variant is normalized to match indexed documents regardless
15795 -- of diacritics.  The second variant keeps its diacritics for proper
15796 -- highlighting via TS_HEADLINE().
15797 CREATE OR REPLACE
15798     FUNCTION metabib.autosuggest_prepare_tsquery(orig TEXT) RETURNS TEXT[] AS
15799 $$
15800 DECLARE
15801     orig_ended_in_space     BOOLEAN;
15802     result                  RECORD;
15803     plain                   TEXT;
15804     normalized              TEXT;
15805 BEGIN
15806     orig_ended_in_space := orig ~ E'\\s$';
15807
15808     orig := ARRAY_TO_STRING(
15809         evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
15810     );
15811
15812     normalized := public.search_normalize(orig); -- also trim()s
15813     plain := trim(orig);
15814
15815     IF NOT orig_ended_in_space THEN
15816         plain := plain || ':*';
15817         normalized := normalized || ':*';
15818     END IF;
15819
15820     plain := ARRAY_TO_STRING(
15821         evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
15822     );
15823     normalized := ARRAY_TO_STRING(
15824         evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
15825     );
15826
15827     RETURN ARRAY[normalized, plain];
15828 END;
15829 $$ LANGUAGE PLPGSQL;
15830
15831
15832 -- Definition of OUT parameters changes, so must drop first
15833 DROP FUNCTION IF EXISTS metabib.suggest_browse_entries (TEXT, TEXT, TEXT, INTEGER, INTEGER, INTEGER);
15834
15835 CREATE OR REPLACE
15836     FUNCTION metabib.suggest_browse_entries(
15837         raw_query_text  TEXT,   -- actually typed by humans at the UI level
15838         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
15839         headline_opts   TEXT,   -- markup options for ts_headline()
15840         visibility_org  INTEGER,-- null if you don't want opac visibility test
15841         query_limit     INTEGER,-- use in LIMIT clause of interal query
15842         normalization   INTEGER -- argument to TS_RANK_CD()
15843     ) RETURNS TABLE (
15844         value                   TEXT,   -- plain
15845         field                   INTEGER,
15846         buoyant_and_class_match BOOL,
15847         field_match             BOOL,
15848         field_weight            INTEGER,
15849         rank                    REAL,
15850         buoyant                 BOOL,
15851         match                   TEXT    -- marked up
15852     ) AS $func$
15853 DECLARE
15854     prepared_query_texts    TEXT[];
15855     query                   TSQUERY;
15856     plain_query             TSQUERY;
15857     opac_visibility_join    TEXT;
15858     search_class_join       TEXT;
15859     r_fields                RECORD;
15860 BEGIN
15861     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
15862
15863     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
15864     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
15865
15866     IF visibility_org IS NOT NULL THEN
15867         opac_visibility_join := '
15868     JOIN asset.opac_visible_copies aovc ON (
15869         aovc.record = mbedm.source AND
15870         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
15871     )';
15872     ELSE
15873         opac_visibility_join := '';
15874     END IF;
15875
15876     -- The following determines whether we only provide suggestsons matching
15877     -- the user's selected search_class, or whether we show other suggestions
15878     -- too. The reason for MIN() is that for search_classes like
15879     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
15880     -- implication is that if title as a class doesn't have restrict,
15881     -- nor does the proper field, but the uniform field does, you're going
15882     -- to get 'false' for your overall evaluation of 'should we restrict?'
15883     -- To invert that, change from MIN() to MAX().
15884
15885     SELECT
15886         INTO r_fields
15887             MIN(cmc.restrict::INT) AS restrict_class,
15888             MIN(cmf.restrict::INT) AS restrict_field
15889         FROM metabib.search_class_to_registered_components(search_class)
15890             AS _registered (field_class TEXT, field INT)
15891         JOIN
15892             config.metabib_class cmc ON (cmc.name = _registered.field_class)
15893         LEFT JOIN
15894             config.metabib_field cmf ON (cmf.id = _registered.field);
15895
15896     -- evaluate 'should we restrict?'
15897     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
15898         search_class_join := '
15899     JOIN
15900         metabib.search_class_to_registered_components($2)
15901         AS _registered (field_class TEXT, field INT) ON (
15902             (_registered.field IS NULL AND
15903                 _registered.field_class = cmf.field_class) OR
15904             (_registered.field = cmf.id)
15905         )
15906     ';
15907     ELSE
15908         search_class_join := '
15909     LEFT JOIN
15910         metabib.search_class_to_registered_components($2)
15911         AS _registered (field_class TEXT, field INT) ON (
15912             _registered.field_class = cmc.name
15913         )
15914     ';
15915     END IF;
15916
15917     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $7, $3) FROM (SELECT DISTINCT
15918         mbe.value,
15919         cmf.id,
15920         cmc.buoyant AND _registered.field_class IS NOT NULL,
15921         _registered.field = cmf.id,
15922         cmf.weight,
15923         TS_RANK_CD(mbe.index_vector, $1, $6),
15924         cmc.buoyant
15925     FROM metabib.browse_entry_def_map mbedm
15926     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
15927     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
15928     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
15929     '  || search_class_join || opac_visibility_join ||
15930     ' WHERE $1 @@ mbe.index_vector
15931     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15932     LIMIT $5) x
15933     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15934     '   -- sic, repeat the order by clause in the outer select too
15935     USING
15936         query, search_class, headline_opts,
15937         visibility_org, query_limit, normalization, plain_query
15938         ;
15939
15940     -- sort order:
15941     --  buoyant AND chosen class = match class
15942     --  chosen field = match field
15943     --  field weight
15944     --  rank
15945     --  buoyancy
15946     --  value itself
15947
15948 END;
15949 $func$ LANGUAGE PLPGSQL;
15950
15951
15952 \qecho 
15953 \qecho The following takes about a minute per 100,000 rows in
15954 \qecho metabib.browse_entry on my development system, which is only a VM with
15955 \qecho 4 GB of memory and 2 cores.
15956 \qecho 
15957 \qecho The following is a very loose estimate of how long the next UPDATE
15958 \qecho statement would take to finish on MY machine, based on YOUR number
15959 \qecho of rows in metabib.browse_entry:
15960 \qecho 
15961
15962 SELECT (COUNT(id) / 100000.0) * INTERVAL '1 minute'
15963     AS "approximate duration of following UPDATE statement"
15964     FROM metabib.browse_entry;
15965
15966 UPDATE metabib.browse_entry SET index_vector = TO_TSVECTOR(
15967     'keyword',
15968     public.search_normalize(
15969         ARRAY_TO_STRING(
15970             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15971         )
15972     )
15973 );
15974
15975
15976 SELECT evergreen.upgrade_deps_block_check('0680', :eg_version);
15977
15978 -- Not much use in having identifier-class fields be suggestions. Credit for the idea goes to Ben Shum.
15979 UPDATE config.metabib_field SET browse_field = FALSE WHERE id < 100 AND field_class = 'identifier';
15980
15981
15982 ---------------------------------------------------------------------------
15983 -- The rest of this was tested on Evergreen Indiana's dev server, which has
15984 -- a large data set  of 2.6M bibs, and was instrumental in sussing out the
15985 -- needed adjustments.  Thanks, EG-IN!
15986 ---------------------------------------------------------------------------
15987
15988 -- GIN indexes are /much/ better for prefix matching, which is important for browse and autosuggest
15989 --Commented out the creation earlier, so we don't need to drop it here.
15990 --DROP INDEX metabib.metabib_browse_entry_index_vector_idx;
15991 CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIN (index_vector);
15992
15993
15994 -- We need thes to make the autosuggest limiting joins fast
15995 CREATE INDEX browse_entry_def_map_def_idx ON metabib.browse_entry_def_map (def);
15996 CREATE INDEX browse_entry_def_map_entry_idx ON metabib.browse_entry_def_map (entry);
15997 CREATE INDEX browse_entry_def_map_source_idx ON metabib.browse_entry_def_map (source);
15998
15999 -- In practice this will always be ~1 row, and the default of 1000 causes terrible plans
16000 ALTER FUNCTION metabib.search_class_to_registered_components(text) ROWS 1;
16001
16002 -- Reworking of the generated query to act in a sane manner in the face of large datasets
16003 CREATE OR REPLACE
16004     FUNCTION metabib.suggest_browse_entries(
16005         raw_query_text  TEXT,   -- actually typed by humans at the UI level
16006         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
16007         headline_opts   TEXT,   -- markup options for ts_headline()
16008         visibility_org  INTEGER,-- null if you don't want opac visibility test
16009         query_limit     INTEGER,-- use in LIMIT clause of interal query
16010         normalization   INTEGER -- argument to TS_RANK_CD()
16011     ) RETURNS TABLE (
16012         value                   TEXT,   -- plain
16013         field                   INTEGER,
16014         buoyant_and_class_match BOOL,
16015         field_match             BOOL,
16016         field_weight            INTEGER,
16017         rank                    REAL,
16018         buoyant                 BOOL,
16019         match                   TEXT    -- marked up
16020     ) AS $func$
16021 DECLARE
16022     prepared_query_texts    TEXT[];
16023     query                   TSQUERY;
16024     plain_query             TSQUERY;
16025     opac_visibility_join    TEXT;
16026     search_class_join       TEXT;
16027     r_fields                RECORD;
16028 BEGIN
16029     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
16030
16031     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
16032     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
16033
16034     IF visibility_org IS NOT NULL THEN
16035         opac_visibility_join := '
16036     JOIN asset.opac_visible_copies aovc ON (
16037         aovc.record = x.source AND
16038         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
16039     )';
16040     ELSE
16041         opac_visibility_join := '';
16042     END IF;
16043
16044     -- The following determines whether we only provide suggestsons matching
16045     -- the user's selected search_class, or whether we show other suggestions
16046     -- too. The reason for MIN() is that for search_classes like
16047     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
16048     -- implication is that if title as a class doesn't have restrict,
16049     -- nor does the proper field, but the uniform field does, you're going
16050     -- to get 'false' for your overall evaluation of 'should we restrict?'
16051     -- To invert that, change from MIN() to MAX().
16052
16053     SELECT
16054         INTO r_fields
16055             MIN(cmc.restrict::INT) AS restrict_class,
16056             MIN(cmf.restrict::INT) AS restrict_field
16057         FROM metabib.search_class_to_registered_components(search_class)
16058             AS _registered (field_class TEXT, field INT)
16059         JOIN
16060             config.metabib_class cmc ON (cmc.name = _registered.field_class)
16061         LEFT JOIN
16062             config.metabib_field cmf ON (cmf.id = _registered.field);
16063
16064     -- evaluate 'should we restrict?'
16065     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
16066         search_class_join := '
16067     JOIN
16068         metabib.search_class_to_registered_components($2)
16069         AS _registered (field_class TEXT, field INT) ON (
16070             (_registered.field IS NULL AND
16071                 _registered.field_class = cmf.field_class) OR
16072             (_registered.field = cmf.id)
16073         )
16074     ';
16075     ELSE
16076         search_class_join := '
16077     LEFT JOIN
16078         metabib.search_class_to_registered_components($2)
16079         AS _registered (field_class TEXT, field INT) ON (
16080             _registered.field_class = cmc.name
16081         )
16082     ';
16083     END IF;
16084
16085     RETURN QUERY EXECUTE '
16086 SELECT  DISTINCT
16087         x.value,
16088         x.id,
16089         x.push,
16090         x.restrict,
16091         x.weight,
16092         x.ts_rank_cd,
16093         x.buoyant,
16094         TS_HEADLINE(value, $7, $3)
16095   FROM  (SELECT DISTINCT
16096                 mbe.value,
16097                 cmf.id,
16098                 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
16099                 _registered.field = cmf.id AS restrict,
16100                 cmf.weight,
16101                 TS_RANK_CD(mbe.index_vector, $1, $6),
16102                 cmc.buoyant,
16103                 mbedm.source
16104           FROM  metabib.browse_entry_def_map mbedm
16105
16106                 -- Start with a pre-limited set of 10k possible suggestions. More than that is not going to be useful anyway
16107                 JOIN (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) mbe ON (mbe.id = mbedm.entry)
16108
16109                 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
16110                 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
16111                 '  || search_class_join || '
16112           ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16113           LIMIT 1000) AS x -- This outer limit makes testing for opac visibility usably fast
16114         ' || opac_visibility_join || '
16115   ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16116   LIMIT $5
16117 '   -- sic, repeat the order by clause in the outer select too
16118     USING
16119         query, search_class, headline_opts,
16120         visibility_org, query_limit, normalization, plain_query
16121         ;
16122
16123     -- sort order:
16124     --  buoyant AND chosen class = match class
16125     --  chosen field = match field
16126     --  field weight
16127     --  rank
16128     --  buoyancy
16129     --  value itself
16130
16131 END;
16132 $func$ LANGUAGE PLPGSQL;
16133
16134 COMMIT;
16135
16136 -- This is split out because it was backported to 2.1, but may not exist before upgrades
16137 -- It can safely fail
16138 -- Also, lets say that. <_<
16139 \qecho
16140 \qecho *************************************************************************
16141 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16142 \qecho We are about to apply a patch that may not be needed. It can fail safely.
16143 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16144 \qecho *************************************************************************
16145 \qecho
16146
16147 -- Evergreen DB patch 0693.schema.do_not_despace_issns.sql
16148 --
16149 -- FIXME: insert description of change, if needed
16150 --
16151 BEGIN;
16152
16153
16154 -- check whether patch can be applied
16155 SELECT evergreen.upgrade_deps_block_check('0693', :eg_version);
16156
16157 -- FIXME: add/check SQL statements to perform the upgrade
16158 -- Delete the index normalizer that was meant to remove spaces from ISSNs
16159 -- but ended up breaking records with multiple ISSNs
16160 DELETE FROM config.metabib_field_index_norm_map WHERE id IN (
16161     SELECT map.id FROM config.metabib_field_index_norm_map map
16162         INNER JOIN config.metabib_field cmf ON cmf.id = map.field
16163         INNER JOIN config.index_normalizer cin ON cin.id = map.norm
16164     WHERE cin.func = 'replace'
16165         AND cmf.field_class = 'identifier'
16166         AND cmf.name = 'issn'
16167         AND map.params = $$[" ",""]$$
16168 );
16169
16170 -- Reindex records that have more than just a single ISSN
16171 -- to ensure that spaces are maintained
16172 SELECT metabib.reingest_metabib_field_entries(source)
16173   FROM metabib.identifier_field_entry mife
16174     INNER JOIN config.metabib_field cmf ON cmf.id = mife.field
16175   WHERE cmf.field_class = 'identifier'
16176     AND cmf.name = 'issn'
16177     AND char_length(value) > 9
16178 ;
16179
16180
16181 COMMIT;
16182
16183 -- outside of any transaction
16184
16185 \qecho ************************************************************************
16186 \qecho                  Failures from here down are okay!
16187 \qecho ************************************************************************
16188
16189 SELECT evergreen.upgrade_deps_block_check('0691', :eg_version);
16190
16191 CREATE INDEX poi_po_idx ON acq.po_item (purchase_order);
16192
16193 CREATE INDEX ie_inv_idx on acq.invoice_entry (invoice);
16194 CREATE INDEX ie_po_idx on acq.invoice_entry (purchase_order);
16195 CREATE INDEX ie_li_idx on acq.invoice_entry (lineitem);
16196
16197 CREATE INDEX ii_inv_idx on acq.invoice_item (invoice);
16198 CREATE INDEX ii_po_idx on acq.invoice_item (purchase_order);
16199 CREATE INDEX ii_poi_idx on acq.invoice_item (po_item);
16200
16201 \qecho All Evergreen core database functions have been converted to
16202 \qecho use PLPERLU instead of PLPERL, so we are attempting to remove
16203 \qecho the PLPERL language here; but it is entirely possible that
16204 \qecho existing sites will have custom PLPERL functions that they
16205 \qecho will want to retain, so the following DROP LANGUAGE statement
16206 \qecho may fail, and that is okay.
16207
16208 DROP LANGUAGE plperl;
16209
16210 \qecho Evergreen depends heavily on each bibliographic record containing
16211 \qecho a 901 field with a subfield "c" to hold the record ID. The following
16212 \qecho query identifies the bibs that are missing 901s or whose first
16213 \qecho 901$c is not equal to the bib ID. This *will* take a long time in a
16214 \qecho big database; as the schema updates are over now, you can cancel this
16215 \qecho if you are in a rush.
16216
16217 SELECT id
16218   FROM biblio.record_entry
16219   WHERE (
16220     (XPATH('//marc:datafield[@tag="901"][1]/marc:subfield[@code="c"]/text()', marc::XML, ARRAY[ARRAY['marc', 'http://www.loc.gov/MARC21/slim']]))[1]::TEXT IS NULL
16221   OR
16222     (XPATH('//marc:datafield[@tag="901"][1]/marc:subfield[@code="c"]/text()', marc::XML, ARRAY[ARRAY['marc', 'http://www.loc.gov/MARC21/slim']]))[1]::TEXT <> id::TEXT)
16223   AND id > -1;
16224
16225 \qecho If there are records with missing or incorrect 901$c values, you can
16226 \qecho generally rely on the triggers in the biblio.record_entry table to
16227 \qecho populate the 901$c properly; for each offending record, run:
16228 \qecho   UPDATE biblio.record_entry SET marc = marc WHERE id = <id>;