]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
8329a410a13dcb6081815646a2695278a794cc40
[working/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-alpha2
2
3 -- Don't require use of -vegversion=something
4 \set eg_version '''2.2'''
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-beta2');
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
1979 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1980 BEGIN
1981     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1982         RETURN NEW;
1983     END IF;
1984
1985     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1986     IF TG_OP = 'UPDATE' THEN
1987         RETURN NEW;
1988     END IF;
1989     RETURN OLD;
1990 END;
1991 $$ LANGUAGE PLPGSQL;
1992
1993 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
1994 DECLARE
1995         auth    authority.record_entry%ROWTYPE;
1996         output  authority.full_rec%ROWTYPE;
1997         field   RECORD;
1998 BEGIN
1999         SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
2000
2001         FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
2002                 output.record := rid;
2003                 output.ind1 := field.ind1;
2004                 output.ind2 := field.ind2;
2005                 output.tag := field.tag;
2006                 output.subfield := field.subfield;
2007                 output.value := field.value;
2008
2009                 RETURN NEXT output;
2010         END LOOP;
2011 END;
2012 $func$ LANGUAGE PLPGSQL;
2013
2014 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
2015 DECLARE
2016         bib     biblio.record_entry%ROWTYPE;
2017         output  metabib.full_rec%ROWTYPE;
2018         field   RECORD;
2019 BEGIN
2020         SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
2021
2022         FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
2023                 output.record := rid;
2024                 output.ind1 := field.ind1;
2025                 output.ind2 := field.ind2;
2026                 output.tag := field.tag;
2027                 output.subfield := field.subfield;
2028                 output.value := field.value;
2029
2030                 RETURN NEXT output;
2031         END LOOP;
2032 END;
2033 $func$ LANGUAGE PLPGSQL;
2034
2035 -----------------------------------------------
2036 -- Seed data for import errors
2037 -----------------------------------------------
2038
2039 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'general.unknown', oils_i18n_gettext('general.unknown', 'Import or Overlay failed', 'vie', 'description') );
2040 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') );
2041 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') );
2042 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') );
2043 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') );
2044 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') );
2045 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') );
2046 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') );
2047 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.xml.malformed', oils_i18n_gettext('import.xml.malformed', 'Malformed record cause Import failure', 'vie', 'description') );
2048 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.xml.malformed', oils_i18n_gettext('overlay.xml.malformed', 'Malformed record cause Overlay failure', 'vie', 'description') );
2049 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.record.quality', oils_i18n_gettext('overlay.record.quality', 'New record had insufficient quality', 'vie', 'description') );
2050
2051
2052 ----------------------------------------------------------------
2053 -- Seed data for queued record/item exports
2054 ----------------------------------------------------------------
2055
2056 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2057         'vandelay.queued_bib_record.print',
2058         'vqbr', 
2059         oils_i18n_gettext(
2060             'vandelay.queued_bib_record.print',
2061             'Print output has been requested for records in an Importer Bib Queue.',
2062             'ath',
2063             'description'
2064         ), 
2065         FALSE
2066     )
2067     ,(
2068         'vandelay.queued_bib_record.csv',
2069         'vqbr', 
2070         oils_i18n_gettext(
2071             'vandelay.queued_bib_record.csv',
2072             'CSV output has been requested for records in an Importer Bib Queue.',
2073             'ath',
2074             'description'
2075         ), 
2076         FALSE
2077     )
2078     ,(
2079         'vandelay.queued_bib_record.email',
2080         'vqbr', 
2081         oils_i18n_gettext(
2082             'vandelay.queued_bib_record.email',
2083             'An email has been requested for records in an Importer Bib Queue.',
2084             'ath',
2085             'description'
2086         ), 
2087         FALSE
2088     )
2089     ,(
2090         'vandelay.queued_auth_record.print',
2091         'vqar', 
2092         oils_i18n_gettext(
2093             'vandelay.queued_auth_record.print',
2094             'Print output has been requested for records in an Importer Authority Queue.',
2095             'ath',
2096             'description'
2097         ), 
2098         FALSE
2099     )
2100     ,(
2101         'vandelay.queued_auth_record.csv',
2102         'vqar', 
2103         oils_i18n_gettext(
2104             'vandelay.queued_auth_record.csv',
2105             'CSV output has been requested for records in an Importer Authority Queue.',
2106             'ath',
2107             'description'
2108         ), 
2109         FALSE
2110     )
2111     ,(
2112         'vandelay.queued_auth_record.email',
2113         'vqar', 
2114         oils_i18n_gettext(
2115             'vandelay.queued_auth_record.email',
2116             'An email has been requested for records in an Importer Authority Queue.',
2117             'ath',
2118             'description'
2119         ), 
2120         FALSE
2121     )
2122     ,(
2123         'vandelay.import_items.print',
2124         'vii', 
2125         oils_i18n_gettext(
2126             'vandelay.import_items.print',
2127             'Print output has been requested for Import Items from records in an Importer Bib Queue.',
2128             'ath',
2129             'description'
2130         ), 
2131         FALSE
2132     )
2133     ,(
2134         'vandelay.import_items.csv',
2135         'vii', 
2136         oils_i18n_gettext(
2137             'vandelay.import_items.csv',
2138             'CSV output has been requested for Import Items from records in an Importer Bib Queue.',
2139             'ath',
2140             'description'
2141         ), 
2142         FALSE
2143     )
2144     ,(
2145         'vandelay.import_items.email',
2146         'vii', 
2147         oils_i18n_gettext(
2148             'vandelay.import_items.email',
2149             'An email has been requested for Import Items from records in an Importer Bib Queue.',
2150             'ath',
2151             'description'
2152         ), 
2153         FALSE
2154     )
2155 ;
2156
2157 INSERT INTO action_trigger.event_definition (
2158         id,
2159         active,
2160         owner,
2161         name,
2162         hook,
2163         validator,
2164         reactor,
2165         group_field,
2166         granularity,
2167         template
2168     ) VALUES (
2169         39,
2170         TRUE,
2171         1,
2172         'Print Output for Queued Bib Records',
2173         'vandelay.queued_bib_record.print',
2174         'NOOP_True',
2175         'ProcessTemplate',
2176         'queue.owner',
2177         'print-on-demand',
2178 $$
2179 [%- USE date -%]
2180 <pre>
2181 Queue ID: [% target.0.queue.id %]
2182 Queue Name: [% target.0.queue.name %]
2183 Queue Type: [% target.0.queue.queue_type %]
2184 Complete? [% target.0.queue.complete %]
2185
2186     [% FOR vqbr IN target %]
2187 =-=-=
2188  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2189  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2190  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2191  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2192  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2193  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2194  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2195  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2196  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2197  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2198  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2199  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2200  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2201  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2202  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2203
2204     [% END %]
2205 </pre>
2206 $$
2207     )
2208 ;
2209
2210 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2211     39, 'attributes')
2212     ,( 39, 'queue')
2213 ;
2214
2215 INSERT INTO action_trigger.event_definition (
2216         id,
2217         active,
2218         owner,
2219         name,
2220         hook,
2221         validator,
2222         reactor,
2223         group_field,
2224         granularity,
2225         template
2226     ) VALUES (
2227         40,
2228         TRUE,
2229         1,
2230         'CSV Output for Queued Bib Records',
2231         'vandelay.queued_bib_record.csv',
2232         'NOOP_True',
2233         'ProcessTemplate',
2234         'queue.owner',
2235         'print-on-demand',
2236 $$
2237 [%- USE date -%]
2238 "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"
2239 [% 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('"', '""') %]"
2240 [% END %]
2241 $$
2242     )
2243 ;
2244
2245 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2246     40, 'attributes')
2247     ,( 40, 'queue')
2248 ;
2249
2250 INSERT INTO action_trigger.event_definition (
2251         id,
2252         active,
2253         owner,
2254         name,
2255         hook,
2256         validator,
2257         reactor,
2258         group_field,
2259         granularity,
2260         template
2261     ) VALUES (
2262         41,
2263         TRUE,
2264         1,
2265         'Email Output for Queued Bib Records',
2266         'vandelay.queued_bib_record.email',
2267         'NOOP_True',
2268         'SendEmail',
2269         'queue.owner',
2270         NULL,
2271 $$
2272 [%- USE date -%]
2273 [%- SET user = target.0.queue.owner -%]
2274 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2275 From: [%- params.sender_email || default_sender %]
2276 Subject: Bibs from Import Queue
2277
2278 Queue ID: [% target.0.queue.id %]
2279 Queue Name: [% target.0.queue.name %]
2280 Queue Type: [% target.0.queue.queue_type %]
2281 Complete? [% target.0.queue.complete %]
2282
2283     [% FOR vqbr IN target %]
2284 =-=-=
2285  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2286  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2287  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2288  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2289  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2290  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2291  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2292  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2293  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2294  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2295  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2296  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2297  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2298  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2299  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2300
2301     [% END %]
2302
2303 $$
2304     )
2305 ;
2306
2307 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2308     41, 'attributes')
2309     ,( 41, 'queue')
2310     ,( 41, 'queue.owner')
2311 ;
2312
2313 INSERT INTO action_trigger.event_definition (
2314         id,
2315         active,
2316         owner,
2317         name,
2318         hook,
2319         validator,
2320         reactor,
2321         group_field,
2322         granularity,
2323         template
2324     ) VALUES (
2325         42,
2326         TRUE,
2327         1,
2328         'Print Output for Queued Authority Records',
2329         'vandelay.queued_auth_record.print',
2330         'NOOP_True',
2331         'ProcessTemplate',
2332         'queue.owner',
2333         'print-on-demand',
2334 $$
2335 [%- USE date -%]
2336 <pre>
2337 Queue ID: [% target.0.queue.id %]
2338 Queue Name: [% target.0.queue.name %]
2339 Queue Type: [% target.0.queue.queue_type %]
2340 Complete? [% target.0.queue.complete %]
2341
2342     [% FOR vqar IN target %]
2343 =-=-=
2344  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2345
2346     [% END %]
2347 </pre>
2348 $$
2349     )
2350 ;
2351
2352 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2353     42, 'attributes')
2354     ,( 42, 'queue')
2355 ;
2356
2357 INSERT INTO action_trigger.event_definition (
2358         id,
2359         active,
2360         owner,
2361         name,
2362         hook,
2363         validator,
2364         reactor,
2365         group_field,
2366         granularity,
2367         template
2368     ) VALUES (
2369         43,
2370         TRUE,
2371         1,
2372         'CSV Output for Queued Authority Records',
2373         'vandelay.queued_auth_record.csv',
2374         'NOOP_True',
2375         'ProcessTemplate',
2376         'queue.owner',
2377         'print-on-demand',
2378 $$
2379 [%- USE date -%]
2380 "Record Identifier"
2381 [% FOR vqar IN target %]"[% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) | replace('"', '""') %]"
2382 [% END %]
2383 $$
2384     )
2385 ;
2386
2387 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2388     43, 'attributes')
2389     ,( 43, 'queue')
2390 ;
2391
2392 INSERT INTO action_trigger.event_definition (
2393         id,
2394         active,
2395         owner,
2396         name,
2397         hook,
2398         validator,
2399         reactor,
2400         group_field,
2401         granularity,
2402         template
2403     ) VALUES (
2404         44,
2405         TRUE,
2406         1,
2407         'Email Output for Queued Authority Records',
2408         'vandelay.queued_auth_record.email',
2409         'NOOP_True',
2410         'SendEmail',
2411         'queue.owner',
2412         NULL,
2413 $$
2414 [%- USE date -%]
2415 [%- SET user = target.0.queue.owner -%]
2416 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2417 From: [%- params.sender_email || default_sender %]
2418 Subject: Authorities from Import Queue
2419
2420 Queue ID: [% target.0.queue.id %]
2421 Queue Name: [% target.0.queue.name %]
2422 Queue Type: [% target.0.queue.queue_type %]
2423 Complete? [% target.0.queue.complete %]
2424
2425     [% FOR vqar IN target %]
2426 =-=-=
2427  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2428
2429     [% END %]
2430
2431 $$
2432     )
2433 ;
2434
2435 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2436     44, 'attributes')
2437     ,( 44, 'queue')
2438     ,( 44, 'queue.owner')
2439 ;
2440
2441 INSERT INTO action_trigger.event_definition (
2442         id,
2443         active,
2444         owner,
2445         name,
2446         hook,
2447         validator,
2448         reactor,
2449         group_field,
2450         granularity,
2451         template
2452     ) VALUES (
2453         45,
2454         TRUE,
2455         1,
2456         'Print Output for Import Items from Queued Bib Records',
2457         'vandelay.import_items.print',
2458         'NOOP_True',
2459         'ProcessTemplate',
2460         'record.queue.owner',
2461         'print-on-demand',
2462 $$
2463 [%- USE date -%]
2464 <pre>
2465 Queue ID: [% target.0.record.queue.id %]
2466 Queue Name: [% target.0.record.queue.name %]
2467 Queue Type: [% target.0.record.queue.queue_type %]
2468 Complete? [% target.0.record.queue.complete %]
2469
2470     [% FOR vii IN target %]
2471 =-=-=
2472  Import Item ID         | [% vii.id %]
2473  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2474  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2475  Attribute Definition   | [% vii.definition %]
2476  Import Error           | [% vii.import_error %]
2477  Import Error Detail    | [% vii.error_detail %]
2478  Owning Library         | [% vii.owning_lib %]
2479  Circulating Library    | [% vii.circ_lib %]
2480  Call Number            | [% vii.call_number %]
2481  Copy Number            | [% vii.copy_number %]
2482  Status                 | [% vii.status.name %]
2483  Shelving Location      | [% vii.location.name %]
2484  Circulate              | [% vii.circulate %]
2485  Deposit                | [% vii.deposit %]
2486  Deposit Amount         | [% vii.deposit_amount %]
2487  Reference              | [% vii.ref %]
2488  Holdable               | [% vii.holdable %]
2489  Price                  | [% vii.price %]
2490  Barcode                | [% vii.barcode %]
2491  Circulation Modifier   | [% vii.circ_modifier %]
2492  Circulate As MARC Type | [% vii.circ_as_type %]
2493  Alert Message          | [% vii.alert_message %]
2494  Public Note            | [% vii.pub_note %]
2495  Private Note           | [% vii.priv_note %]
2496  OPAC Visible           | [% vii.opac_visible %]
2497
2498     [% END %]
2499 </pre>
2500 $$
2501     )
2502 ;
2503
2504 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2505     45, 'record')
2506     ,( 45, 'record.attributes')
2507     ,( 45, 'record.queue')
2508     ,( 45, 'record.queue.owner')
2509 ;
2510
2511 INSERT INTO action_trigger.event_definition (
2512         id,
2513         active,
2514         owner,
2515         name,
2516         hook,
2517         validator,
2518         reactor,
2519         group_field,
2520         granularity,
2521         template
2522     ) VALUES (
2523         46,
2524         TRUE,
2525         1,
2526         'CSV Output for Import Items from Queued Bib Records',
2527         'vandelay.import_items.csv',
2528         'NOOP_True',
2529         'ProcessTemplate',
2530         'record.queue.owner',
2531         'print-on-demand',
2532 $$
2533 [%- USE date -%]
2534 "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"
2535 [% 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('"', '""') %]"
2536 [% END %]
2537 $$
2538     )
2539 ;
2540
2541 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2542     46, 'record')
2543     ,( 46, 'record.attributes')
2544     ,( 46, 'record.queue')
2545     ,( 46, 'record.queue.owner')
2546 ;
2547
2548 INSERT INTO action_trigger.event_definition (
2549         id,
2550         active,
2551         owner,
2552         name,
2553         hook,
2554         validator,
2555         reactor,
2556         group_field,
2557         granularity,
2558         template
2559     ) VALUES (
2560         47,
2561         TRUE,
2562         1,
2563         'Email Output for Import Items from Queued Bib Records',
2564         'vandelay.import_items.email',
2565         'NOOP_True',
2566         'SendEmail',
2567         'record.queue.owner',
2568         NULL,
2569 $$
2570 [%- USE date -%]
2571 [%- SET user = target.0.record.queue.owner -%]
2572 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2573 From: [%- params.sender_email || default_sender %]
2574 Subject: Import Items from Import Queue
2575
2576 Queue ID: [% target.0.record.queue.id %]
2577 Queue Name: [% target.0.record.queue.name %]
2578 Queue Type: [% target.0.record.queue.queue_type %]
2579 Complete? [% target.0.record.queue.complete %]
2580
2581     [% FOR vii IN target %]
2582 =-=-=
2583  Import Item ID         | [% vii.id %]
2584  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2585  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2586  Attribute Definition   | [% vii.definition %]
2587  Import Error           | [% vii.import_error %]
2588  Import Error Detail    | [% vii.error_detail %]
2589  Owning Library         | [% vii.owning_lib %]
2590  Circulating Library    | [% vii.circ_lib %]
2591  Call Number            | [% vii.call_number %]
2592  Copy Number            | [% vii.copy_number %]
2593  Status                 | [% vii.status.name %]
2594  Shelving Location      | [% vii.location.name %]
2595  Circulate              | [% vii.circulate %]
2596  Deposit                | [% vii.deposit %]
2597  Deposit Amount         | [% vii.deposit_amount %]
2598  Reference              | [% vii.ref %]
2599  Holdable               | [% vii.holdable %]
2600  Price                  | [% vii.price %]
2601  Barcode                | [% vii.barcode %]
2602  Circulation Modifier   | [% vii.circ_modifier %]
2603  Circulate As MARC Type | [% vii.circ_as_type %]
2604  Alert Message          | [% vii.alert_message %]
2605  Public Note            | [% vii.pub_note %]
2606  Private Note           | [% vii.priv_note %]
2607  OPAC Visible           | [% vii.opac_visible %]
2608
2609     [% END %]
2610 $$
2611     )
2612 ;
2613
2614 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2615     47, 'record')
2616     ,( 47, 'record.attributes')
2617     ,( 47, 'record.queue')
2618     ,( 47, 'record.queue.owner')
2619 ;
2620
2621
2622
2623 SELECT evergreen.upgrade_deps_block_check('0574', :eg_version);
2624
2625 UPDATE action_trigger.event_definition SET template =
2626 $$
2627 [%- USE date -%]
2628 <style>
2629     table { border-collapse: collapse; }
2630     td { padding: 5px; border-bottom: 1px solid #888; }
2631     th { font-weight: bold; }
2632 </style>
2633 [%
2634     # Sort the holds into copy-location buckets
2635     # In the main print loop, sort each bucket by callnumber before printing
2636     SET holds_list = [];
2637     SET loc_data = [];
2638     SET current_location = target.0.current_copy.location.id;
2639     FOR hold IN target;
2640         IF current_location != hold.current_copy.location.id;
2641             SET current_location = hold.current_copy.location.id;
2642             holds_list.push(loc_data);
2643             SET loc_data = [];
2644         END;
2645         SET hold_data = {
2646             'hold' => hold,
2647             'callnumber' => hold.current_copy.call_number.label
2648         };
2649         loc_data.push(hold_data);
2650     END;
2651     holds_list.push(loc_data)
2652 %]
2653 <table>
2654     <thead>
2655         <tr>
2656             <th>Title</th>
2657             <th>Author</th>
2658             <th>Shelving Location</th>
2659             <th>Call Number</th>
2660             <th>Barcode/Part</th>
2661             <th>Patron</th>
2662         </tr>
2663     </thead>
2664     <tbody>
2665     [% FOR loc_data IN holds_list  %]
2666         [% FOR hold_data IN loc_data.sort('callnumber') %]
2667             [%
2668                 SET hold = hold_data.hold;
2669                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
2670             %]
2671             <tr>
2672                 <td>[% copy_data.title | truncate %]</td>
2673                 <td>[% copy_data.author | truncate %]</td>
2674                 <td>[% hold.current_copy.location.name %]</td>
2675                 <td>[% hold.current_copy.call_number.label %]</td>
2676                 <td>[% hold.current_copy.barcode %]
2677                     [% FOR part IN hold.current_copy.parts %]
2678                        [% part.part.label %]
2679                     [% END %]
2680                 </td>
2681                 <td>[% hold.usr.card.barcode %]</td>
2682             </tr>
2683         [% END %]
2684     [% END %]
2685     <tbody>
2686 </table>
2687 $$
2688     WHERE id = 35;
2689
2690 INSERT INTO action_trigger.environment (
2691         event_def,
2692         path
2693     ) VALUES
2694         (35, 'current_copy.parts'),
2695         (35, 'current_copy.parts.part')
2696 ;
2697
2698
2699 -- Evergreen DB patch XXXX.schema.authority-control-sets.sql
2700 --
2701 -- Schema upgrade to add Authority Control Set functionality
2702 --
2703
2704
2705 -- check whether patch can be applied
2706 SELECT evergreen.upgrade_deps_block_check('0575', :eg_version);
2707
2708 CREATE TABLE authority.control_set (
2709     id          SERIAL  PRIMARY KEY,
2710     name        TEXT    NOT NULL UNIQUE, -- i18n
2711     description TEXT                     -- i18n
2712 );
2713
2714 CREATE TABLE authority.control_set_authority_field (
2715     id          SERIAL  PRIMARY KEY,
2716     main_entry  INT     REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2717     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2718     tag         CHAR(3) NOT NULL,
2719     nfi CHAR(1),
2720     sf_list     TEXT    NOT NULL,
2721     name        TEXT    NOT NULL, -- i18n
2722     description TEXT              -- i18n
2723 );
2724
2725 CREATE TABLE authority.control_set_bib_field (
2726     id              SERIAL  PRIMARY KEY,
2727     authority_field INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2728     tag             CHAR(3) NOT NULL
2729 );
2730
2731 CREATE TABLE authority.thesaurus (
2732     code        TEXT    PRIMARY KEY,     -- MARC21 thesaurus code
2733     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2734     name        TEXT    NOT NULL UNIQUE, -- i18n
2735     description TEXT                     -- i18n
2736 );
2737
2738 CREATE TABLE authority.browse_axis (
2739     code        TEXT    PRIMARY KEY,
2740     name        TEXT    UNIQUE NOT NULL, -- i18n
2741     sorter      TEXT    REFERENCES config.record_attr_definition (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2742     description TEXT
2743 );
2744
2745 CREATE TABLE authority.browse_axis_authority_field_map (
2746     id          SERIAL  PRIMARY KEY,
2747     axis        TEXT    NOT NULL REFERENCES authority.browse_axis (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2748     field       INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
2749 );
2750
2751 ALTER TABLE authority.record_entry ADD COLUMN control_set INT REFERENCES authority.control_set (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED;
2752 ALTER TABLE authority.rec_descriptor DROP COLUMN char_encoding, ADD COLUMN encoding_level TEXT, ADD COLUMN thesaurus TEXT;
2753
2754 CREATE INDEX authority_full_rec_value_index ON authority.full_rec (value);
2755 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);
2756  
2757 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
2758 DECLARE
2759     acsaf           authority.control_set_authority_field%ROWTYPE;
2760     tag_used        TEXT;
2761     sf              TEXT;
2762     thes_code       TEXT;
2763     cset            INT;
2764     heading_text    TEXT;
2765     tmp_text        TEXT;
2766 BEGIN
2767     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
2768     IF thes_code IS NULL THEN
2769         thes_code := '|';
2770     END IF;
2771
2772     SELECT control_set INTO cset FROM authority.thesaurus WHERE code = thes_code;
2773     IF NOT FOUND THEN
2774         cset = 1;
2775     END IF;
2776
2777     heading_text := '';
2778     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
2779         tag_used := acsaf.tag;
2780         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
2781             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
2782             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
2783                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
2784             END IF;
2785         END LOOP;
2786         EXIT WHEN heading_text <> '';
2787     END LOOP;
2788  
2789     IF thes_code = 'z' THEN
2790         thes_code := oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml);
2791     END IF;
2792
2793     IF heading_text <> '' THEN
2794         IF no_thesaurus IS TRUE THEN
2795             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
2796         ELSE
2797             heading_text := tag_used || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
2798         END IF;
2799     ELSE
2800         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
2801     END IF;
2802
2803     RETURN heading_text;
2804 END;
2805 $func$ LANGUAGE PLPGSQL IMMUTABLE;
2806
2807 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2808     SELECT authority.normalize_heading($1, TRUE);
2809 $func$ LANGUAGE SQL IMMUTABLE;
2810
2811 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2812     SELECT authority.normalize_heading($1, FALSE);
2813 $func$ LANGUAGE SQL IMMUTABLE;
2814
2815 CREATE OR REPLACE VIEW authority.tracing_links AS
2816     SELECT  main.record AS record,
2817             main.id AS main_id,
2818             main.tag AS main_tag,
2819             oils_xpath_string('//*[@tag="'||main.tag||'"]/*[local-name()="subfield"]', are.marc) AS main_value,
2820             substr(link.value,1,1) AS relationship,
2821             substr(link.value,2,1) AS use_restriction,
2822             substr(link.value,3,1) AS deprecation,
2823             substr(link.value,4,1) AS display_restriction,
2824             link.id AS link_id,
2825             link.tag AS link_tag,
2826             oils_xpath_string('//*[@tag="'||link.tag||'"]/*[local-name()="subfield"]', are.marc) AS link_value,
2827             authority.normalize_heading(are.marc) AS normalized_main_value
2828       FROM  authority.full_rec main
2829             JOIN authority.record_entry are ON (main.record = are.id)
2830             JOIN authority.control_set_authority_field main_entry
2831                 ON (main_entry.tag = main.tag
2832                     AND main_entry.main_entry IS NULL
2833                     AND main.subfield = 'a' )
2834             JOIN authority.control_set_authority_field sub_entry
2835                 ON (main_entry.id = sub_entry.main_entry)
2836             JOIN authority.full_rec link
2837                 ON (link.record = main.record
2838                     AND link.tag = sub_entry.tag
2839                     AND link.subfield = 'w' );
2840  
2841 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
2842 DECLARE
2843     cset                INT;
2844     main_entry          authority.control_set_authority_field%ROWTYPE;
2845     bib_field           authority.control_set_bib_field%ROWTYPE;
2846     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
2847     replace_data        XML[] DEFAULT '{}'::XML[];
2848     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
2849     auth_field          XML[];
2850 BEGIN
2851     IF auth_id IS NULL THEN
2852         RETURN NULL;
2853     END IF;
2854
2855     -- Default to the LoC controll set
2856     SELECT COALESCE(control_set,1) INTO cset FROM authority.record_entry WHERE id = auth_id;
2857
2858     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
2859         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
2860         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
2861             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
2862                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
2863                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
2864             END LOOP;
2865             EXIT;
2866         END IF;
2867     END LOOP;
2868  
2869     RETURN XMLELEMENT(
2870         name record,
2871         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
2872         XMLELEMENT( name leader, '00881nam a2200193   4500'),
2873         replace_data,
2874         XMLELEMENT(
2875             name datafield,
2876             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
2877             XMLELEMENT(
2878                 name subfield,
2879                 XMLATTRIBUTES('r' AS code),
2880                 ARRAY_TO_STRING(replace_rules,',')
2881             )
2882         )
2883     )::TEXT;
2884 END;
2885 $f$ STABLE LANGUAGE PLPGSQL;
2886  
2887 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
2888     SELECT authority.generate_overlay_template( marc ) FROM authority.record_entry WHERE id = $1;
2889 $func$ LANGUAGE SQL;
2890  
2891 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
2892
2893     use MARC::Record;
2894     use MARC::File::XML (BinaryEncoding => 'UTF-8');
2895     use MARC::Charset;
2896     use strict;
2897
2898     MARC::Charset->assume_unicode(1);
2899
2900     my $target_xml = shift;
2901     my $source_xml = shift;
2902     my $field_spec = shift;
2903     my $force_add = shift || 0;
2904
2905     my $target_r = MARC::Record->new_from_xml( $target_xml );
2906     my $source_r = MARC::Record->new_from_xml( $source_xml );
2907
2908     return $target_xml unless ($target_r && $source_r);
2909
2910     my @field_list = split(',', $field_spec);
2911
2912     my %fields;
2913     for my $f (@field_list) {
2914         $f =~ s/^\s*//; $f =~ s/\s*$//;
2915         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
2916             my $field = $1;
2917             $field =~ s/\s+//;
2918             my $sf = $2;
2919             $sf =~ s/\s+//;
2920             my $match = $3;
2921             $match =~ s/^\s*//; $match =~ s/\s*$//;
2922             $fields{$field} = { sf => [ split('', $sf) ] };
2923             if ($match) {
2924                 my ($msf,$mre) = split('~', $match);
2925                 if (length($msf) > 0 and length($mre) > 0) {
2926                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
2927                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
2928                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
2929                 }
2930             }
2931         }
2932     }
2933
2934     for my $f ( keys %fields) {
2935         if ( @{$fields{$f}{sf}} ) {
2936             for my $from_field ($source_r->field( $f )) {
2937                 my @tos = $target_r->field( $f );
2938                 if (!@tos) {
2939                     next if (exists($fields{$f}{match}) and !$force_add);
2940                     my @new_fields = map { $_->clone } $source_r->field( $f );
2941                     $target_r->insert_fields_ordered( @new_fields );
2942                 } else {
2943                     for my $to_field (@tos) {
2944                         if (exists($fields{$f}{match})) {
2945                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
2946                         }
2947                         my @new_sf = map { ($_ => $from_field->subfield($_)) } grep { defined($from_field->subfield($_)) } @{$fields{$f}{sf}};
2948                         $to_field->add_subfields( @new_sf );
2949                     }
2950                 }
2951             }
2952         } else {
2953             my @new_fields = map { $_->clone } $source_r->field( $f );
2954             $target_r->insert_fields_ordered( @new_fields );
2955         }
2956     }
2957
2958     $target_xml = $target_r->as_xml_record;
2959     $target_xml =~ s/^<\?.+?\?>$//mo;
2960     $target_xml =~ s/\n//sgo;
2961     $target_xml =~ s/>\s+</></sgo;
2962
2963     return $target_xml;
2964
2965 $_$ LANGUAGE PLPERLU;
2966
2967
2968 CREATE INDEX by_heading ON authority.record_entry (authority.simple_normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE;
2969
2970 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, search_field, facet_field) VALUES
2971     (28, 'identifier', 'authority_id', oils_i18n_gettext(28, 'Authority Record ID', 'cmf', 'label'), 'marcxml', '//marc:datafield/marc:subfield[@code="0"]', FALSE, TRUE);
2972  
2973 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('AUT','z',' ');
2974 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MFHD','uvxy',' ');
2975  
2976 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'AUT', 17, 1, ' ');
2977 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Subj', '008', 'AUT', 11, 1, '|');
2978 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('RecStat', 'ldr', 'AUT', 5, 1, 'n');
2979  
2980 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
2981     SELECT  m.id,
2982             i.id,
2983             -1
2984       FROM  config.metabib_field m,
2985             config.index_normalizer i
2986       WHERE i.func = 'remove_paren_substring'
2987             AND m.id IN (28);
2988
2989 SELECT SETVAL('authority.control_set_id_seq'::TEXT, 100);
2990 SELECT SETVAL('authority.control_set_authority_field_id_seq'::TEXT, 1000);
2991 SELECT SETVAL('authority.control_set_bib_field_id_seq'::TEXT, 1000);
2992
2993 INSERT INTO authority.control_set (id, name, description) VALUES (
2994     1,
2995     oils_i18n_gettext('1','LoC','acs','name'),
2996     oils_i18n_gettext('1','Library of Congress standard authority record control semantics','acs','description')
2997 );
2998
2999 INSERT INTO authority.control_set_authority_field (id, control_set, main_entry, tag, sf_list, name) VALUES
3000
3001 -- Main entries
3002     (1, 1, NULL, '100', 'abcdefklmnopqrstvxyz', oils_i18n_gettext('1','Heading -- Personal Name','acsaf','name')),
3003     (2, 1, NULL, '110', 'abcdefgklmnoprstvxyz', oils_i18n_gettext('2','Heading -- Corporate Name','acsaf','name')),
3004     (3, 1, NULL, '111', 'acdefgklnpqstvxyz', oils_i18n_gettext('3','Heading -- Meeting Name','acsaf','name')),
3005     (4, 1, NULL, '130', 'adfgklmnoprstvxyz', oils_i18n_gettext('4','Heading -- Uniform Title','acsaf','name')),
3006     (5, 1, NULL, '150', 'abvxyz', oils_i18n_gettext('5','Heading -- Topical Term','acsaf','name')),
3007     (6, 1, NULL, '151', 'avxyz', oils_i18n_gettext('6','Heading -- Geographic Name','acsaf','name')),
3008     (7, 1, NULL, '155', 'avxyz', oils_i18n_gettext('7','Heading -- Genre/Form Term','acsaf','name')),
3009     (8, 1, NULL, '180', 'vxyz', oils_i18n_gettext('8','Heading -- General Subdivision','acsaf','name')),
3010     (9, 1, NULL, '181', 'vxyz', oils_i18n_gettext('9','Heading -- Geographic Subdivision','acsaf','name')),
3011     (10, 1, NULL, '182', 'vxyz', oils_i18n_gettext('10','Heading -- Chronological Subdivision','acsaf','name')),
3012     (11, 1, NULL, '185', 'vxyz', oils_i18n_gettext('11','Heading -- Form Subdivision','acsaf','name')),
3013     (12, 1, NULL, '148', 'avxyz', oils_i18n_gettext('12','Heading -- Chronological Term','acsaf','name')),
3014
3015 -- See Also From tracings
3016     (21, 1, 1, '500', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('21','See Also From Tracing -- Personal Name','acsaf','name')),
3017     (22, 1, 2, '510', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('22','See Also From Tracing -- Corporate Name','acsaf','name')),
3018     (23, 1, 3, '511', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('23','See Also From Tracing -- Meeting Name','acsaf','name')),
3019     (24, 1, 4, '530', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('24','See Also From Tracing -- Uniform Title','acsaf','name')),
3020     (25, 1, 5, '550', 'abivwxyz4', oils_i18n_gettext('25','See Also From Tracing -- Topical Term','acsaf','name')),
3021     (26, 1, 6, '551', 'aivwxyz4', oils_i18n_gettext('26','See Also From Tracing -- Geographic Name','acsaf','name')),
3022     (27, 1, 7, '555', 'aivwxyz4', oils_i18n_gettext('27','See Also From Tracing -- Genre/Form Term','acsaf','name')),
3023     (28, 1, 8, '580', 'ivwxyz4', oils_i18n_gettext('28','See Also From Tracing -- General Subdivision','acsaf','name')),
3024     (29, 1, 9, '581', 'ivwxyz4', oils_i18n_gettext('29','See Also From Tracing -- Geographic Subdivision','acsaf','name')),
3025     (30, 1, 10, '582', 'ivwxyz4', oils_i18n_gettext('30','See Also From Tracing -- Chronological Subdivision','acsaf','name')),
3026     (31, 1, 11, '585', 'ivwxyz4', oils_i18n_gettext('31','See Also From Tracing -- Form Subdivision','acsaf','name')),
3027     (32, 1, 12, '548', 'aivwxyz4', oils_i18n_gettext('32','See Also From Tracing -- Chronological Term','acsaf','name')),
3028
3029 -- Linking entries
3030     (41, 1, 1, '700', 'abcdefghjklmnopqrstvwxyz25', oils_i18n_gettext('41','Established Heading Linking Entry -- Personal Name','acsaf','name')),
3031     (42, 1, 2, '710', 'abcdefghklmnoprstvwxyz25', oils_i18n_gettext('42','Established Heading Linking Entry -- Corporate Name','acsaf','name')),
3032     (43, 1, 3, '711', 'acdefghklnpqstvwxyz25', oils_i18n_gettext('43','Established Heading Linking Entry -- Meeting Name','acsaf','name')),
3033     (44, 1, 4, '730', 'adfghklmnoprstvwxyz25', oils_i18n_gettext('44','Established Heading Linking Entry -- Uniform Title','acsaf','name')),
3034     (45, 1, 5, '750', 'abvwxyz25', oils_i18n_gettext('45','Established Heading Linking Entry -- Topical Term','acsaf','name')),
3035     (46, 1, 6, '751', 'avwxyz25', oils_i18n_gettext('46','Established Heading Linking Entry -- Geographic Name','acsaf','name')),
3036     (47, 1, 7, '755', 'avwxyz25', oils_i18n_gettext('47','Established Heading Linking Entry -- Genre/Form Term','acsaf','name')),
3037     (48, 1, 8, '780', 'vwxyz25', oils_i18n_gettext('48','Subdivision Linking Entry -- General Subdivision','acsaf','name')),
3038     (49, 1, 9, '781', 'vwxyz25', oils_i18n_gettext('49','Subdivision Linking Entry -- Geographic Subdivision','acsaf','name')),
3039     (50, 1, 10, '782', 'vwxyz25', oils_i18n_gettext('50','Subdivision Linking Entry -- Chronological Subdivision','acsaf','name')),
3040     (51, 1, 11, '785', 'vwxyz25', oils_i18n_gettext('51','Subdivision Linking Entry -- Form Subdivision','acsaf','name')),
3041     (52, 1, 12, '748', 'avwxyz25', oils_i18n_gettext('52','Established Heading Linking Entry -- Chronological Term','acsaf','name')),
3042
3043 -- See From tracings
3044     (61, 1, 1, '400', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('61','See Also Tracing -- Personal Name','acsaf','name')),
3045     (62, 1, 2, '410', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('62','See Also Tracing -- Corporate Name','acsaf','name')),
3046     (63, 1, 3, '411', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('63','See Also Tracing -- Meeting Name','acsaf','name')),
3047     (64, 1, 4, '430', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('64','See Also Tracing -- Uniform Title','acsaf','name')),
3048     (65, 1, 5, '450', 'abivwxyz4', oils_i18n_gettext('65','See Also Tracing -- Topical Term','acsaf','name')),
3049     (66, 1, 6, '451', 'aivwxyz4', oils_i18n_gettext('66','See Also Tracing -- Geographic Name','acsaf','name')),
3050     (67, 1, 7, '455', 'aivwxyz4', oils_i18n_gettext('67','See Also Tracing -- Genre/Form Term','acsaf','name')),
3051     (68, 1, 8, '480', 'ivwxyz4', oils_i18n_gettext('68','See Also Tracing -- General Subdivision','acsaf','name')),
3052     (69, 1, 9, '481', 'ivwxyz4', oils_i18n_gettext('69','See Also Tracing -- Geographic Subdivision','acsaf','name')),
3053     (70, 1, 10, '482', 'ivwxyz4', oils_i18n_gettext('70','See Also Tracing -- Chronological Subdivision','acsaf','name')),
3054     (71, 1, 11, '485', 'ivwxyz4', oils_i18n_gettext('71','See Also Tracing -- Form Subdivision','acsaf','name')),
3055     (72, 1, 12, '448', 'aivwxyz4', oils_i18n_gettext('72','See Also Tracing -- Chronological Term','acsaf','name'));
3056
3057 INSERT INTO authority.browse_axis (code,name,description,sorter) VALUES
3058     ('title','Title','Title axis','titlesort'),
3059     ('author','Author','Author axis','titlesort'),
3060     ('subject','Subject','Subject axis','titlesort'),
3061     ('topic','Topic','Topic Subject axis','titlesort');
3062
3063 INSERT INTO authority.browse_axis_authority_field_map (axis,field) VALUES
3064     ('author',  1 ),
3065     ('author',  2 ),
3066     ('author',  3 ),
3067     ('title',   4 ),
3068     ('topic',   5 ),
3069     ('subject', 5 ),
3070     ('subject', 6 ),
3071     ('subject', 7 ),
3072     ('subject', 12);
3073
3074 INSERT INTO authority.control_set_bib_field (tag, authority_field) 
3075     SELECT '100', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3076         UNION
3077     SELECT '600', id FROM authority.control_set_authority_field WHERE tag IN ('100','180','181','182','185')
3078         UNION
3079     SELECT '700', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3080         UNION
3081     SELECT '800', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3082         UNION
3083
3084     SELECT '110', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3085         UNION
3086     SELECT '610', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3087         UNION
3088     SELECT '710', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3089         UNION
3090     SELECT '810', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3091         UNION
3092
3093     SELECT '111', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3094         UNION
3095     SELECT '611', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3096         UNION
3097     SELECT '711', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3098         UNION
3099     SELECT '811', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3100         UNION
3101
3102     SELECT '130', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3103         UNION
3104     SELECT '240', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3105         UNION
3106     SELECT '630', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3107         UNION
3108     SELECT '730', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3109         UNION
3110     SELECT '830', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3111         UNION
3112
3113     SELECT '648', id FROM authority.control_set_authority_field WHERE tag IN ('148')
3114         UNION
3115
3116     SELECT '650', id FROM authority.control_set_authority_field WHERE tag IN ('150','180','181','182','185')
3117         UNION
3118     SELECT '651', id FROM authority.control_set_authority_field WHERE tag IN ('151','180','181','182','185')
3119         UNION
3120     SELECT '655', id FROM authority.control_set_authority_field WHERE tag IN ('155','180','181','182','185')
3121 ;
3122
3123 INSERT INTO authority.thesaurus (code, name, control_set) VALUES
3124     ('a', oils_i18n_gettext('a','Library of Congress Subject Headings','at','name'), 1),
3125     ('b', oils_i18n_gettext('b',$$LC subject headings for children's literature$$,'at','name'), 1), -- silly vim '
3126     ('c', oils_i18n_gettext('c','Medical Subject Headings','at','name'), 1),
3127     ('d', oils_i18n_gettext('d','National Agricultural Library subject authority file','at','name'), 1),
3128     ('k', oils_i18n_gettext('k','Canadian Subject Headings','at','name'), 1),
3129     ('n', oils_i18n_gettext('n','Not applicable','at','name'), 1),
3130     ('r', oils_i18n_gettext('r','Art and Architecture Thesaurus','at','name'), 1),
3131     ('s', oils_i18n_gettext('s','Sears List of Subject Headings','at','name'), 1),
3132     ('v', oils_i18n_gettext('v','Repertoire de vedettes-matiere','at','name'), 1),
3133     ('z', oils_i18n_gettext('z','Other','at','name'), 1),
3134     ('|', oils_i18n_gettext('|','No attempt to code','at','name'), 1);
3135  
3136 CREATE OR REPLACE FUNCTION authority.map_thesaurus_to_control_set () RETURNS TRIGGER AS $func$
3137 BEGIN
3138     IF NEW.control_set IS NULL THEN
3139         SELECT  control_set INTO NEW.control_set
3140           FROM  authority.thesaurus
3141           WHERE vandelay.marc21_extract_fixed_field(NEW.marc,'Subj') = code;
3142     END IF;
3143
3144     RETURN NEW;
3145 END;
3146 $func$ LANGUAGE PLPGSQL;
3147
3148 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 ();
3149
3150 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
3151 BEGIN
3152     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
3153     INSERT INTO authority.rec_descriptor (record, record_status, encoding_level, thesaurus)
3154         SELECT  auth_id,
3155                 vandelay.marc21_extract_fixed_field(marc,'RecStat'),
3156                 vandelay.marc21_extract_fixed_field(marc,'ELvl'),
3157                 vandelay.marc21_extract_fixed_field(marc,'Subj')
3158           FROM  authority.record_entry
3159           WHERE id = auth_id;
3160      RETURN;
3161  END;
3162  $func$ LANGUAGE PLPGSQL;
3163
3164 --Removed dupe authority.indexing_ingest_or_delete
3165
3166 -- Evergreen DB patch 0577.schema.vandelay-item-import-copy-loc-ancestors.sql
3167 --
3168 -- Ingest items copy location inheritance
3169 --
3170
3171 -- check whether patch can be applied
3172 SELECT evergreen.upgrade_deps_block_check('0577', :eg_version); -- berick
3173
3174 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3175 DECLARE
3176
3177     owning_lib      TEXT;
3178     circ_lib        TEXT;
3179     call_number     TEXT;
3180     copy_number     TEXT;
3181     status          TEXT;
3182     location        TEXT;
3183     circulate       TEXT;
3184     deposit         TEXT;
3185     deposit_amount  TEXT;
3186     ref             TEXT;
3187     holdable        TEXT;
3188     price           TEXT;
3189     barcode         TEXT;
3190     circ_modifier   TEXT;
3191     circ_as_type    TEXT;
3192     alert_message   TEXT;
3193     opac_visible    TEXT;
3194     pub_note        TEXT;
3195     priv_note       TEXT;
3196
3197     attr_def        RECORD;
3198     tmp_attr_set    RECORD;
3199     attr_set        vandelay.import_item%ROWTYPE;
3200
3201     xpath           TEXT;
3202
3203 BEGIN
3204
3205     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3206
3207     IF FOUND THEN
3208
3209         attr_set.definition := attr_def.id;
3210
3211         -- Build the combined XPath
3212
3213         owning_lib :=
3214             CASE
3215                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3216                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3217                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3218             END;
3219
3220         circ_lib :=
3221             CASE
3222                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3223                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3224                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3225             END;
3226
3227         call_number :=
3228             CASE
3229                 WHEN attr_def.call_number IS NULL THEN 'null()'
3230                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3231                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3232             END;
3233
3234         copy_number :=
3235             CASE
3236                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3237                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3238                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3239             END;
3240
3241         status :=
3242             CASE
3243                 WHEN attr_def.status IS NULL THEN 'null()'
3244                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3245                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3246             END;
3247
3248         location :=
3249             CASE
3250                 WHEN attr_def.location IS NULL THEN 'null()'
3251                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3252                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3253             END;
3254
3255         circulate :=
3256             CASE
3257                 WHEN attr_def.circulate IS NULL THEN 'null()'
3258                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3259                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3260             END;
3261
3262         deposit :=
3263             CASE
3264                 WHEN attr_def.deposit IS NULL THEN 'null()'
3265                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3266                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3267             END;
3268
3269         deposit_amount :=
3270             CASE
3271                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3272                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3273                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3274             END;
3275
3276         ref :=
3277             CASE
3278                 WHEN attr_def.ref IS NULL THEN 'null()'
3279                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3280                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3281             END;
3282
3283         holdable :=
3284             CASE
3285                 WHEN attr_def.holdable IS NULL THEN 'null()'
3286                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3287                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3288             END;
3289
3290         price :=
3291             CASE
3292                 WHEN attr_def.price IS NULL THEN 'null()'
3293                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3294                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3295             END;
3296
3297         barcode :=
3298             CASE
3299                 WHEN attr_def.barcode IS NULL THEN 'null()'
3300                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3301                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3302             END;
3303
3304         circ_modifier :=
3305             CASE
3306                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3307                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3308                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3309             END;
3310
3311         circ_as_type :=
3312             CASE
3313                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3314                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3315                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3316             END;
3317
3318         alert_message :=
3319             CASE
3320                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3321                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3322                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3323             END;
3324
3325         opac_visible :=
3326             CASE
3327                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3328                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3329                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3330             END;
3331
3332         pub_note :=
3333             CASE
3334                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3335                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3336                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3337             END;
3338         priv_note :=
3339             CASE
3340                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3341                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3342                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3343             END;
3344
3345
3346         xpath :=
3347             owning_lib      || '|' ||
3348             circ_lib        || '|' ||
3349             call_number     || '|' ||
3350             copy_number     || '|' ||
3351             status          || '|' ||
3352             location        || '|' ||
3353             circulate       || '|' ||
3354             deposit         || '|' ||
3355             deposit_amount  || '|' ||
3356             ref             || '|' ||
3357             holdable        || '|' ||
3358             price           || '|' ||
3359             barcode         || '|' ||
3360             circ_modifier   || '|' ||
3361             circ_as_type    || '|' ||
3362             alert_message   || '|' ||
3363             pub_note        || '|' ||
3364             priv_note       || '|' ||
3365             opac_visible;
3366
3367         -- RAISE NOTICE 'XPath: %', xpath;
3368
3369         FOR tmp_attr_set IN
3370                 SELECT  *
3371                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3372                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3373                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3374                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3375         LOOP
3376
3377             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3378             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3379
3380             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
3381             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
3382
3383             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3384             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3385             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3386
3387
3388             -- search up the org unit tree for a matching copy location
3389
3390             WITH RECURSIVE anscestor_depth AS (
3391                 SELECT  ou.id,
3392                     out.depth AS depth,
3393                     ou.parent_ou
3394                 FROM  actor.org_unit ou
3395                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3396                 WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3397                     UNION ALL
3398                 SELECT  ou.id,
3399                     out.depth,
3400                     ou.parent_ou
3401                 FROM  actor.org_unit ou
3402                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3403                     JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3404             ) SELECT  cpl.id INTO attr_set.location
3405                 FROM  anscestor_depth a
3406                     JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3407                 WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3408                 ORDER BY a.depth DESC
3409                 LIMIT 1; 
3410
3411             attr_set.circulate      :=
3412                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3413                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3414
3415             attr_set.deposit        :=
3416                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3417                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3418
3419             attr_set.holdable       :=
3420                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3421                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3422
3423             attr_set.opac_visible   :=
3424                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3425                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
3426
3427             attr_set.ref            :=
3428                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
3429                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
3430
3431             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
3432             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
3433             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
3434
3435             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
3436             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
3437             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
3438             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
3439             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3440             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
3441             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
3442             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3443
3444             RETURN NEXT attr_set;
3445
3446         END LOOP;
3447
3448     END IF;
3449
3450     RETURN;
3451
3452 END;
3453 $$ LANGUAGE PLPGSQL;
3454
3455
3456 -- Evergreen DB patch XXXX.data.org-setting-ui.circ.billing.uncheck_bills_and_unfocus_payment_box.sql
3457 --
3458 -- New org setting ui.circ.billing.uncheck_bills_and_unfocus_payment_box
3459 --
3460
3461 -- check whether patch can be applied
3462 SELECT evergreen.upgrade_deps_block_check('0584', :eg_version);
3463
3464 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3465     VALUES ( 
3466         'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3467         oils_i18n_gettext(
3468             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3469             'GUI: Uncheck bills by default in the patron billing interface',
3470             'coust',
3471             'label'
3472         ),
3473         oils_i18n_gettext(
3474             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3475             'Uncheck bills by default in the patron billing interface,'
3476             || ' and focus on the Uncheck All button instead of the'
3477             || ' Payment Received field.',
3478             'coust',
3479             'description'
3480         ),
3481         'bool'
3482     );
3483
3484
3485 -- check whether patch can be applied
3486 SELECT evergreen.upgrade_deps_block_check('0585', :eg_version);
3487
3488 INSERT into config.org_unit_setting_type
3489 ( name, label, description, datatype ) VALUES
3490 ( 'circ.checkout_fills_related_hold_exact_match_only',
3491     'Checkout Fills Related Hold On Valid Copy Only',
3492     '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.',
3493     'bool');
3494
3495
3496 -- check whether patch can be applied
3497 SELECT evergreen.upgrade_deps_block_check('0586', :eg_version);
3498
3499 INSERT INTO permission.perm_list (id, code, description) VALUES (
3500     511,
3501     'PERSISTENT_LOGIN',
3502     oils_i18n_gettext(
3503         511,
3504         'Allows a user to authenticate and get a long-lived session (length configured in opensrf.xml)',
3505         'ppl',
3506         'description'
3507     )
3508 );
3509
3510 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
3511     SELECT
3512         pgt.id, perm.id, aout.depth, FALSE
3513     FROM
3514         permission.grp_tree pgt,
3515         permission.perm_list perm,
3516         actor.org_unit_type aout
3517     WHERE
3518         pgt.name = 'Users' AND
3519         aout.name = 'Consortium' AND
3520         perm.code = 'PERSISTENT_LOGIN';
3521
3522 \qecho 
3523 \qecho If this transaction succeeded, your users (staff and patrons) now have
3524 \qecho the PERSISTENT_LOGIN permission by default.
3525 \qecho 
3526
3527
3528 -- Evergreen DB patch XXXX.data.org-setting-circ.offline.skip_foo_if_newer_status_changed_time.sql
3529 --
3530 -- New org setting circ.offline.skip_checkout_if_newer_status_changed_time
3531 -- New org setting circ.offline.skip_renew_if_newer_status_changed_time
3532 -- New org setting circ.offline.skip_checkin_if_newer_status_changed_time
3533 --
3534
3535 -- check whether patch can be applied
3536 SELECT evergreen.upgrade_deps_block_check('0593', :eg_version);
3537
3538 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3539     VALUES ( 
3540         'circ.offline.skip_checkout_if_newer_status_changed_time',
3541         oils_i18n_gettext(
3542             'circ.offline.skip_checkout_if_newer_status_changed_time',
3543             'Offline: Skip offline checkout if newer item Status Changed Time.',
3544             'coust',
3545             'label'
3546         ),
3547         oils_i18n_gettext(
3548             'circ.offline.skip_checkout_if_newer_status_changed_time',
3549             'Skip offline checkout transaction (raise exception when'
3550             || ' processing) if item Status Changed Time is newer than the'
3551             || ' recorded transaction time.  WARNING: The Reshelving to'
3552             || ' Available status rollover will trigger this.',
3553             'coust',
3554             'description'
3555         ),
3556         'bool'
3557     ),( 
3558         'circ.offline.skip_renew_if_newer_status_changed_time',
3559         oils_i18n_gettext(
3560             'circ.offline.skip_renew_if_newer_status_changed_time',
3561             'Offline: Skip offline renewal if newer item Status Changed Time.',
3562             'coust',
3563             'label'
3564         ),
3565         oils_i18n_gettext(
3566             'circ.offline.skip_renew_if_newer_status_changed_time',
3567             'Skip offline renewal transaction (raise exception when'
3568             || ' processing) if item Status Changed Time is newer than the'
3569             || ' recorded transaction time.  WARNING: The Reshelving to'
3570             || ' Available status rollover will trigger this.',
3571             'coust',
3572             'description'
3573         ),
3574         'bool'
3575     ),( 
3576         'circ.offline.skip_checkin_if_newer_status_changed_time',
3577         oils_i18n_gettext(
3578             'circ.offline.skip_checkin_if_newer_status_changed_time',
3579             'Offline: Skip offline checkin if newer item Status Changed Time.',
3580             'coust',
3581             'label'
3582         ),
3583         oils_i18n_gettext(
3584             'circ.offline.skip_checkin_if_newer_status_changed_time',
3585             'Skip offline checkin transaction (raise exception when'
3586             || ' processing) if item Status Changed Time is newer than the'
3587             || ' recorded transaction time.  WARNING: The Reshelving to'
3588             || ' Available status rollover will trigger this.',
3589             'coust',
3590             'description'
3591         ),
3592         'bool'
3593     );
3594
3595 -- Evergreen DB patch YYYY.schema.acp_status_date_changed.sql
3596 --
3597 -- Change trigger which updates copy status_changed_time to ignore the
3598 -- Reshelving->Available status rollover
3599
3600 -- FIXME: 0039.schema.acp_status_date_changed.sql defines this the first time
3601 -- around, but along with the column itself, etc.  And it gets modified with
3602 -- 0562.schema.copy_active_date.sql.  Not sure how to use the supercedes /
3603 -- deprecate stuff for upgrade scripts, if it's even applicable when a given
3604 -- upgrade script is doing so much.
3605
3606 -- check whether patch can be applied
3607 SELECT evergreen.upgrade_deps_block_check('0594', :eg_version);
3608
3609 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
3610 RETURNS TRIGGER AS $$
3611 BEGIN
3612         IF NEW.status <> OLD.status AND NOT (NEW.status = 0 AND OLD.status = 7) THEN
3613         NEW.status_changed_time := now();
3614         IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
3615             NEW.active_date := now();
3616         END IF;
3617     END IF;
3618     RETURN NEW;
3619 END;
3620 $$ LANGUAGE plpgsql;
3621
3622 -- Evergreen DB patch 0595.data.org-setting-ui.patron_search.result_cap.sql
3623 --
3624 -- New org setting ui.patron_search.result_cap
3625 --
3626
3627 -- check whether patch can be applied
3628 SELECT evergreen.upgrade_deps_block_check('0595', :eg_version);
3629
3630 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
3631     VALUES (
3632         'ui.patron_search.result_cap',
3633         oils_i18n_gettext(
3634             'ui.patron_search.result_cap',
3635             'GUI: Cap results in Patron Search at this number.',
3636             'coust',
3637             'label'
3638         ),
3639         oils_i18n_gettext(
3640             'ui.patron_search.result_cap',
3641             'So for example, if you search for John Doe, normally you would get'
3642             || ' at most 50 results.  This setting allows you to raise or lower'
3643             || ' that limit.',
3644             'coust',
3645             'description'
3646         ),
3647         'integer'
3648     );
3649
3650 -- Evergreen DB patch 0596.schema.vandelay-item-import-error-detail.sql
3651
3652 -- check whether patch can be applied
3653 SELECT evergreen.upgrade_deps_block_check('0596', :eg_version);
3654
3655 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3656     'import.item.invalid.status', oils_i18n_gettext('import.item.invalid.status', 'Invalid value for "status"', 'vie', 'description') );
3657 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3658     'import.item.invalid.price', oils_i18n_gettext('import.item.invalid.price', 'Invalid value for "price"', 'vie', 'description') );
3659 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3660     'import.item.invalid.deposit_amount', oils_i18n_gettext('import.item.invalid.deposit_amount', 'Invalid value for "deposit_amount"', 'vie', 'description') );
3661 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3662     'import.item.invalid.owning_lib', oils_i18n_gettext('import.item.invalid.owning_lib', 'Invalid value for "owning_lib"', 'vie', 'description') );
3663 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3664     'import.item.invalid.circ_lib', oils_i18n_gettext('import.item.invalid.circ_lib', 'Invalid value for "circ_lib"', 'vie', 'description') );
3665 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3666     'import.item.invalid.copy_number', oils_i18n_gettext('import.item.invalid.copy_number', 'Invalid value for "copy_number"', 'vie', 'description') );
3667 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3668     'import.item.invalid.circ_as_type', oils_i18n_gettext('import.item.invalid.circ_as_type', 'Invalid value for "circ_as_type"', 'vie', 'description') );
3669
3670 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3671 DECLARE
3672
3673     owning_lib      TEXT;
3674     circ_lib        TEXT;
3675     call_number     TEXT;
3676     copy_number     TEXT;
3677     status          TEXT;
3678     location        TEXT;
3679     circulate       TEXT;
3680     deposit         TEXT;
3681     deposit_amount  TEXT;
3682     ref             TEXT;
3683     holdable        TEXT;
3684     price           TEXT;
3685     barcode         TEXT;
3686     circ_modifier   TEXT;
3687     circ_as_type    TEXT;
3688     alert_message   TEXT;
3689     opac_visible    TEXT;
3690     pub_note        TEXT;
3691     priv_note       TEXT;
3692
3693     attr_def        RECORD;
3694     tmp_attr_set    RECORD;
3695     attr_set        vandelay.import_item%ROWTYPE;
3696
3697     xpath           TEXT;
3698     tmp_str         TEXT;
3699
3700 BEGIN
3701
3702     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3703
3704     IF FOUND THEN
3705
3706         attr_set.definition := attr_def.id;
3707
3708         -- Build the combined XPath
3709
3710         owning_lib :=
3711             CASE
3712                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3713                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3714                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3715             END;
3716
3717         circ_lib :=
3718             CASE
3719                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3720                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3721                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3722             END;
3723
3724         call_number :=
3725             CASE
3726                 WHEN attr_def.call_number IS NULL THEN 'null()'
3727                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3728                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3729             END;
3730
3731         copy_number :=
3732             CASE
3733                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3734                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3735                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3736             END;
3737
3738         status :=
3739             CASE
3740                 WHEN attr_def.status IS NULL THEN 'null()'
3741                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3742                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3743             END;
3744
3745         location :=
3746             CASE
3747                 WHEN attr_def.location IS NULL THEN 'null()'
3748                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3749                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3750             END;
3751
3752         circulate :=
3753             CASE
3754                 WHEN attr_def.circulate IS NULL THEN 'null()'
3755                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3756                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3757             END;
3758
3759         deposit :=
3760             CASE
3761                 WHEN attr_def.deposit IS NULL THEN 'null()'
3762                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3763                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3764             END;
3765
3766         deposit_amount :=
3767             CASE
3768                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3769                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3770                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3771             END;
3772
3773         ref :=
3774             CASE
3775                 WHEN attr_def.ref IS NULL THEN 'null()'
3776                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3777                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3778             END;
3779
3780         holdable :=
3781             CASE
3782                 WHEN attr_def.holdable IS NULL THEN 'null()'
3783                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3784                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3785             END;
3786
3787         price :=
3788             CASE
3789                 WHEN attr_def.price IS NULL THEN 'null()'
3790                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3791                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3792             END;
3793
3794         barcode :=
3795             CASE
3796                 WHEN attr_def.barcode IS NULL THEN 'null()'
3797                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3798                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3799             END;
3800
3801         circ_modifier :=
3802             CASE
3803                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3804                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3805                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3806             END;
3807
3808         circ_as_type :=
3809             CASE
3810                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3811                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3812                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3813             END;
3814
3815         alert_message :=
3816             CASE
3817                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3818                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3819                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3820             END;
3821
3822         opac_visible :=
3823             CASE
3824                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3825                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3826                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3827             END;
3828
3829         pub_note :=
3830             CASE
3831                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3832                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3833                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3834             END;
3835         priv_note :=
3836             CASE
3837                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3838                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3839                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3840             END;
3841
3842
3843         xpath :=
3844             owning_lib      || '|' ||
3845             circ_lib        || '|' ||
3846             call_number     || '|' ||
3847             copy_number     || '|' ||
3848             status          || '|' ||
3849             location        || '|' ||
3850             circulate       || '|' ||
3851             deposit         || '|' ||
3852             deposit_amount  || '|' ||
3853             ref             || '|' ||
3854             holdable        || '|' ||
3855             price           || '|' ||
3856             barcode         || '|' ||
3857             circ_modifier   || '|' ||
3858             circ_as_type    || '|' ||
3859             alert_message   || '|' ||
3860             pub_note        || '|' ||
3861             priv_note       || '|' ||
3862             opac_visible;
3863
3864         FOR tmp_attr_set IN
3865                 SELECT  *
3866                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3867                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3868                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3869                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3870         LOOP
3871
3872             attr_set.import_error := NULL;
3873             attr_set.error_detail := NULL;
3874             attr_set.deposit_amount := NULL;
3875             attr_set.copy_number := NULL;
3876             attr_set.price := NULL;
3877
3878             IF tmp_attr_set.pr != '' THEN
3879                 tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3880                 IF tmp_str = '' THEN 
3881                     attr_set.import_error := 'import.item.invalid.price';
3882                     attr_set.error_detail := tmp_attr_set.pr; -- original value
3883                     RETURN NEXT attr_set; CONTINUE; 
3884                 END IF;
3885                 attr_set.price := tmp_str::NUMERIC(8,2); 
3886             END IF;
3887
3888             IF tmp_attr_set.dep_amount != '' THEN
3889                 tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3890                 IF tmp_str = '' THEN 
3891                     attr_set.import_error := 'import.item.invalid.deposit_amount';
3892                     attr_set.error_detail := tmp_attr_set.dep_amount; 
3893                     RETURN NEXT attr_set; CONTINUE; 
3894                 END IF;
3895                 attr_set.deposit_amount := tmp_str::NUMERIC(8,2); 
3896             END IF;
3897
3898             IF tmp_attr_set.cnum != '' THEN
3899                 tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
3900                 IF tmp_str = '' THEN 
3901                     attr_set.import_error := 'import.item.invalid.copy_number';
3902                     attr_set.error_detail := tmp_attr_set.cnum; 
3903                     RETURN NEXT attr_set; CONTINUE; 
3904                 END IF;
3905                 attr_set.copy_number := tmp_str::INT; 
3906             END IF;
3907
3908             IF tmp_attr_set.ol != '' THEN
3909                 SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3910                 IF NOT FOUND THEN
3911                     attr_set.import_error := 'import.item.invalid.owning_lib';
3912                     attr_set.error_detail := tmp_attr_set.ol;
3913                     RETURN NEXT attr_set; CONTINUE; 
3914                 END IF;
3915             END IF;
3916
3917             IF tmp_attr_set.clib != '' THEN
3918                 SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3919                 IF NOT FOUND THEN
3920                     attr_set.import_error := 'import.item.invalid.circ_lib';
3921                     attr_set.error_detail := tmp_attr_set.clib;
3922                     RETURN NEXT attr_set; CONTINUE; 
3923                 END IF;
3924             END IF;
3925
3926             IF tmp_attr_set.cs != '' THEN
3927                 SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3928                 IF NOT FOUND THEN
3929                     attr_set.import_error := 'import.item.invalid.status';
3930                     attr_set.error_detail := tmp_attr_set.cs;
3931                     RETURN NEXT attr_set; CONTINUE; 
3932                 END IF;
3933             END IF;
3934
3935             IF tmp_attr_set.circ_mod != '' THEN
3936                 SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
3937                 IF NOT FOUND THEN
3938                     attr_set.import_error := 'import.item.invalid.circ_modifier';
3939                     attr_set.error_detail := tmp_attr_set.circ_mod;
3940                     RETURN NEXT attr_set; CONTINUE; 
3941                 END IF;
3942             END IF;
3943
3944             IF tmp_attr_set.circ_as != '' THEN
3945                 SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
3946                 IF NOT FOUND THEN
3947                     attr_set.import_error := 'import.item.invalid.circ_as_type';
3948                     attr_set.error_detail := tmp_attr_set.circ_as;
3949                     RETURN NEXT attr_set; CONTINUE; 
3950                 END IF;
3951             END IF;
3952
3953             IF tmp_attr_set.cl != '' THEN
3954
3955                 -- search up the org unit tree for a matching copy location
3956                 WITH RECURSIVE anscestor_depth AS (
3957                     SELECT  ou.id,
3958                         out.depth AS depth,
3959                         ou.parent_ou
3960                     FROM  actor.org_unit ou
3961                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3962                     WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3963                         UNION ALL
3964                     SELECT  ou.id,
3965                         out.depth,
3966                         ou.parent_ou
3967                     FROM  actor.org_unit ou
3968                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3969                         JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3970                 ) SELECT  cpl.id INTO attr_set.location
3971                     FROM  anscestor_depth a
3972                         JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3973                     WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3974                     ORDER BY a.depth DESC
3975                     LIMIT 1; 
3976
3977                 IF NOT FOUND THEN
3978                     attr_set.import_error := 'import.item.invalid.location';
3979                     attr_set.error_detail := tmp_attr_set.cs;
3980                     RETURN NEXT attr_set; CONTINUE; 
3981                 END IF;
3982             END IF;
3983
3984             attr_set.circulate      :=
3985                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3986                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3987
3988             attr_set.deposit        :=
3989                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3990                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3991
3992             attr_set.holdable       :=
3993                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3994                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3995
3996             attr_set.opac_visible   :=
3997                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3998                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
3999
4000             attr_set.ref            :=
4001                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
4002                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
4003
4004             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
4005             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
4006             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4007             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
4008             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
4009             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4010
4011             RETURN NEXT attr_set;
4012
4013         END LOOP;
4014
4015     END IF;
4016
4017     RETURN;
4018
4019 END;
4020 $$ LANGUAGE PLPGSQL;
4021
4022 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
4023 DECLARE
4024     attr_def    BIGINT;
4025     item_data   vandelay.import_item%ROWTYPE;
4026 BEGIN
4027
4028     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
4029         RETURN NEW;
4030     END IF;
4031
4032     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
4033
4034     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
4035         INSERT INTO vandelay.import_item (
4036             record,
4037             definition,
4038             owning_lib,
4039             circ_lib,
4040             call_number,
4041             copy_number,
4042             status,
4043             location,
4044             circulate,
4045             deposit,
4046             deposit_amount,
4047             ref,
4048             holdable,
4049             price,
4050             barcode,
4051             circ_modifier,
4052             circ_as_type,
4053             alert_message,
4054             pub_note,
4055             priv_note,
4056             opac_visible,
4057             import_error,
4058             error_detail
4059         ) VALUES (
4060             NEW.id,
4061             item_data.definition,
4062             item_data.owning_lib,
4063             item_data.circ_lib,
4064             item_data.call_number,
4065             item_data.copy_number,
4066             item_data.status,
4067             item_data.location,
4068             item_data.circulate,
4069             item_data.deposit,
4070             item_data.deposit_amount,
4071             item_data.ref,
4072             item_data.holdable,
4073             item_data.price,
4074             item_data.barcode,
4075             item_data.circ_modifier,
4076             item_data.circ_as_type,
4077             item_data.alert_message,
4078             item_data.pub_note,
4079             item_data.priv_note,
4080             item_data.opac_visible,
4081             item_data.import_error,
4082             item_data.error_detail
4083         );
4084     END LOOP;
4085
4086     RETURN NULL;
4087 END;
4088 $func$ LANGUAGE PLPGSQL;
4089
4090 -- Evergreen DB patch XXXX.schema.vandelay.bib_match_isxn_caseless.sql
4091
4092
4093 -- check whether patch can be applied
4094 SELECT evergreen.upgrade_deps_block_check('0597', :eg_version);
4095
4096 CREATE INDEX metabib_full_rec_isxn_caseless_idx
4097     ON metabib.real_full_rec (LOWER(value))
4098     WHERE tag IN ('020', '022', '024');
4099
4100
4101 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4102     record_xml TEXT
4103 ) RETURNS HSTORE AS $$
4104 BEGIN
4105     RETURN (SELECT
4106         HSTORE(
4107             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4108             ARRAY_ACCUM(value)
4109         )
4110         FROM (
4111             SELECT
4112                 tag, subfield,
4113                 CASE WHEN tag IN ('020', '022', '024') THEN  -- caseless
4114                     ARRAY_ACCUM(LOWER(value))::TEXT
4115                 ELSE
4116                     ARRAY_ACCUM(value)::TEXT
4117                 END AS value
4118                 FROM vandelay.flatten_marc(record_xml)
4119                 GROUP BY tag, subfield ORDER BY tag, subfield
4120         ) subquery
4121     );
4122 END;
4123 $$ LANGUAGE PLPGSQL;
4124
4125 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4126     node vandelay.match_set_point
4127 ) RETURNS VOID AS $$
4128 DECLARE
4129     jrow        TEXT;
4130     my_alias    TEXT;
4131     op          TEXT;
4132     tagkey      TEXT;
4133     caseless    BOOL;
4134 BEGIN
4135     -- remember $1 is tags_rstore, and $2 is svf_rstore
4136
4137     IF node.negate THEN
4138         op := '<>';
4139     ELSE
4140         op := '=';
4141     END IF;
4142
4143     caseless := FALSE;
4144
4145     IF node.tag IS NOT NULL THEN
4146         caseless := (node.tag IN ('020', '022', '024'));
4147         tagkey := node.tag;
4148         IF node.subfield IS NOT NULL THEN
4149             tagkey := tagkey || node.subfield;
4150         END IF;
4151     END IF;
4152
4153     my_alias := 'n' || node.id::TEXT;
4154
4155     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4156         ' AS quality FROM metabib.';
4157     IF node.tag IS NOT NULL THEN
4158         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4159             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4160             node.tag || '''';
4161         IF node.subfield IS NOT NULL THEN
4162             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4163                 node.subfield || '''';
4164         END IF;
4165         jrow := jrow || ' AND (';
4166
4167         IF caseless THEN
4168             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4169         ELSE
4170             jrow := jrow || my_alias || '.value ' || op;
4171         END IF;
4172
4173         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4174     ELSE    -- svf
4175         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4176             my_alias || '.id = bre.id AND (' ||
4177             my_alias || '.attrs->''' || node.svf ||
4178             ''' ' || op || ' $2->''' || node.svf || '''))';
4179     END IF;
4180     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4181 END;
4182 $$ LANGUAGE PLPGSQL;
4183
4184 -- Evergreen DB patch 0598.schema.vandelay_one_match_per.sql
4185 --
4186
4187
4188 -- check whether patch can be applied
4189 SELECT evergreen.upgrade_deps_block_check('0598', :eg_version);
4190
4191 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
4192     match_set_id INTEGER, record_xml TEXT
4193 ) RETURNS SETOF vandelay.match_set_test_result AS $$
4194 DECLARE
4195     tags_rstore HSTORE;
4196     svf_rstore  HSTORE;
4197     coal        TEXT;
4198     joins       TEXT;
4199     query_      TEXT;
4200     wq          TEXT;
4201     qvalue      INTEGER;
4202     rec         RECORD;
4203 BEGIN
4204     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
4205     svf_rstore := vandelay.extract_rec_attrs(record_xml);
4206
4207     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
4208     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
4209
4210     -- generate the where clause and return that directly (into wq), and as
4211     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
4212     wq := vandelay.get_expr_from_match_set(match_set_id);
4213
4214     query_ := 'SELECT DISTINCT(bre.id) AS record, ';
4215
4216     -- qrows table is for the quality bits we add to the SELECT clause
4217     SELECT ARRAY_TO_STRING(
4218         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
4219     ) INTO coal FROM _vandelay_tmp_qrows;
4220
4221     -- our query string so far is the SELECT clause and the inital FROM.
4222     -- no JOINs yet nor the WHERE clause
4223     query_ := query_ || coal || ' AS quality ' || E'\n' ||
4224         'FROM biblio.record_entry bre ';
4225
4226     -- jrows table is for the joins we must make (and the real text conditions)
4227     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
4228         FROM _vandelay_tmp_jrows;
4229
4230     -- add those joins and the where clause to our query.
4231     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
4232
4233     -- this will return rows of record,quality
4234     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
4235         RETURN NEXT rec;
4236     END LOOP;
4237
4238     DROP TABLE _vandelay_tmp_qrows;
4239     DROP TABLE _vandelay_tmp_jrows;
4240     RETURN;
4241 END;
4242
4243 $$ LANGUAGE PLPGSQL;
4244
4245 -- Evergreen DB patch 0606.schema.czs_use_perm_column.sql
4246 --
4247 -- This adds a column to config.z3950_source called use_perm.
4248 -- The idea is that if a permission is set for a given source,
4249 -- then staff will need the referenced permission to use that
4250 -- source.
4251 --
4252
4253 -- check whether patch can be applied
4254 SELECT evergreen.upgrade_deps_block_check('0606', :eg_version);
4255
4256 ALTER TABLE config.z3950_source 
4257     ADD COLUMN use_perm INT REFERENCES permission.perm_list (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
4258
4259 COMMENT ON COLUMN config.z3950_source.use_perm IS $$
4260 If set, this permission is required for the source to be listed in the staff
4261 client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
4262 $$;
4263
4264 -- Evergreen DB patch 0608.data.vandelay-export-error-match-info.sql
4265 --
4266 --
4267
4268
4269 -- check whether patch can be applied
4270 SELECT evergreen.upgrade_deps_block_check('0608', :eg_version);
4271
4272 -- Add vqbr.import_error, vqbr.error_detail, and vqbr.matches.size to queue print output
4273
4274 UPDATE action_trigger.event_definition SET template = $$
4275 [%- USE date -%]
4276 <pre>
4277 Queue ID: [% target.0.queue.id %]
4278 Queue Name: [% target.0.queue.name %]
4279 Queue Type: [% target.0.queue.queue_type %]
4280 Complete? [% target.0.queue.complete %]
4281
4282     [% FOR vqbr IN target %]
4283 =-=-=
4284  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
4285  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
4286  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
4287  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
4288  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
4289  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
4290  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
4291  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
4292  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
4293  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
4294  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
4295  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
4296  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
4297  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
4298  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
4299  Import Error     | [% vqbr.import_error %]
4300  Error Detail     | [% vqbr.error_detail %]
4301  Match Count      | [% vqbr.matches.size %]
4302
4303     [% END %]
4304 </pre>
4305 $$
4306 WHERE id = 39;
4307
4308
4309 -- Do the same for the CVS version
4310
4311 UPDATE action_trigger.event_definition SET template = $$
4312 [%- USE date -%]
4313 "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"
4314 [% 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 %]"
4315 [% END %]
4316 $$
4317 WHERE id = 40;
4318
4319 -- Add matches to the env for both
4320 INSERT INTO action_trigger.environment (event_def, path) VALUES (39, 'matches');
4321 INSERT INTO action_trigger.environment (event_def, path) VALUES (40, 'matches');
4322
4323
4324 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4325
4326 -- check whether patch can be applied
4327 SELECT evergreen.upgrade_deps_block_check('0609', :eg_version);
4328
4329 ALTER TABLE acq.lineitem_detail 
4330     ADD COLUMN receiver INT REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
4331
4332
4333 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4334
4335 -- check whether patch can be applied
4336 SELECT evergreen.upgrade_deps_block_check('0610', :eg_version);
4337
4338 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
4339     'acq.copy_creator_uses_receiver',
4340     oils_i18n_gettext( 
4341         'acq.copy_creator_uses_receiver',
4342         'Acq: Set copy creator as receiver',
4343         'coust',
4344         'label'
4345     ),
4346     oils_i18n_gettext( 
4347         'acq.copy_creator_uses_receiver',
4348         'When receiving a copy in acquisitions, set the copy "creator" to be the staff that received the copy',
4349         'coust',
4350         'label'
4351     ),
4352     'bool'
4353 );
4354
4355 -- Evergreen DB patch 0611.data.magic_macros.sql
4356
4357 -- check whether patch can be applied
4358 SELECT evergreen.upgrade_deps_block_check('0611', :eg_version);
4359
4360 INSERT into config.org_unit_setting_type
4361 ( name, label, description, datatype ) VALUES
4362 (
4363         'circ.staff_client.receipt.header_text',
4364         oils_i18n_gettext(
4365             'circ.staff_client.receipt.header_text',
4366             'Receipt Template: Content of header_text include',
4367             'coust',
4368             'label'
4369         ),
4370         oils_i18n_gettext(
4371             'circ.staff_client.receipt.header_text',
4372             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(header_text)%',
4373             'coust',
4374             'description'
4375         ),
4376         'string'
4377     )
4378 ,(
4379         'circ.staff_client.receipt.footer_text',
4380         oils_i18n_gettext(
4381             'circ.staff_client.receipt.footer_text',
4382             'Receipt Template: Content of footer_text include',
4383             'coust',
4384             'label'
4385         ),
4386         oils_i18n_gettext(
4387             'circ.staff_client.receipt.footer_text',
4388             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(footer_text)%',
4389             'coust',
4390             'description'
4391         ),
4392         'string'
4393     )
4394 ,(
4395         'circ.staff_client.receipt.notice_text',
4396         oils_i18n_gettext(
4397             'circ.staff_client.receipt.notice_text',
4398             'Receipt Template: Content of notice_text include',
4399             'coust',
4400             'label'
4401         ),
4402         oils_i18n_gettext(
4403             'circ.staff_client.receipt.notice_text',
4404             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(notice_text)%',
4405             'coust',
4406             'description'
4407         ),
4408         'string'
4409     )
4410 ,(
4411         'circ.staff_client.receipt.alert_text',
4412         oils_i18n_gettext(
4413             'circ.staff_client.receipt.alert_text',
4414             'Receipt Template: Content of alert_text include',
4415             'coust',
4416             'label'
4417         ),
4418         oils_i18n_gettext(
4419             'circ.staff_client.receipt.alert_text',
4420             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(alert_text)%',
4421             'coust',
4422             'description'
4423         ),
4424         'string'
4425     )
4426 ,(
4427         'circ.staff_client.receipt.event_text',
4428         oils_i18n_gettext(
4429             'circ.staff_client.receipt.event_text',
4430             'Receipt Template: Content of event_text include',
4431             'coust',
4432             'label'
4433         ),
4434         oils_i18n_gettext(
4435             'circ.staff_client.receipt.event_text',
4436             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(event_text)%',
4437             'coust',
4438             'description'
4439         ),
4440         'string'
4441     );
4442
4443 -- Evergreen DB patch 0612.schema.authority_overlay_protection.sql
4444 --
4445
4446
4447 -- check whether patch can be applied
4448 SELECT evergreen.upgrade_deps_block_check('0612', :eg_version);
4449
4450 -- FIXME: add/check SQL statements to perform the upgrade
4451
4452 -- Function to generate an ephemeral overlay template from an authority record
4453 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
4454 DECLARE
4455     cset                INT;
4456     main_entry          authority.control_set_authority_field%ROWTYPE;
4457     bib_field           authority.control_set_bib_field%ROWTYPE;
4458     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
4459     replace_data        XML[] DEFAULT '{}'::XML[];
4460     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
4461     auth_field          XML[];
4462 BEGIN
4463     IF auth_id IS NULL THEN
4464         RETURN NULL;
4465     END IF;
4466
4467     -- Default to the LoC controll set
4468     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
4469
4470     -- if none, make a best guess
4471     IF cset IS NULL THEN
4472         SELECT  control_set INTO cset
4473           FROM  authority.control_set_authority_field
4474           WHERE tag IN (
4475                     SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marc::XML)::TEXT[])
4476                       FROM  authority.record_entry
4477                       WHERE id = auth_id
4478                 )
4479           LIMIT 1;
4480     END IF;
4481
4482     -- if STILL none, no-op change
4483     IF cset IS NULL THEN
4484         RETURN XMLELEMENT(
4485             name record,
4486             XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4487             XMLELEMENT( name leader, '00881nam a2200193   4500'),
4488             XMLELEMENT(
4489                 name datafield,
4490                 XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4491                 XMLELEMENT(
4492                     name subfield,
4493                     XMLATTRIBUTES('d' AS code),
4494                     '901c'
4495                 )
4496             )
4497         )::TEXT;
4498     END IF;
4499
4500     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
4501         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
4502         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
4503             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
4504                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
4505                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
4506             END LOOP;
4507             EXIT;
4508         END IF;
4509     END LOOP;
4510
4511     RETURN XMLELEMENT(
4512         name record,
4513         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4514         XMLELEMENT( name leader, '00881nam a2200193   4500'),
4515         replace_data,
4516         XMLELEMENT(
4517             name datafield,
4518             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4519             XMLELEMENT(
4520                 name subfield,
4521                 XMLATTRIBUTES('r' AS code),
4522                 ARRAY_TO_STRING(replace_rules,',')
4523             )
4524         )
4525     )::TEXT;
4526 END;
4527 $f$ STABLE LANGUAGE PLPGSQL;
4528
4529
4530
4531 -- Evergreen DB patch 0613.schema.vandelay_isxn_normalization.sql
4532 --
4533
4534
4535 -- check whether patch can be applied
4536 SELECT evergreen.upgrade_deps_block_check('0613', :eg_version);
4537
4538 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4539     record_xml TEXT
4540 ) RETURNS HSTORE AS $func$
4541 BEGIN
4542     RETURN (SELECT
4543         HSTORE(
4544             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4545             ARRAY_ACCUM(value)
4546         )
4547         FROM (
4548             SELECT  tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
4549               FROM  (SELECT tag,
4550                             subfield,
4551                             CASE WHEN tag = '020' THEN -- caseless -- isbn
4552                                 LOWER((REGEXP_MATCHES(value,$$^(\S{10,17})$$))[1] || '%')
4553                             WHEN tag = '022' THEN -- caseless -- issn
4554                                 LOWER((REGEXP_MATCHES(value,$$^(\S{4}[- ]?\S{4})$$))[1] || '%')
4555                             WHEN tag = '024' THEN -- caseless -- upc (other)
4556                                 LOWER(value || '%')
4557                             ELSE
4558                                 value
4559                             END AS value
4560                       FROM  vandelay.flatten_marc(record_xml)) x
4561                 GROUP BY tag, subfield ORDER BY tag, subfield
4562         ) subquery
4563     );
4564 END;
4565 $func$ LANGUAGE PLPGSQL;
4566
4567 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4568     node vandelay.match_set_point
4569 ) RETURNS VOID AS $$
4570 DECLARE
4571     jrow        TEXT;
4572     my_alias    TEXT;
4573     op          TEXT;
4574     tagkey      TEXT;
4575     caseless    BOOL;
4576 BEGIN
4577     -- remember $1 is tags_rstore, and $2 is svf_rstore
4578
4579     caseless := FALSE;
4580
4581     IF node.tag IS NOT NULL THEN
4582         caseless := (node.tag IN ('020', '022', '024'));
4583         tagkey := node.tag;
4584         IF node.subfield IS NOT NULL THEN
4585             tagkey := tagkey || node.subfield;
4586         END IF;
4587     END IF;
4588
4589     IF node.negate THEN
4590         IF caseless THEN
4591             op := 'NOT LIKE';
4592         ELSE
4593             op := '<>';
4594         END IF;
4595     ELSE
4596         IF caseless THEN
4597             op := 'LIKE';
4598         ELSE
4599             op := '=';
4600         END IF;
4601     END IF;
4602
4603     my_alias := 'n' || node.id::TEXT;
4604
4605     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4606         ' AS quality FROM metabib.';
4607     IF node.tag IS NOT NULL THEN
4608         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4609             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4610             node.tag || '''';
4611         IF node.subfield IS NOT NULL THEN
4612             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4613                 node.subfield || '''';
4614         END IF;
4615         jrow := jrow || ' AND (';
4616
4617         IF caseless THEN
4618             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4619         ELSE
4620             jrow := jrow || my_alias || '.value ' || op;
4621         END IF;
4622
4623         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4624     ELSE    -- svf
4625         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4626             my_alias || '.id = bre.id AND (' ||
4627             my_alias || '.attrs->''' || node.svf ||
4628             ''' ' || op || ' $2->''' || node.svf || '''))';
4629     END IF;
4630     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4631 END;
4632 $$ LANGUAGE PLPGSQL;
4633
4634
4635
4636 -- Evergreen DB patch XXXX.schema.generic-mapping-index-normalizer.sql
4637 --
4638
4639 -- check whether patch can be applied
4640 SELECT evergreen.upgrade_deps_block_check('0615', :eg_version);
4641
4642 -- evergreen.generic_map_normalizer 
4643
4644 CREATE OR REPLACE FUNCTION evergreen.generic_map_normalizer ( TEXT, TEXT ) RETURNS TEXT AS $f$
4645 my $string = shift;
4646 my %map;
4647
4648 my $default = $string;
4649
4650 $_ = shift;
4651 while (/^\s*?(.*?)\s*?=>\s*?(\S+)\s*/) {
4652     if ($1 eq '') {
4653         $default = $2;
4654     } else {
4655         $map{$2} = [split(/\s*,\s*/, $1)];
4656     }
4657     $_ = $';
4658 }
4659
4660 for my $key ( keys %map ) {
4661     return $key if (grep { $_ eq $string } @{ $map{$key} });
4662 }
4663
4664 return $default;
4665
4666 $f$ LANGUAGE PLPERLU;
4667
4668 -- evergreen.generic_map_normalizer 
4669
4670 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
4671     'Generic Mapping Normalizer', 
4672     'Map values or sets of values to new values',
4673     'generic_map_normalizer', 
4674     1
4675 );
4676
4677
4678 SELECT evergreen.upgrade_deps_block_check('0616', :eg_version);
4679
4680 CREATE OR REPLACE FUNCTION actor.org_unit_prox_update () RETURNS TRIGGER as $$
4681 BEGIN
4682
4683
4684 IF TG_OP = 'DELETE' THEN
4685
4686     DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4687
4688 END IF;
4689
4690 IF TG_OP = 'UPDATE' THEN
4691
4692     IF NEW.parent_ou <> OLD.parent_ou THEN
4693
4694         DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4695             INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4696             SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4697                 FROM  actor.org_unit l, actor.org_unit r
4698                 WHERE (l.id = NEW.id or r.id = NEW.id);
4699
4700     END IF;
4701
4702 END IF;
4703
4704 IF TG_OP = 'INSERT' THEN
4705
4706      INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4707      SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4708          FROM  actor.org_unit l, actor.org_unit r
4709          WHERE (l.id = NEW.id or r.id = NEW.id);
4710
4711 END IF;
4712
4713 RETURN null;
4714
4715 END;
4716 $$ LANGUAGE plpgsql;
4717
4718
4719 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 ();
4720
4721
4722 SELECT evergreen.upgrade_deps_block_check('0617', :eg_version);
4723
4724 -- add notify columns to booking.reservation
4725 ALTER TABLE booking.reservation
4726   ADD COLUMN email_notify BOOLEAN NOT NULL DEFAULT FALSE;
4727
4728 -- create the hook and validator
4729 INSERT INTO action_trigger.hook (key, core_type, description, passive)
4730   VALUES ('reservation.available', 'bresv', 'A reservation is available for pickup', false);
4731 INSERT INTO action_trigger.validator (module, description)
4732   VALUES ('ReservationIsAvailable','Checked that a reserved resource is available for checkout');
4733
4734 -- create org unit setting to toggle checkbox display
4735 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
4736   VALUES ('booking.allow_email_notify', 'booking.allow_email_notify', 'Permit email notification when a reservation is ready for pickup.', 'bool');
4737
4738
4739 SELECT evergreen.upgrade_deps_block_check('0618', :eg_version);
4740
4741 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';
4742
4743 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';
4744
4745 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';
4746
4747 -- Evergreen DB patch 0619.schema.au_last_update_time.sql
4748
4749 -- check whether patch can be applied
4750 SELECT evergreen.upgrade_deps_block_check('0619', :eg_version);
4751
4752 -- Add new column last_update_time to actor.usr, with trigger to maintain it
4753 -- Add corresponding new column to auditor.actor_usr_history
4754
4755 ALTER TABLE actor.usr
4756         ADD COLUMN last_update_time TIMESTAMPTZ;
4757
4758 ALTER TABLE auditor.actor_usr_history
4759         ADD COLUMN last_update_time TIMESTAMPTZ;
4760
4761 CREATE OR REPLACE FUNCTION actor.au_updated()
4762 RETURNS TRIGGER AS $$
4763 BEGIN
4764     NEW.last_update_time := now();
4765         RETURN NEW;
4766 END;
4767 $$ LANGUAGE plpgsql;
4768
4769 CREATE TRIGGER au_update_trig
4770         BEFORE INSERT OR UPDATE ON actor.usr
4771         FOR EACH ROW EXECUTE PROCEDURE actor.au_updated();
4772
4773 -- Evergreen DB patch XXXX.data.opac_payment_history_age_limit.sql
4774
4775
4776 SELECT evergreen.upgrade_deps_block_check('0621', :eg_version);
4777
4778 INSERT into config.org_unit_setting_type (name, label, description, datatype)
4779 VALUES (
4780     'opac.payment_history_age_limit',
4781     oils_i18n_gettext('opac.payment_history_age_limit',
4782         'OPAC: Payment History Age Limit', 'coust', 'label'),
4783     oils_i18n_gettext('opac.payment_history_age_limit',
4784         'The OPAC should not display payments by patrons that are older than any interval defined here.', 'coust', 'label'),
4785     'interval'
4786 );
4787
4788 -- Updates config.org_unit_setting_type to remove the old tag prefixes for once 
4789 -- groups have been added.
4790 --
4791
4792 SELECT evergreen.upgrade_deps_block_check('0622', :eg_version);
4793
4794 INSERT INTO config.settings_group (name, label) VALUES
4795 ('sys', oils_i18n_gettext('config.settings_group.system', 'System', 'coust', 'label')),
4796 ('gui', oils_i18n_gettext('config.settings_group.gui', 'GUI', 'coust', 'label')),
4797 ('lib', oils_i18n_gettext('config.settings_group.lib', 'Library', 'coust', 'label')),
4798 ('sec', oils_i18n_gettext('config.settings_group.sec', 'Security', 'coust', 'label')),
4799 ('cat', oils_i18n_gettext('config.settings_group.cat', 'Cataloging', 'coust', 'label')),
4800 ('holds', oils_i18n_gettext('config.settings_group.holds', 'Holds', 'coust', 'label')),
4801 ('circ', oils_i18n_gettext('config.settings_group.circulation', 'Circulation', 'coust', 'label')),
4802 ('self', oils_i18n_gettext('config.settings_group.self', 'Self Check', 'coust', 'label')),
4803 ('opac', oils_i18n_gettext('config.settings_group.opac', 'OPAC', 'coust', 'label')),
4804 ('prog', oils_i18n_gettext('config.settings_group.program', 'Program', 'coust', 'label')),
4805 ('glob', oils_i18n_gettext('config.settings_group.global', 'Global', 'coust', 'label')),
4806 ('finance', oils_i18n_gettext('config.settings_group.finances', 'Finances', 'coust', 'label')),
4807 ('credit', oils_i18n_gettext('config.settings_group.ccp', 'Credit Card Processing', 'coust', 'label')),
4808 ('serial', oils_i18n_gettext('config.settings_group.serial', 'Serials', 'coust', 'label')),
4809 ('recall', oils_i18n_gettext('config.settings_group.recall', 'Recalls', 'coust', 'label')),
4810 ('booking', oils_i18n_gettext('config.settings_group.booking', 'Booking', 'coust', 'label')),
4811 ('offline', oils_i18n_gettext('config.settings_group.offline', 'Offline', 'coust', 'label')),
4812 ('receipt_template', oils_i18n_gettext('config.settings_group.receipt_template', 'Receipt Template', 'coust', 'label'));
4813
4814 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Set copy creator as receiver' WHERE name = 'acq.copy_creator_uses_receiver';
4815 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_circ_modifier';
4816 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_copy_location';
4817 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.block';
4818 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.warn';
4819 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.holds.allow_holds_from_purchase_request';
4820 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_barcode_prefix';
4821 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_callnumber_prefix';
4822 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.opac_timeout';
4823 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.persistent_login_interval';
4824 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.staff_timeout';
4825 UPDATE config.org_unit_setting_type SET grp = 'booking' WHERE name = 'booking.allow_email_notify';
4826 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'cat.bib.alert_on_empty';
4827 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';
4828 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'cat.bib.keep_on_empty';
4829 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default Classification Scheme' WHERE name = 'cat.default_classification_scheme';
4830 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (fast add)' WHERE name = 'cat.default_copy_status_fast';
4831 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (normal)' WHERE name = 'cat.default_copy_status_normal';
4832 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'cat.default_item_price';
4833 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font family' WHERE name = 'cat.label.font.family';
4834 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font size' WHERE name = 'cat.label.font.size';
4835 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font weight' WHERE name = 'cat.label.font.weight';
4836 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';
4837 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label maximum lines' WHERE name = 'cat.spine.line.height';
4838 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label left margin' WHERE name = 'cat.spine.line.margin';
4839 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label line width' WHERE name = 'cat.spine.line.width';
4840 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Delete volume with last copy' WHERE name = 'cat.volume.delete_on_empty';
4841 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';
4842 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Block Renewal of Items Needed for Holds' WHERE name = 'circ.block_renews_for_holds';
4843 UPDATE config.org_unit_setting_type SET grp = 'booking', label='Elbow room' WHERE name = 'circ.booking_reservation.default_elbow_room';
4844 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_lost_on_zero';
4845 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_on_damaged';
4846 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_auto_renew_age';
4847 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold';
4848 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold_exact_match_only';
4849 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_never_checked_out.mark_missing';
4850 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_return.copy_status';
4851 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.damaged.void_ovedue';
4852 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.damaged_item_processing_fee';
4853 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';
4854 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard boundary' WHERE name = 'circ.hold_boundary.hard';
4855 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft boundary' WHERE name = 'circ.hold_boundary.soft';
4856 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Alert Interval' WHERE name = 'circ.hold_expire_alert_interval';
4857 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Interval' WHERE name = 'circ.hold_expire_interval';
4858 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.hold_shelf_status_delay';
4859 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft stalling interval' WHERE name = 'circ.hold_stalling.soft';
4860 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard stalling interval' WHERE name = 'circ.hold_stalling_hard';
4861 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Use Active Date for Age Protection' WHERE name = 'circ.holds.age_protect.active_date';
4862 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Behind Desk Pickup Supported' WHERE name = 'circ.holds.behind_desk_pickup_supported';
4863 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display age' WHERE name = 'circ.holds.canceled.display_age';
4864 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display count' WHERE name = 'circ.holds.canceled.display_count';
4865 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Clear shelf copy status' WHERE name = 'circ.holds.clear_shelf.copy_status';
4866 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';
4867 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Default Estimated Wait' WHERE name = 'circ.holds.default_estimated_wait_interval';
4868 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.default_shelf_expire_interval';
4869 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';
4870 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Alert' WHERE name = 'circ.holds.hold_has_copy_at.alert';
4871 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Block' WHERE name = 'circ.holds.hold_has_copy_at.block';
4872 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Maximum library target attempts' WHERE name = 'circ.holds.max_org_unit_target_loops';
4873 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Minimum Estimated Wait' WHERE name = 'circ.holds.min_estimated_wait_interval';
4874 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Org Unit Target Weight' WHERE name = 'circ.holds.org_unit_target_weight';
4875 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';
4876 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Truncated loan period.' WHERE name = 'circ.holds.recall_return_interval';
4877 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Circulation duration that triggers a recall.' WHERE name = 'circ.holds.recall_threshold';
4878 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';
4879 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.target_skip_me';
4880 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Reset request time on un-cancel' WHERE name = 'circ.holds.uncancel.reset_request_time';
4881 UPDATE config.org_unit_setting_type SET grp = 'holds', label='FIFO' WHERE name = 'circ.holds_fifo';
4882 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'circ.item_checkout_history.max';
4883 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost Checkin Generates New Overdues' WHERE name = 'circ.lost.generate_overdue_on_checkin';
4884 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost items usable on checkin' WHERE name = 'circ.lost_immediately_available';
4885 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.lost_materials_processing_fee';
4886 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost max interval' WHERE name = 'circ.max_accept_return_of_lost';
4887 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Cap Max Fine at Item Price' WHERE name = 'circ.max_fine.cap_at_price';
4888 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.max_patron_claim_return_count';
4889 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Item Status for Missing Pieces' WHERE name = 'circ.missing_pieces.copy_status';
4890 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'circ.obscure_dob';
4891 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';
4892 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';
4893 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';
4894 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Offline: Patron Usernames Allowed' WHERE name = 'circ.offline.username_allowed';
4895 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';
4896 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';
4897 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';
4898 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';
4899 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';
4900 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.patron_invalid_address_apply_penalty';
4901 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.pre_cat_copy_circ_lib';
4902 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.reshelving_complete.interval';
4903 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Restore overdues on lost item return' WHERE name = 'circ.restore_overdue_on_lost_return';
4904 UPDATE config.org_unit_setting_type SET grp = 'self', label='Pop-up alert for errors' WHERE name = 'circ.selfcheck.alert.popup';
4905 UPDATE config.org_unit_setting_type SET grp = 'self', label='Audio Alerts' WHERE name = 'circ.selfcheck.alert.sound';
4906 UPDATE config.org_unit_setting_type SET grp = 'self' WHERE name = 'circ.selfcheck.auto_override_checkout_events';
4907 UPDATE config.org_unit_setting_type SET grp = 'self', label='Block copy checkout status' WHERE name = 'circ.selfcheck.block_checkout_on_copy_status';
4908 UPDATE config.org_unit_setting_type SET grp = 'self', label='Patron Login Timeout (in seconds)' WHERE name = 'circ.selfcheck.patron_login_timeout';
4909 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require Patron Password' WHERE name = 'circ.selfcheck.patron_password_required';
4910 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require patron password' WHERE name = 'circ.selfcheck.require_patron_password';
4911 UPDATE config.org_unit_setting_type SET grp = 'self', label='Workstation Required' WHERE name = 'circ.selfcheck.workstation_required';
4912 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.staff_client.actor_on_checkout';
4913 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'circ.staff_client.do_not_auto_attempt_print';
4914 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of alert_text include' WHERE name = 'circ.staff_client.receipt.alert_text';
4915 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of event_text include' WHERE name = 'circ.staff_client.receipt.event_text';
4916 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of footer_text include' WHERE name = 'circ.staff_client.receipt.footer_text';
4917 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of header_text include' WHERE name = 'circ.staff_client.receipt.header_text';
4918 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of notice_text include' WHERE name = 'circ.staff_client.receipt.notice_text';
4919 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Minimum Transit Checkin Interval' WHERE name = 'circ.transit.min_checkin_interval';
4920 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Deactivate Card' WHERE name = 'circ.user_merge.deactivate_cards';
4921 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Address Delete' WHERE name = 'circ.user_merge.delete_addresses';
4922 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Barcode Delete' WHERE name = 'circ.user_merge.delete_cards';
4923 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost item billing when returned' WHERE name = 'circ.void_lost_on_checkin';
4924 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';
4925 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';
4926 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'credit.payments.allow';
4927 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable AuthorizeNet payments' WHERE name = 'credit.processor.authorizenet.enabled';
4928 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet login' WHERE name = 'credit.processor.authorizenet.login';
4929 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet password' WHERE name = 'credit.processor.authorizenet.password';
4930 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet server' WHERE name = 'credit.processor.authorizenet.server';
4931 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet test mode' WHERE name = 'credit.processor.authorizenet.testmode';
4932 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Name default credit processor' WHERE name = 'credit.processor.default';
4933 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayflowPro payments' WHERE name = 'credit.processor.payflowpro.enabled';
4934 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro login/merchant ID' WHERE name = 'credit.processor.payflowpro.login';
4935 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro partner' WHERE name = 'credit.processor.payflowpro.partner';
4936 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro password' WHERE name = 'credit.processor.payflowpro.password';
4937 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro test mode' WHERE name = 'credit.processor.payflowpro.testmode';
4938 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro vendor' WHERE name = 'credit.processor.payflowpro.vendor';
4939 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayPal payments' WHERE name = 'credit.processor.paypal.enabled';
4940 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal login' WHERE name = 'credit.processor.paypal.login';
4941 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal password' WHERE name = 'credit.processor.paypal.password';
4942 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal signature' WHERE name = 'credit.processor.paypal.signature';
4943 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal test mode' WHERE name = 'credit.processor.paypal.testmode';
4944 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Dates with this pattern.' WHERE name = 'format.date';
4945 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Times with this pattern.' WHERE name = 'format.time';
4946 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.default_locale';
4947 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'global.juvenile_age_threshold';
4948 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.password_regex';
4949 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';
4950 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Courier Code' WHERE name = 'lib.courier_code';
4951 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'notice.telephony.callfile_lines';
4952 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Allow pending addresses' WHERE name = 'opac.allow_pending_address';
4953 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'opac.barcode_regex';
4954 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Use fully compressed serial holdings' WHERE name = 'opac.fully_compressed_serial_holdings';
4955 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Org Unit Hiding Depth' WHERE name = 'opac.org_unit_hiding.depth';
4956 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Payment History Age Limit' WHERE name = 'opac.payment_history_age_limit';
4957 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'org.bounced_emails';
4958 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Boundary' WHERE name = 'org.patron_opt_boundary';
4959 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Default' WHERE name = 'org.patron_opt_default';
4960 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'patron.password.use_phone';
4961 UPDATE config.org_unit_setting_type SET grp = 'serial', label='Previous Issuance Copy Location' WHERE name = 'serial.prev_issuance_copy_location';
4962 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Patrons Logged' WHERE name = 'ui.admin.patron_log.max_entries';
4963 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Actions Logged' WHERE name = 'ui.admin.work_log.max_entries';
4964 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';
4965 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';
4966 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';
4967 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';
4968 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.patron_summary.horizontal';
4969 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.show_billing_tab_on_bills';
4970 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Suppress popup-dialogs during check-in.' WHERE name = 'ui.circ.suppress_checkin_popups';
4971 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Button bar' WHERE name = 'ui.general.button_bar';
4972 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Hotkeyset' WHERE name = 'ui.general.hotkeyset';
4973 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Idle timeout' WHERE name = 'ui.general.idle_timeout';
4974 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Country for New Addresses in Patron Editor' WHERE name = 'ui.patron.default_country';
4975 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Ident Type for Patron Registration' WHERE name = 'ui.patron.default_ident_type';
4976 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Default level of patrons'' internet access' WHERE name = 'ui.patron.default_inet_access_level';
4977 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show active field on patron registration' WHERE name = 'ui.patron.edit.au.active.show';
4978 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest active field on patron registration' WHERE name = 'ui.patron.edit.au.active.suggest';
4979 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';
4980 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';
4981 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.show';
4982 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.suggest';
4983 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.show';
4984 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.suggest';
4985 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';
4986 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';
4987 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';
4988 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';
4989 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';
4990 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';
4991 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';
4992 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';
4993 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';
4994 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';
4995 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.require';
4996 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.show';
4997 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.suggest';
4998 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';
4999 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';
5000 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require email field on patron registration' WHERE name = 'ui.patron.edit.au.email.require';
5001 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show email field on patron registration' WHERE name = 'ui.patron.edit.au.email.show';
5002 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest email field on patron registration' WHERE name = 'ui.patron.edit.au.email.suggest';
5003 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';
5004 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';
5005 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';
5006 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';
5007 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';
5008 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';
5009 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';
5010 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';
5011 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';
5012 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.show';
5013 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.suggest';
5014 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';
5015 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';
5016 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';
5017 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';
5018 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';
5019 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';
5020 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';
5021 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';
5022 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';
5023 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.show';
5024 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.suggest';
5025 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require county field on patron registration' WHERE name = 'ui.patron.edit.aua.county.require';
5026 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';
5027 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';
5028 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default showing suggested patron registration fields' WHERE name = 'ui.patron.edit.default_suggested';
5029 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.example';
5030 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.regex';
5031 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';
5032 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';
5033 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';
5034 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Unified Volume/Item Creator/Editor' WHERE name = 'ui.unified_volume_copy_editor';
5035 UPDATE config.org_unit_setting_type SET grp = 'gui', label='URL for remote directory containing list column settings.' WHERE name = 'url.remote_column_settings';
5036
5037
5038
5039
5040 SELECT evergreen.upgrade_deps_block_check('0623', :eg_version);
5041
5042
5043 CREATE TABLE config.org_unit_setting_type_log (
5044     id              BIGSERIAL   PRIMARY KEY,
5045     date_applied    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
5046     org             INT         REFERENCES actor.org_unit (id),
5047     original_value  TEXT,
5048     new_value       TEXT,
5049     field_name      TEXT      REFERENCES config.org_unit_setting_type (name)
5050 );
5051
5052 -- Log each change in oust to oustl, so admins can see what they messed up if someting stops working.
5053 CREATE OR REPLACE FUNCTION ous_change_log() RETURNS TRIGGER AS $ous_change_log$
5054     DECLARE
5055     original TEXT;
5056     BEGIN
5057         -- Check for which setting is being updated, and log it.
5058         SELECT INTO original value FROM actor.org_unit_setting WHERE name = NEW.name AND org_unit = NEW.org_unit;
5059                 
5060         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (NEW.org_unit, original, NEW.value, NEW.name);
5061         
5062         RETURN NEW;
5063     END;
5064 $ous_change_log$ LANGUAGE plpgsql;    
5065
5066 CREATE TRIGGER log_ous_change
5067     BEFORE INSERT OR UPDATE ON actor.org_unit_setting
5068     FOR EACH ROW EXECUTE PROCEDURE ous_change_log();
5069
5070 CREATE OR REPLACE FUNCTION ous_delete_log() RETURNS TRIGGER AS $ous_delete_log$
5071     DECLARE
5072     original TEXT;
5073     BEGIN
5074         -- Check for which setting is being updated, and log it.
5075         SELECT INTO original value FROM actor.org_unit_setting WHERE name = OLD.name AND org_unit = OLD.org_unit;
5076                 
5077         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (OLD.org_unit, original, 'null', OLD.name);
5078         
5079         RETURN OLD;
5080     END;
5081 $ous_delete_log$ LANGUAGE plpgsql;    
5082
5083 CREATE TRIGGER log_ous_del
5084     BEFORE DELETE ON actor.org_unit_setting
5085     FOR EACH ROW EXECUTE PROCEDURE ous_delete_log();
5086
5087 -- Evergreen DB patch 0625.data.opac_staff_saved_search_size.sql
5088
5089
5090 SELECT evergreen.upgrade_deps_block_check('0625', :eg_version);
5091
5092 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype)
5093 VALUES (
5094     'opac.staff_saved_search.size', 'opac',
5095     oils_i18n_gettext('opac.staff_saved_search.size',
5096         'OPAC: Number of staff client saved searches to display on left side of results and record details pages', 'coust', 'label'),
5097     oils_i18n_gettext('opac.staff_saved_search.size',
5098         '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'),
5099     'integer'
5100 );
5101
5102 -- Evergreen DB patch 0626.schema.bookbag-goodies.sql
5103
5104
5105 SELECT evergreen.upgrade_deps_block_check('0626', :eg_version);
5106
5107 ALTER TABLE container.biblio_record_entry_bucket
5108     ADD COLUMN description TEXT;
5109
5110 ALTER TABLE container.call_number_bucket
5111     ADD COLUMN description TEXT;
5112
5113 ALTER TABLE container.copy_bucket
5114     ADD COLUMN description TEXT;
5115
5116 ALTER TABLE container.user_bucket
5117     ADD COLUMN description TEXT;
5118
5119 INSERT INTO action_trigger.hook (key, core_type, description, passive)
5120 VALUES (
5121     'container.biblio_record_entry_bucket.csv',
5122     'cbreb',
5123     oils_i18n_gettext(
5124         'container.biblio_record_entry_bucket.csv',
5125         'Produce a CSV file representing a bookbag',
5126         'ath',
5127         'description'
5128     ),
5129     FALSE
5130 );
5131
5132 INSERT INTO action_trigger.reactor (module, description)
5133 VALUES (
5134     'ContainerCSV',
5135     oils_i18n_gettext(
5136         'ContainerCSV',
5137         'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
5138         'atr',
5139         'description'
5140     )
5141 );
5142
5143 INSERT INTO action_trigger.event_definition (
5144     id, active, owner,
5145     name, hook, reactor,
5146     validator, template
5147 ) VALUES (
5148     48, TRUE, 1,
5149     'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
5150     'NOOP_True',
5151 $$
5152 [%-
5153 # target is the bookbag itself. The 'items' variable does not need to be in
5154 # the environment because a special reactor will take care of filling it in.
5155
5156 FOR item IN items;
5157     bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
5158     title = "";
5159     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
5160         title = title _ part.textContent;
5161     END;
5162     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
5163
5164     helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
5165 END -%]
5166 $$
5167 );
5168
5169 -- Evergreen DB patch 0627.data.patron-password-reset-msg.sql
5170 --
5171 -- Updates password reset template to match TPAC reset form
5172 --
5173
5174 -- check whether patch can be applied
5175 SELECT evergreen.upgrade_deps_block_check('0627', :eg_version);
5176
5177 UPDATE action_trigger.event_definition SET template = 
5178 $$
5179 [%- USE date -%]
5180 [%- user = target.usr -%]
5181 To: [%- params.recipient_email || user.email %]
5182 From: [%- params.sender_email || user.home_ou.email || default_sender %]
5183 Subject: [% user.home_ou.name %]: library account password reset request
5184
5185 You have received this message because you, or somebody else, requested a reset
5186 of your library system password. If you did not request a reset of your library
5187 system password, just ignore this message and your current password will
5188 continue to work.
5189
5190 If you did request a reset of your library system password, please perform
5191 the following steps to continue the process of resetting your password:
5192
5193 1. Open the following link in a web browser: https://[% params.hostname %]/eg/opac/password_reset/[% target.uuid %]
5194 The browser displays a password reset form.
5195
5196 2. Enter your new password in the password reset form in the browser. You must
5197 enter the password twice to ensure that you do not make a mistake. If the
5198 passwords match, you will then be able to log in to your library system account
5199 with the new password.
5200
5201 $$
5202 WHERE id = 20; -- Password reset request notification
5203
5204
5205 SELECT evergreen.upgrade_deps_block_check('0630', :eg_version);
5206
5207 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5208 ( 'circ.transit.suppress_hold', 'circ',
5209     oils_i18n_gettext('circ.transit.suppress_hold',
5210         'Suppress Hold Transits Group',
5211         'coust', 'label'),
5212     oils_i18n_gettext('circ.transit.suppress_hold',
5213         '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.',
5214         'coust', 'description'),
5215     'string')
5216 ,( 'circ.transit.suppress_non_hold', 'circ',
5217     oils_i18n_gettext('circ.transit.suppress_non_hold',
5218         'Suppress Non-Hold Transits Group',
5219         'coust', 'label'),
5220     oils_i18n_gettext('circ.transit.suppress_non_hold',
5221         '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.',
5222         'coust', 'description'),
5223     'string');
5224
5225
5226 -- check whether patch can be applied
5227 SELECT evergreen.upgrade_deps_block_check('0632', :eg_version);
5228
5229 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5230 ( 'opac.username_regex', 'glob',
5231     oils_i18n_gettext('opac.username_regex',
5232         'Patron username format',
5233         'coust', 'label'),
5234     oils_i18n_gettext('opac.username_regex',
5235         'Regular expression defining the patron username format, used for patron registration and self-service username changing only',
5236         'coust', 'description'),
5237     'string')
5238 ,( 'opac.lock_usernames', 'glob',
5239     oils_i18n_gettext('opac.lock_usernames',
5240         'Lock Usernames',
5241         'coust', 'label'),
5242     oils_i18n_gettext('opac.lock_usernames',
5243         'If enabled username changing via the OPAC will be disabled',
5244         'coust', 'description'),
5245     'bool')
5246 ,( 'opac.unlimit_usernames', 'glob',
5247     oils_i18n_gettext('opac.unlimit_usernames',
5248         'Allow multiple username changes',
5249         'coust', 'label'),
5250     oils_i18n_gettext('opac.unlimit_usernames',
5251         '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.',
5252         'coust', 'description'),
5253     'bool')
5254 ;
5255
5256 -- Evergreen DB patch 0635.data.opac.jump-to-details-setting.sql
5257 --
5258
5259
5260 -- check whether patch can be applied
5261 SELECT evergreen.upgrade_deps_block_check('0635', :eg_version);
5262
5263 INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype )
5264     VALUES (
5265         'opac.staff.jump_to_details_on_single_hit', 
5266         'opac',
5267         oils_i18n_gettext(
5268             'opac.staff.jump_to_details_on_single_hit',
5269             'Jump to details on 1 hit (staff client)',
5270             'coust', 
5271             'label'
5272         ),
5273         oils_i18n_gettext(
5274             'opac.staff.jump_to_details_on_single_hit',
5275             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the OPAC within the staff client',
5276             'coust', 
5277             'description'
5278         ),
5279         'bool'
5280     ), (
5281         'opac.patron.jump_to_details_on_single_hit', 
5282         'opac',
5283         oils_i18n_gettext(
5284             'opac.patron.jump_to_details_on_single_hit',
5285             'Jump to details on 1 hit (public)',
5286             'coust', 
5287             'label'
5288         ),
5289         oils_i18n_gettext(
5290             'opac.patron.jump_to_details_on_single_hit',
5291             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the public OPAC',
5292             'coust', 
5293             'description'
5294         ),
5295         'bool'
5296     );
5297
5298 -- Evergreen DB patch 0636.data.grace_period_extend.sql
5299 --
5300 -- OU setting turns on grace period auto extension. By default they only do so
5301 -- when the grace period ends on a closed date, but there are two modifiers to
5302 -- change that.
5303 -- 
5304 -- The first modifier causes grace periods to extend for all closed dates that
5305 -- they intersect. This is "grace periods are only consumed by open days."
5306 -- 
5307 -- The second modifier causes a grace period that ends just before a closed
5308 -- day, with or without extension having happened, to include the closed day
5309 -- (and any following it) as well. This is mainly so that a backdate into the
5310 -- closed period following the grace period will assume the "best case" of the
5311 -- item having been returned after hours on the last day of the closed date.
5312 --
5313
5314
5315 -- check whether patch can be applied
5316 SELECT evergreen.upgrade_deps_block_check('0636', :eg_version);
5317
5318 INSERT INTO config.org_unit_setting_type(name, grp, label, description, datatype) VALUES
5319
5320 ( 'circ.grace.extend', 'circ',
5321     oils_i18n_gettext('circ.grace.extend',
5322         'Auto-Extend Grace Periods',
5323         'coust', 'label'),
5324     oils_i18n_gettext('circ.grace.extend',
5325         '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.',
5326         'coust', 'description'),
5327     'bool')
5328
5329 ,( 'circ.grace.extend.all', 'circ',
5330     oils_i18n_gettext('circ.grace.extend.all',
5331         'Auto-Extending Grace Periods extend for all closed dates',
5332         'coust', 'label'),
5333     oils_i18n_gettext('circ.grace.extend.all',
5334         '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".',
5335         'coust', 'description'),
5336     'bool')
5337
5338 ,( 'circ.grace.extend.into_closed', 'circ',
5339     oils_i18n_gettext('circ.grace.extend.into_closed',
5340         'Auto-Extending Grace Periods include trailing closed dates',
5341         'coust', 'label'),
5342     oils_i18n_gettext('circ.grace.extend.into_closed',
5343          '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.',
5344         'coust', 'description'),
5345     'bool');
5346
5347
5348 -- XXXX.schema-acs-nfi.sql
5349
5350 SELECT evergreen.upgrade_deps_block_check('0640', :eg_version);
5351
5352 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
5353 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
5354 BEGIN
5355
5356     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
5357         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
5358         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
5359         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5360           -- Should remove matching $0 from controlled fields at the same time?
5361         RETURN NEW; -- and we're done
5362     END IF;
5363
5364     IF TG_OP = 'UPDATE' THEN -- re-ingest?
5365         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
5366
5367         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
5368             RETURN NEW;
5369         END IF;
5370
5371         -- Propagate these updates to any linked bib records
5372         PERFORM authority.propagate_changes(NEW.id) FROM authority.record_entry WHERE id = NEW.id;
5373
5374         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5375     END IF;
5376
5377     INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5378         SELECT record, atag, value, sort_value FROM authority.simple_heading_set(NEW.marc);
5379
5380     -- Flatten and insert the afr data
5381     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
5382     IF NOT FOUND THEN
5383         PERFORM authority.reingest_authority_full_rec(NEW.id);
5384         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
5385         IF NOT FOUND THEN
5386             PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
5387         END IF;
5388     END IF;
5389
5390     RETURN NEW;
5391 END;
5392 $func$ LANGUAGE PLPGSQL;
5393
5394 -- Entries that need to respect an NFI
5395 UPDATE authority.control_set_authority_field SET nfi = '2'
5396     WHERE id IN (4,24,44,64);
5397
5398 DROP TRIGGER authority_full_rec_fti_trigger ON authority.full_rec;
5399 CREATE TRIGGER authority_full_rec_fti_trigger
5400     BEFORE UPDATE OR INSERT ON authority.full_rec
5401     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5402
5403 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
5404 DECLARE
5405     acsaf           authority.control_set_authority_field%ROWTYPE;
5406     tag_used        TEXT;
5407     nfi_used        TEXT;
5408     sf              TEXT;
5409     thes_code       TEXT;
5410     cset            INT;
5411     heading_text    TEXT;
5412     tmp_text        TEXT;
5413     first_sf        BOOL;
5414     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5415 BEGIN
5416     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
5417
5418     IF cset IS NULL THEN
5419         SELECT  control_set INTO cset
5420           FROM  authority.control_set_authority_field
5421           WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
5422           LIMIT 1;
5423     END IF;
5424
5425     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
5426     IF thes_code IS NULL THEN
5427         thes_code := '|';
5428     ELSIF thes_code = 'z' THEN
5429         thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
5430     END IF;
5431
5432     heading_text := '';
5433     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
5434         tag_used := acsaf.tag;
5435         nfi_used := acsaf.nfi;
5436         first_sf := TRUE;
5437         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5438             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
5439
5440             IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
5441
5442                 tmp_text := SUBSTRING(
5443                     tmp_text FROM
5444                     COALESCE(
5445                         NULLIF(
5446                             REGEXP_REPLACE(
5447                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5448                                 $$\D+$$,
5449                                 '',
5450                                 'g'
5451                             ),
5452                             ''
5453                         )::INT,
5454                         0
5455                     ) + 1
5456                 );
5457
5458             END IF;
5459
5460             first_sf := FALSE;
5461
5462             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
5463                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
5464             END IF;
5465         END LOOP;
5466         EXIT WHEN heading_text <> '';
5467     END LOOP;
5468
5469     IF heading_text <> '' THEN
5470         IF no_thesaurus IS TRUE THEN
5471             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
5472         ELSE
5473             heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
5474         END IF;
5475     ELSE
5476         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
5477     END IF;
5478
5479     RETURN heading_text;
5480 END;
5481 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5482
5483 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5484     SELECT authority.normalize_heading($1, TRUE);
5485 $func$ LANGUAGE SQL IMMUTABLE;
5486
5487 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5488     SELECT authority.normalize_heading($1, FALSE);
5489 $func$ LANGUAGE SQL IMMUTABLE;
5490
5491
5492 CREATE TABLE authority.simple_heading (
5493     id              BIGSERIAL   PRIMARY KEY,
5494     record          BIGINT      NOT NULL REFERENCES authority.record_entry (id),
5495     atag            INT         NOT NULL REFERENCES authority.control_set_authority_field (id),
5496     value           TEXT        NOT NULL,
5497     sort_value      TEXT        NOT NULL,
5498     index_vector    tsvector    NOT NULL
5499 );
5500 CREATE TRIGGER authority_simple_heading_fti_trigger
5501     BEFORE UPDATE OR INSERT ON authority.simple_heading
5502     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5503
5504 CREATE INDEX authority_simple_heading_index_vector_idx ON authority.simple_heading USING GIST (index_vector);
5505 CREATE INDEX authority_simple_heading_value_idx ON authority.simple_heading (value);
5506 CREATE INDEX authority_simple_heading_sort_value_idx ON authority.simple_heading (sort_value);
5507
5508 CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
5509 DECLARE
5510     res             authority.simple_heading%ROWTYPE;
5511     acsaf           authority.control_set_authority_field%ROWTYPE;
5512     tag_used        TEXT;
5513     nfi_used        TEXT;
5514     sf              TEXT;
5515     cset            INT;
5516     heading_text    TEXT;
5517     sort_text       TEXT;
5518     tmp_text        TEXT;
5519     tmp_xml         TEXT;
5520     first_sf        BOOL;
5521     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5522 BEGIN
5523
5524     res.record := auth_id;
5525
5526     SELECT  control_set INTO cset
5527       FROM  authority.control_set_authority_field
5528       WHERE tag IN ( SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]) )
5529       LIMIT 1;
5530
5531     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
5532
5533         res.atag := acsaf.id;
5534         tag_used := acsaf.tag;
5535         nfi_used := acsaf.nfi;
5536
5537         FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)) LOOP
5538             heading_text := '';
5539
5540             FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5541                 heading_text := heading_text || COALESCE( ' ' || oils_xpath_string('//*[@code="'||sf||'"]',tmp_xml::TEXT), '');
5542             END LOOP;
5543
5544             heading_text := public.naco_normalize(heading_text);
5545             
5546             IF nfi_used IS NOT NULL THEN
5547
5548                 sort_text := SUBSTRING(
5549                     heading_text FROM
5550                     COALESCE(
5551                         NULLIF(
5552                             REGEXP_REPLACE(
5553                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5554                                 $$\D+$$,
5555                                 '',
5556                                 'g'
5557                             ),
5558                             ''
5559                         )::INT,
5560                         0
5561                     ) + 1
5562                 );
5563
5564             ELSE
5565                 sort_text := heading_text;
5566             END IF;
5567
5568             IF heading_text IS NOT NULL AND heading_text <> '' THEN
5569                 res.value := heading_text;
5570                 res.sort_value := sort_text;
5571                 RETURN NEXT res;
5572             END IF;
5573
5574         END LOOP;
5575
5576     END LOOP;
5577
5578     RETURN;
5579 END;
5580 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5581
5582 -- Support function used to find the pivot for alpha-heading-browse style searching
5583 CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT ) RETURNS TEXT AS $$
5584 DECLARE
5585     sort_value_row  RECORD;
5586     value_row       RECORD;
5587     t_term          TEXT;
5588 BEGIN
5589
5590     t_term := public.naco_normalize(q);
5591
5592     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5593                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5594             ash.sort_value
5595       INTO  sort_value_row
5596       FROM  authority.simple_heading ash
5597       WHERE ash.atag = ANY (a)
5598             AND ash.sort_value >= t_term
5599       ORDER BY rank DESC, ash.sort_value
5600       LIMIT 1;
5601
5602     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5603                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5604             ash.sort_value
5605       INTO  value_row
5606       FROM  authority.simple_heading ash
5607       WHERE ash.atag = ANY (a)
5608             AND ash.value >= t_term
5609       ORDER BY rank DESC, ash.sort_value
5610       LIMIT 1;
5611
5612     IF value_row.rank > sort_value_row.rank THEN
5613         RETURN value_row.sort_value;
5614     ELSE
5615         RETURN sort_value_row.sort_value;
5616     END IF;
5617 END;
5618 $$ LANGUAGE PLPGSQL;
5619
5620
5621 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 $$
5622 DECLARE
5623     pivot_sort_value    TEXT;
5624     boffset             INT DEFAULT 0;
5625     aoffset             INT DEFAULT 0;
5626     blimit              INT DEFAULT 0;
5627     alimit              INT DEFAULT 0;
5628 BEGIN
5629
5630     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5631
5632     IF page = 0 THEN
5633         blimit := pagesize / 2;
5634         alimit := blimit;
5635
5636         IF pagesize % 2 <> 0 THEN
5637             alimit := alimit + 1;
5638         END IF;
5639     ELSE
5640         blimit := pagesize;
5641         alimit := blimit;
5642
5643         boffset := pagesize / 2;
5644         aoffset := boffset;
5645
5646         IF pagesize % 2 <> 0 THEN
5647             boffset := boffset + 1;
5648         END IF;
5649     END IF;
5650
5651     IF page <= 0 THEN
5652         RETURN QUERY
5653             -- "bottom" half of the browse results
5654             SELECT id FROM (
5655                 SELECT  ash.id,
5656                         row_number() over ()
5657                   FROM  authority.simple_heading ash
5658                   WHERE ash.atag = ANY (atag_list)
5659                         AND ash.sort_value < pivot_sort_value
5660                   ORDER BY ash.sort_value DESC
5661                   LIMIT blimit
5662                   OFFSET ABS(page) * pagesize - boffset
5663             ) x ORDER BY row_number DESC;
5664     END IF;
5665
5666     IF page >= 0 THEN
5667         RETURN QUERY
5668             -- "bottom" half of the browse results
5669             SELECT  ash.id
5670               FROM  authority.simple_heading ash
5671               WHERE ash.atag = ANY (atag_list)
5672                     AND ash.sort_value >= pivot_sort_value
5673               ORDER BY ash.sort_value
5674               LIMIT alimit
5675               OFFSET ABS(page) * pagesize - aoffset;
5676     END IF;
5677 END;
5678 $$ LANGUAGE PLPGSQL ROWS 10;
5679
5680 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 $$
5681 DECLARE
5682     pivot_sort_value    TEXT;
5683 BEGIN
5684
5685     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5686
5687     IF page < 0 THEN
5688         RETURN QUERY
5689             -- "bottom" half of the browse results
5690             SELECT id FROM (
5691                 SELECT  ash.id,
5692                         row_number() over ()
5693                   FROM  authority.simple_heading ash
5694                   WHERE ash.atag = ANY (atag_list)
5695                         AND ash.sort_value < pivot_sort_value
5696                   ORDER BY ash.sort_value DESC
5697                   LIMIT pagesize
5698                   OFFSET (ABS(page) - 1) * pagesize
5699             ) x ORDER BY row_number DESC;
5700     END IF;
5701
5702     IF page >= 0 THEN
5703         RETURN QUERY
5704             -- "bottom" half of the browse results
5705             SELECT  ash.id
5706               FROM  authority.simple_heading ash
5707               WHERE ash.atag = ANY (atag_list)
5708                     AND ash.sort_value >= pivot_sort_value
5709               ORDER BY ash.sort_value
5710               LIMIT pagesize
5711               OFFSET ABS(page) * pagesize ;
5712     END IF;
5713 END;
5714 $$ LANGUAGE PLPGSQL ROWS 10;
5715
5716 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 $$
5717     SELECT  ash.id
5718       FROM  authority.simple_heading ash,
5719             public.naco_normalize($2) t(term),
5720             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5721       WHERE ash.atag = ANY ($1)
5722             AND ash.index_vector @@ ptsq.term
5723       ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
5724                     + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
5725                     + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
5726       LIMIT $4
5727       OFFSET $4 * $3;
5728 $$ LANGUAGE SQL ROWS 10;
5729
5730 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 $$
5731     SELECT  ash.id
5732       FROM  authority.simple_heading ash,
5733             public.naco_normalize($2) t(term),
5734             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5735       WHERE ash.atag = ANY ($1)
5736             AND ash.index_vector @@ ptsq.term
5737       ORDER BY ash.sort_value
5738       LIMIT $4
5739       OFFSET $4 * $3;
5740 $$ LANGUAGE SQL ROWS 10;
5741
5742
5743 CREATE OR REPLACE FUNCTION authority.axis_authority_tags(a TEXT) RETURNS INT[] AS $$
5744     SELECT ARRAY_ACCUM(field) FROM authority.browse_axis_authority_field_map WHERE axis = $1;
5745 $$ LANGUAGE SQL;
5746
5747 CREATE OR REPLACE FUNCTION authority.axis_authority_tags_refs(a TEXT) RETURNS INT[] AS $$
5748     SELECT  ARRAY_CAT(
5749                 ARRAY[a.field],
5750                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.field)
5751             )
5752       FROM  authority.browse_axis_authority_field_map a
5753       WHERE axis = $1
5754 $$ LANGUAGE SQL;
5755
5756
5757
5758 CREATE OR REPLACE FUNCTION authority.btag_authority_tags(btag TEXT) RETURNS INT[] AS $$
5759     SELECT ARRAY_ACCUM(authority_field) FROM authority.control_set_bib_field WHERE tag = $1
5760 $$ LANGUAGE SQL;
5761
5762 CREATE OR REPLACE FUNCTION authority.btag_authority_tags_refs(btag TEXT) RETURNS INT[] AS $$
5763     SELECT  ARRAY_CAT(
5764                 ARRAY[a.authority_field],
5765                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.authority_field)
5766             )
5767       FROM  authority.control_set_bib_field a
5768       WHERE a.tag = $1
5769 $$ LANGUAGE SQL;
5770
5771
5772
5773 CREATE OR REPLACE FUNCTION authority.atag_authority_tags(atag TEXT) RETURNS INT[] AS $$
5774     SELECT ARRAY_ACCUM(id) FROM authority.control_set_authority_field WHERE tag = $1
5775 $$ LANGUAGE SQL;
5776
5777 CREATE OR REPLACE FUNCTION authority.atag_authority_tags_refs(atag TEXT) RETURNS INT[] AS $$
5778     SELECT  ARRAY_CAT(
5779                 ARRAY[a.id],
5780                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.id)
5781             )
5782       FROM  authority.control_set_authority_field a
5783       WHERE a.tag = $1
5784 $$ LANGUAGE SQL;
5785
5786
5787 CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5788     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4)
5789 $$ LANGUAGE SQL ROWS 10;
5790
5791 CREATE OR REPLACE FUNCTION authority.btag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5792     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4)
5793 $$ LANGUAGE SQL ROWS 10;
5794
5795 CREATE OR REPLACE FUNCTION authority.atag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5796     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4)
5797 $$ LANGUAGE SQL ROWS 10;
5798
5799 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 $$
5800     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4)
5801 $$ LANGUAGE SQL ROWS 10;
5802
5803 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 $$
5804     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4)
5805 $$ LANGUAGE SQL ROWS 10;
5806
5807 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 $$
5808     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4)
5809 $$ LANGUAGE SQL ROWS 10;
5810
5811
5812 CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5813     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4)
5814 $$ LANGUAGE SQL ROWS 10;
5815
5816 CREATE OR REPLACE FUNCTION authority.btag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5817     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4)
5818 $$ LANGUAGE SQL ROWS 10;
5819
5820 CREATE OR REPLACE FUNCTION authority.atag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5821     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4)
5822 $$ LANGUAGE SQL ROWS 10;
5823
5824 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 $$
5825     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4)
5826 $$ LANGUAGE SQL ROWS 10;
5827
5828 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 $$
5829     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4)
5830 $$ LANGUAGE SQL ROWS 10;
5831
5832 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 $$
5833     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4)
5834 $$ LANGUAGE SQL ROWS 10;
5835
5836
5837 CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5838     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4)
5839 $$ LANGUAGE SQL ROWS 10;
5840
5841 CREATE OR REPLACE FUNCTION authority.btag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5842     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4)
5843 $$ LANGUAGE SQL ROWS 10;
5844
5845 CREATE OR REPLACE FUNCTION authority.atag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5846     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4)
5847 $$ LANGUAGE SQL ROWS 10;
5848
5849 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 $$
5850     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4)
5851 $$ LANGUAGE SQL ROWS 10;
5852
5853 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 $$
5854     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4)
5855 $$ LANGUAGE SQL ROWS 10;
5856
5857 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 $$
5858     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4)
5859 $$ LANGUAGE SQL ROWS 10;
5860
5861
5862 CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5863     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4)
5864 $$ LANGUAGE SQL ROWS 10;
5865
5866 CREATE OR REPLACE FUNCTION authority.btag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5867     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4)
5868 $$ LANGUAGE SQL ROWS 10;
5869
5870 CREATE OR REPLACE FUNCTION authority.atag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5871     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4)
5872 $$ LANGUAGE SQL ROWS 10;
5873
5874 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 $$
5875     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4)
5876 $$ LANGUAGE SQL ROWS 10;
5877
5878 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 $$
5879     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4)
5880 $$ LANGUAGE SQL ROWS 10;
5881
5882 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 $$
5883     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4)
5884 $$ LANGUAGE SQL ROWS 10;
5885
5886
5887
5888 -- Evergreen DB patch 0641.schema.org_unit_setting_json_check.sql
5889 --
5890 --
5891
5892 -- check whether patch can be applied
5893 SELECT evergreen.upgrade_deps_block_check('0641', :eg_version);
5894
5895 ALTER TABLE actor.org_unit_setting ADD CONSTRAINT aous_must_be_json CHECK ( is_json(value) );
5896
5897 -- Evergreen DB patch 0642.data.acq-worksheet-hold-count.sql
5898
5899 -- check whether patch can be applied
5900 SELECT evergreen.upgrade_deps_block_check('0642', :eg_version);
5901
5902 UPDATE action_trigger.event_definition SET template = 
5903 $$
5904 [%- USE date -%]
5905 [%- SET li = target; -%]
5906 <div class="wrapper">
5907     <div class="summary" style='font-size:110%; font-weight:bold;'>
5908
5909         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
5910         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
5911         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
5912         <div class="lineid">Lineitem ID: [% li.id %]</div>
5913         <div>Open Holds: [% helpers.bre_open_hold_count(li.eg_bib_id) %]</div>
5914
5915         [% IF li.distribution_formulas.size > 0 %]
5916             [% SET forms = [] %]
5917             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
5918             <div>Distribution Formulas: [% forms.join(',') %]</div>
5919         [% END %]
5920
5921         [% IF li.lineitem_notes.size > 0 %]
5922             Lineitem Notes:
5923             <ul>
5924                 [%- FOR note IN li.lineitem_notes -%]
5925                     <li>
5926                     [% IF note.alert_text %]
5927                         [% note.alert_text.code -%] 
5928                         [% IF note.value -%]
5929                             : [% note.value %]
5930                         [% END %]
5931                     [% ELSE %]
5932                         [% note.value -%] 
5933                     [% END %]
5934                     </li>
5935                 [% END %]
5936             </ul>
5937         [% END %]
5938     </div>
5939     <br/>
5940     <table>
5941         <thead>
5942             <tr>
5943                 <th>Branch</th>
5944                 <th>Barcode</th>
5945                 <th>Call Number</th>
5946                 <th>Fund</th>
5947                 <th>Shelving Location</th>
5948                 <th>Recd.</th>
5949                 <th>Notes</th>
5950             </tr>
5951         </thead>
5952         <tbody>
5953         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
5954             [% 
5955                 IF detail.eg_copy_id;
5956                     SET copy = detail.eg_copy_id;
5957                     SET cn_label = copy.call_number.label;
5958                 ELSE; 
5959                     SET copy = detail; 
5960                     SET cn_label = detail.cn_label;
5961                 END 
5962             %]
5963             <tr>
5964                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
5965                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
5966                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
5967                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
5968                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
5969                 <td style='padding:5px;'>[% copy.location.name %]</td>
5970                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
5971                 <td style='padding:5px;'>[% detail.note %]</td>
5972             </tr>
5973         [% END %]
5974         </tbody>
5975     </table>
5976 </div>
5977 $$
5978 WHERE id = 14;
5979
5980
5981 SELECT evergreen.upgrade_deps_block_check('0643', :eg_version);
5982
5983 DO $$
5984 DECLARE x TEXT;
5985 BEGIN
5986
5987     FOR x IN
5988         SELECT  marc
5989           FROM  authority.record_entry
5990           WHERE id > 0
5991                 AND NOT deleted
5992                 AND id NOT IN (SELECT DISTINCT record FROM authority.simple_heading)
5993     LOOP
5994         INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5995             SELECT record, atag, value, sort_value FROM authority.simple_heading_set(x);
5996     END LOOP;
5997 END;
5998 $$;
5999
6000
6001
6002 SELECT evergreen.upgrade_deps_block_check('0644', :eg_version);
6003
6004 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6005 ( 'circ.holds.target_when_closed', 'circ',
6006     oils_i18n_gettext('circ.holds.target_when_closed',
6007         'Target copies for a hold even if copy''s circ lib is closed',
6008         'coust', 'label'),
6009     oils_i18n_gettext('circ.holds.target_when_closed',
6010         '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).',
6011         'coust', 'description'),
6012     'bool'),
6013 ( 'circ.holds.target_when_closed_if_at_pickup_lib', 'circ',
6014     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6015         'Target copies for a hold even if copy''s circ lib is closed IF the circ lib is the hold''s pickup lib',
6016         'coust', 'label'),
6017     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6018         '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.',
6019         'coust', 'description'),
6020     'bool')
6021 ;
6022
6023 -- Evergreen DB patch XXXX.data.hold-notification-cleanup-mod.sql
6024
6025 -- check whether patch can be applied
6026 SELECT evergreen.upgrade_deps_block_check('0647', :eg_version);
6027
6028 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
6029     'CreateHoldNotification',
6030     oils_i18n_gettext(
6031         'CreateHoldNotification',
6032         'Creates a hold_notification record for each notified hold',
6033         'atclean',
6034         'description'
6035     )
6036 );
6037
6038 UPDATE action_trigger.event_definition 
6039     SET 
6040         cleanup_success = 'CreateHoldNotification' 
6041     WHERE 
6042         id = 5 -- stock hold-ready email event_def
6043         AND cleanup_success IS NULL; -- don't clobber any existing cleanup mod
6044
6045 -- Evergreen DB patch XXXX.schema.unnest-hold-permit-upgrade-script-repair.sql
6046 --
6047 -- This patch makes no changes to the baseline schema and is 
6048 -- only meant to repair a previous upgrade script.
6049 --
6050
6051 -- check whether patch can be applied
6052 SELECT evergreen.upgrade_deps_block_check('0651', :eg_version);
6053
6054 --Removed dupe action.hold_request_permit_test
6055
6056 -- Evergreen DB patch XXXX.data.vandelay-queue-bib-bucket-type.sql
6057 --
6058
6059 -- check whether patch can be applied
6060 SELECT evergreen.upgrade_deps_block_check('0652', :eg_version);
6061
6062 INSERT INTO container.biblio_record_entry_bucket_type (code, label) VALUES (
6063     'vandelay_queue',
6064     oils_i18n_gettext('vandelay_queue', 'Vandelay Queue', 'cbrebt', 'label')
6065 );
6066
6067 -- Evergreen DB patch XXXX.schema.unapi-indb-optional-org.sql
6068
6069 -- check whether patch can be applied
6070 SELECT evergreen.upgrade_deps_block_check('0653', :eg_version);
6071
6072 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;
6073
6074 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$
6075 DECLARE
6076     layout          unapi.bre_output_layout%ROWTYPE;
6077     transform       config.xml_transform%ROWTYPE;
6078     item_format     TEXT;
6079     tmp_xml         TEXT;
6080     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
6081     ouid            INT;
6082     element_list    TEXT[];
6083 BEGIN
6084
6085     IF org = '-' OR org IS NULL THEN
6086         SELECT shortname INTO org FROM evergreen.org_top();
6087     END IF;
6088
6089     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6090     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6091
6092     IF layout.name IS NULL THEN
6093         RETURN NULL::XML;
6094     END IF;
6095
6096     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
6097     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
6098
6099     -- Gather the bib xml
6100     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
6101
6102     IF layout.title_element IS NOT NULL THEN
6103         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;
6104     END IF;
6105
6106     IF layout.description_element IS NOT NULL THEN
6107         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;
6108     END IF;
6109
6110     IF layout.creator_element IS NOT NULL THEN
6111         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;
6112     END IF;
6113
6114     IF layout.update_ts_element IS NOT NULL THEN
6115         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;
6116     END IF;
6117
6118     IF unapi_url IS NOT NULL THEN
6119         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;
6120     END IF;
6121
6122     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
6123
6124     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
6125     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
6126         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;
6127     END LOOP;
6128
6129     RETURN tmp_xml::XML;
6130 END;
6131 $F$ LANGUAGE PLPGSQL;
6132
6133 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$
6134 DECLARE
6135     me      biblio.record_entry%ROWTYPE;
6136     layout  unapi.bre_output_layout%ROWTYPE;
6137     xfrm    config.xml_transform%ROWTYPE;
6138     ouid    INT;
6139     tmp_xml TEXT;
6140     top_el  TEXT;
6141     output  XML;
6142     hxml    XML;
6143     axml    XML;
6144 BEGIN
6145
6146     IF org = '-' OR org IS NULL THEN
6147         SELECT shortname INTO org FROM evergreen.org_top();
6148     END IF;
6149
6150     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6151
6152     IF ouid IS NULL THEN
6153         RETURN NULL::XML;
6154     END IF;
6155
6156     IF format = 'holdings_xml' THEN -- the special case
6157         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6158         RETURN output;
6159     END IF;
6160
6161     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6162
6163     IF layout.name IS NULL THEN
6164         RETURN NULL::XML;
6165     END IF;
6166
6167     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6168
6169     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6170
6171     -- grab SVF if we need them
6172     IF ('mra' = ANY (includes)) THEN 
6173         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6174     ELSE
6175         axml := NULL::XML;
6176     END IF;
6177
6178     -- grab hodlings if we need them
6179     IF ('holdings_xml' = ANY (includes)) THEN 
6180         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6181     ELSE
6182         hxml := NULL::XML;
6183     END IF;
6184
6185
6186     -- generate our item node
6187
6188
6189     IF format = 'marcxml' THEN
6190         tmp_xml := me.marc;
6191         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6192            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6193         END IF; 
6194     ELSE
6195         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6196     END IF;
6197
6198     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6199
6200     IF axml IS NOT NULL THEN 
6201         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6202     END IF;
6203
6204     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6205         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6206     END IF;
6207
6208     IF ('bre.unapi' = ANY (includes)) THEN 
6209         output := REGEXP_REPLACE(
6210             tmp_xml,
6211             '</' || top_el || '>(.*?)',
6212             XMLELEMENT(
6213                 name abbr,
6214                 XMLATTRIBUTES(
6215                     'http://www.w3.org/1999/xhtml' AS xmlns,
6216                     'unapi-id' AS class,
6217                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6218                 )
6219             )::TEXT || '</' || top_el || E'>\\1'
6220         );
6221     ELSE
6222         output := tmp_xml;
6223     END IF;
6224
6225     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6226     RETURN output;
6227 END;
6228 $F$ LANGUAGE PLPGSQL;
6229
6230
6231
6232
6233 SELECT evergreen.upgrade_deps_block_check('0654', :eg_version);
6234
6235 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6236  ( 514, 'UPDATE_PATRON_ACTIVE_CARD', oils_i18n_gettext( 514,
6237     'Allows a user to manually adjust a patron''s active cards', 'ppl', 'description')),
6238  ( 515, 'UPDATE_PATRON_PRIMARY_CARD', oils_i18n_gettext( 515,
6239     'Allows a user to manually adjust a patron''s primary card', 'ppl', 'description'));
6240
6241 -- Evergreen DB patch 0655.config.bib_source.can_have_copies.sql
6242 --
6243 -- This column introduces the ability to prevent bib records associated
6244 -- with specific bib sources from being able to have volumes or MFHD
6245 -- records attached to them.
6246 --
6247
6248 -- check whether patch can be applied
6249 SELECT evergreen.upgrade_deps_block_check('0655', :eg_version);
6250
6251 ALTER TABLE config.bib_source
6252 ADD COLUMN can_have_copies BOOL NOT NULL DEFAULT TRUE;
6253
6254 -- Evergreen DB patch XXXX.LP893315_schema.function.filter_deleted_acns_from_unapi.holdings_xml.sql
6255 --
6256 -- Prevent deleted call numbers from hiding active call numbers / copies / URIs
6257 --
6258
6259 -- check whether patch can be applied
6260 SELECT evergreen.upgrade_deps_block_check('0656', :eg_version);
6261
6262 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$
6263      SELECT  XMLELEMENT(
6264                  name holdings,
6265                  XMLATTRIBUTES(
6266                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6267                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6268                  ),
6269                  XMLELEMENT(
6270                      name counts,
6271                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
6272                          SELECT  XMLELEMENT(
6273                                      name count,
6274                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6275                                  )::text
6276                            FROM  asset.opac_ou_record_copy_count($2,  $1)
6277                                      UNION
6278                          SELECT  XMLELEMENT(
6279                                      name count,
6280                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6281                                  )::text
6282                            FROM  asset.staff_ou_record_copy_count($2, $1)
6283                                      ORDER BY 1
6284                      )x)
6285                  ),
6286                  CASE 
6287                      WHEN ('bmp' = ANY ($5)) THEN
6288                         XMLELEMENT(
6289                             name monograph_parts,
6290                             (SELECT XMLAGG(bmp) FROM (
6291                                 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)
6292                                   FROM  biblio.monograph_part
6293                                   WHERE record = $1
6294                             )x)
6295                         )
6296                      ELSE NULL
6297                  END,
6298                  XMLELEMENT(
6299                      name volumes,
6300                      (SELECT XMLAGG(acn) FROM (
6301                         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)
6302                           FROM  asset.call_number acn
6303                           WHERE acn.record = $1
6304                                 AND acn.deleted IS FALSE
6305                                 AND EXISTS (
6306                                     SELECT  1
6307                                       FROM  asset.copy acp
6308                                             JOIN actor.org_unit_descendants(
6309                                                 $2,
6310                                                 (COALESCE(
6311                                                     $4,
6312                                                     (SELECT aout.depth
6313                                                       FROM  actor.org_unit_type aout
6314                                                             JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6315                                                     )
6316                                                 ))
6317                                             ) aoud ON (acp.circ_lib = aoud.id)
6318                                       LIMIT 1
6319                                )
6320                           ORDER BY label_sortkey
6321                           LIMIT $6
6322                           OFFSET $7
6323                      )x)
6324                  ),
6325                  CASE WHEN ('ssub' = ANY ($5)) THEN 
6326                      XMLELEMENT(
6327                          name subscriptions,
6328                          (SELECT XMLAGG(ssub) FROM (
6329                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6330                               FROM  serial.subscription
6331                               WHERE record_entry = $1
6332                         )x)
6333                      )
6334                  ELSE NULL END,
6335                  CASE WHEN ('acp' = ANY ($5)) THEN 
6336                      XMLELEMENT(
6337                          name foreign_copies,
6338                          (SELECT XMLAGG(acp) FROM (
6339                             SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6340                               FROM  biblio.peer_bib_copy_map p
6341                                     JOIN asset.copy c ON (p.target_copy = c.id)
6342                               WHERE NOT c.deleted AND peer_record = $1
6343                         )x)
6344                      )
6345                  ELSE NULL END
6346              );
6347 $F$ LANGUAGE SQL;
6348
6349 -- Evergreen DB patch 0657.schema.address-alert.sql
6350 --
6351
6352 -- check whether patch can be applied
6353 SELECT evergreen.upgrade_deps_block_check('0657', :eg_version);
6354
6355 CREATE TABLE actor.address_alert (
6356     id              SERIAL  PRIMARY KEY,
6357     owner           INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6358     active          BOOL    NOT NULL DEFAULT TRUE,
6359     match_all       BOOL    NOT NULL DEFAULT TRUE,
6360     alert_message   TEXT    NOT NULL,
6361     street1         TEXT,
6362     street2         TEXT,
6363     city            TEXT,
6364     county          TEXT,
6365     state           TEXT,
6366     country         TEXT,
6367     post_code       TEXT,
6368     mailing_address BOOL    NOT NULL DEFAULT FALSE,
6369     billing_address BOOL    NOT NULL DEFAULT FALSE
6370 );
6371
6372 CREATE OR REPLACE FUNCTION actor.address_alert_matches (
6373         org_unit INT, 
6374         street1 TEXT, 
6375         street2 TEXT, 
6376         city TEXT, 
6377         county TEXT, 
6378         state TEXT, 
6379         country TEXT, 
6380         post_code TEXT,
6381         mailing_address BOOL DEFAULT FALSE,
6382         billing_address BOOL DEFAULT FALSE
6383     ) RETURNS SETOF actor.address_alert AS $$
6384
6385 SELECT *
6386 FROM actor.address_alert
6387 WHERE
6388     active
6389     AND owner IN (SELECT id FROM actor.org_unit_ancestors($1)) 
6390     AND (
6391         (NOT mailing_address AND NOT billing_address)
6392         OR (mailing_address AND $9)
6393         OR (billing_address AND $10)
6394     )
6395     AND (
6396             (
6397                 match_all
6398                 AND COALESCE($2, '') ~* COALESCE(street1,   '.*')
6399                 AND COALESCE($3, '') ~* COALESCE(street2,   '.*')
6400                 AND COALESCE($4, '') ~* COALESCE(city,      '.*')
6401                 AND COALESCE($5, '') ~* COALESCE(county,    '.*')
6402                 AND COALESCE($6, '') ~* COALESCE(state,     '.*')
6403                 AND COALESCE($7, '') ~* COALESCE(country,   '.*')
6404                 AND COALESCE($8, '') ~* COALESCE(post_code, '.*')
6405             ) OR (
6406                 NOT match_all 
6407                 AND (  
6408                        $2 ~* street1
6409                     OR $3 ~* street2
6410                     OR $4 ~* city
6411                     OR $5 ~* county
6412                     OR $6 ~* state
6413                     OR $7 ~* country
6414                     OR $8 ~* post_code
6415                 )
6416             )
6417         )
6418     ORDER BY actor.org_unit_proximity(owner, $1)
6419 $$ LANGUAGE SQL;
6420
6421
6422 /* UNDO
6423 DROP FUNCTION actor.address_alert_matches(INT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, BOOL, BOOL);
6424 DROP TABLE actor.address_alert;
6425 */
6426 -- Evergreen DB patch 0659.add_create_report_perms.sql
6427 --
6428 -- Add a permission to control the ability to create report templates
6429 --
6430
6431 -- check whether patch can be applied
6432 SELECT evergreen.upgrade_deps_block_check('0659', :eg_version);
6433
6434 -- FIXME: add/check SQL statements to perform the upgrade
6435 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6436  ( 516, 'CREATE_REPORT_TEMPLATE', oils_i18n_gettext( 516,
6437     'Allows a user to create report templates', 'ppl', 'description' ));
6438
6439 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6440     SELECT grp, 516, depth, grantable
6441         FROM permission.grp_perm_map
6442         WHERE perm = (
6443             SELECT id
6444                 FROM permission.perm_list
6445                 WHERE code = 'RUN_REPORTS'
6446         );
6447
6448
6449
6450 SELECT evergreen.upgrade_deps_block_check('0660', :eg_version);
6451
6452 UPDATE action_trigger.event_definition SET template = $$
6453 [%-
6454 # target is the bookbag itself. The 'items' variable does not need to be in
6455 # the environment because a special reactor will take care of filling it in.
6456
6457 FOR item IN items;
6458     bibxml = helpers.unapi_bre(item.target_biblio_record_entry, {flesh => '{mra}'});
6459     title = "";
6460     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
6461         title = title _ part.textContent;
6462     END;
6463     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
6464     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
6465
6466     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";
6467 END -%]
6468 $$
6469 WHERE reactor = 'ContainerCSV';
6470
6471 -- Evergreen DB patch 0661.data.yaous-opac-tag-circed-items.sql
6472 --
6473 -- Add org unit setting that enables users who have opted in to
6474 -- tracking their circulation history to see which items they
6475 -- have previously checked out in search results.
6476 --
6477
6478 -- check whether patch can be applied
6479 SELECT evergreen.upgrade_deps_block_check('0661', :eg_version);
6480
6481 INSERT into config.org_unit_setting_type 
6482     (name, grp, label, description, datatype) 
6483     VALUES ( 
6484         'opac.search.tag_circulated_items', 
6485         'opac',
6486         oils_i18n_gettext(
6487             'opac.search.tag_circulated_items',
6488             'Tag Circulated Items in Results',
6489             'coust', 
6490             'label'
6491         ),
6492         oils_i18n_gettext(
6493             'opac.search.tag_circulated_items',
6494             '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',
6495             'coust', 
6496             'description'
6497         ),
6498         'bool'
6499     );
6500
6501
6502 -- Evergreen DB patch 0662.schema.coded-value-map-index-normalizer.sql
6503 --
6504
6505 -- check whether patch can be applied
6506 SELECT evergreen.upgrade_deps_block_check('0662', :eg_version);
6507
6508 -- create the normalizer
6509 CREATE OR REPLACE FUNCTION evergreen.coded_value_map_normalizer( input TEXT, ctype TEXT ) 
6510     RETURNS TEXT AS $F$
6511         SELECT COALESCE(value,$1) 
6512             FROM config.coded_value_map 
6513             WHERE ctype = $2 AND code = $1;
6514 $F$ LANGUAGE SQL;
6515
6516 -- register the normalizer
6517 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6518     'Coded Value Map Normalizer', 
6519     'Applies coded_value_map mapping of values',
6520     'coded_value_map_normalizer', 
6521     1
6522 );
6523
6524 -- Evergreen DB patch 0663.schema.archive_circ_stat_cats.sql
6525 --
6526 -- Enables users to set copy and patron stat cats to be archivable
6527 -- for the purposes of statistics even after the circs are aged.
6528 --
6529
6530 -- check whether patch can be applied
6531 SELECT evergreen.upgrade_deps_block_check('0663', :eg_version);
6532
6533 -- New tables
6534
6535 CREATE TABLE action.archive_actor_stat_cat (
6536     id          BIGSERIAL   PRIMARY KEY,
6537     xact        BIGINT      NOT NULL,
6538     stat_cat    INT         NOT NULL,
6539     value       TEXT        NOT NULL
6540 );
6541
6542 CREATE TABLE action.archive_asset_stat_cat (
6543     id          BIGSERIAL   PRIMARY KEY,
6544     xact        BIGINT      NOT NULL,
6545     stat_cat    INT         NOT NULL,
6546     value       TEXT        NOT NULL
6547 );
6548
6549 -- Add columns to existing tables
6550
6551 -- Archive Flag Columns
6552 ALTER TABLE actor.stat_cat
6553     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6554 ALTER TABLE asset.stat_cat
6555     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6556
6557 -- Circulation copy column
6558 ALTER TABLE action.circulation
6559     ADD COLUMN copy_location INT NULL REFERENCES asset.copy_location(id) DEFERRABLE INITIALLY DEFERRED;
6560
6561 -- Create trigger function to auto-fill the copy_location field
6562 CREATE OR REPLACE FUNCTION action.fill_circ_copy_location () RETURNS TRIGGER AS $$
6563 BEGIN
6564     SELECT INTO NEW.copy_location location FROM asset.copy WHERE id = NEW.target_copy;
6565     RETURN NEW;
6566 END;
6567 $$ LANGUAGE PLPGSQL;
6568
6569 -- Create trigger function to auto-archive stat cat entries
6570 CREATE OR REPLACE FUNCTION action.archive_stat_cats () RETURNS TRIGGER AS $$
6571 BEGIN
6572     INSERT INTO action.archive_actor_stat_cat(xact, stat_cat, value)
6573         SELECT NEW.id, asceum.stat_cat, asceum.stat_cat_entry
6574         FROM actor.stat_cat_entry_usr_map asceum
6575              JOIN actor.stat_cat sc ON asceum.stat_cat = sc.id
6576         WHERE NEW.usr = asceum.target_usr AND sc.checkout_archive;
6577     INSERT INTO action.archive_asset_stat_cat(xact, stat_cat, value)
6578         SELECT NEW.id, ascecm.stat_cat, asce.value
6579         FROM asset.stat_cat_entry_copy_map ascecm
6580              JOIN asset.stat_cat sc ON ascecm.stat_cat = sc.id
6581              JOIN asset.stat_cat_entry asce ON ascecm.stat_cat_entry = asce.id
6582         WHERE NEW.target_copy = ascecm.owning_copy AND sc.checkout_archive;
6583     RETURN NULL;
6584 END;
6585 $$ LANGUAGE PLPGSQL;
6586
6587 -- Apply triggers
6588 CREATE TRIGGER fill_circ_copy_location_tgr BEFORE INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.fill_circ_copy_location();
6589 CREATE TRIGGER archive_stat_cats_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.archive_stat_cats();
6590
6591 -- Ensure all triggers are disabled for speedy updates!
6592 ALTER TABLE action.circulation DISABLE TRIGGER ALL;
6593
6594 -- Update view to use circ's copy_location field instead of the copy's current copy_location field
6595 CREATE OR REPLACE VIEW action.all_circulation AS
6596     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6597         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6598         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
6599         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6600         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6601         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6602       FROM  action.aged_circulation
6603             UNION ALL
6604     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,
6605         cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
6606         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6607         circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6608         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6609         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6610         circ.parent_circ
6611       FROM  action.circulation circ
6612         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6613         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6614         JOIN actor.usr p ON (circ.usr = p.id)
6615         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6616         LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
6617
6618 -- Update action.circulation with real copy_location numbers instead of all NULL
6619 DO $$BEGIN RAISE WARNING 'We are about to do an update on every row in action.circulation. This may take a while. %', timeofday(); END;$$;
6620 UPDATE action.circulation circ SET copy_location = ac.location FROM asset.copy ac WHERE ac.id = circ.target_copy;
6621
6622 -- Set not null/default on new column, re-enable triggers
6623 ALTER TABLE action.circulation
6624     ALTER COLUMN copy_location SET NOT NULL,
6625     ALTER COLUMN copy_location SET DEFAULT 1,
6626     ENABLE TRIGGER ALL;
6627
6628 -- Evergreen DB patch 0664.schema.hold-current-shelf-lib.sql
6629 --
6630 --
6631
6632
6633 -- check whether patch can be applied
6634 SELECT evergreen.upgrade_deps_block_check('0664', :eg_version);
6635
6636 -- add the new column
6637 ALTER TABLE action.hold_request ADD COLUMN current_shelf_lib 
6638     INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED;
6639
6640 -- Add some others before the UPDATE we are about to do breaks our ability to add columns
6641 -- But we need this table first.
6642 CREATE TABLE config.sms_carrier (
6643     id              SERIAL PRIMARY KEY,
6644     region          TEXT,
6645     name            TEXT,
6646     email_gateway   TEXT,
6647     active          BOOLEAN DEFAULT TRUE
6648 );
6649
6650 ALTER TABLE action.hold_request ADD COLUMN sms_notify TEXT;
6651 ALTER TABLE action.hold_request ADD COLUMN sms_carrier INT REFERENCES config.sms_carrier (id);
6652 ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
6653     sms_notify IS NULL
6654     OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
6655 );
6656
6657
6658
6659 -- set the value for current_shelf_lib on existing shelved holds
6660 UPDATE action.hold_request
6661     SET current_shelf_lib = pickup_lib
6662     FROM asset.copy
6663     WHERE 
6664             action.hold_request.shelf_time IS NOT NULL 
6665         AND action.hold_request.capture_time IS NOT NULL
6666         AND action.hold_request.current_copy IS NOT NULL
6667         AND action.hold_request.fulfillment_time IS NULL
6668         AND action.hold_request.cancel_time IS NULL
6669         AND asset.copy.id = action.hold_request.current_copy
6670         AND asset.copy.status = 8; -- on holds shelf
6671
6672
6673 SELECT evergreen.upgrade_deps_block_check('0666', :eg_version);
6674
6675 -- 950.data.seed-values.sql
6676 INSERT INTO config.settings_group (name, label) VALUES
6677     (
6678         'sms',
6679         oils_i18n_gettext(
6680             'sms',
6681             'SMS Text Messages',
6682             'csg',
6683             'label'
6684         )
6685     )
6686 ;
6687
6688 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6689     (
6690         'sms.enable',
6691         'sms',
6692         oils_i18n_gettext(
6693             'sms.enable',
6694             'Enable features that send SMS text messages.',
6695             'coust',
6696             'label'
6697         ),
6698         oils_i18n_gettext(
6699             'sms.enable',
6700             '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.',
6701             'coust',
6702             'description'
6703         ),
6704         'bool'
6705     )
6706     ,(
6707         'sms.disable_authentication_requirement.callnumbers',
6708         'sms',
6709         oils_i18n_gettext(
6710             'sms.disable_authentication_requirement.callnumbers',
6711             'Disable auth requirement for texting call numbers.',
6712             'coust',
6713             'label'
6714         ),
6715         oils_i18n_gettext(
6716             'sms.disable_authentication_requirement.callnumbers',
6717             'Disable authentication requirement for sending call number information via SMS from the OPAC.',
6718             'coust',
6719             'description'
6720         ),
6721         'bool'
6722     )
6723 ;
6724
6725 -- 090.schema.action.sql
6726 -- 950.data.seed-values.sql
6727 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
6728     'opac.default_sms_carrier',
6729     'sms',
6730     TRUE,
6731     oils_i18n_gettext(
6732         'opac.default_sms_carrier',
6733         'Default SMS/Text Carrier',
6734         'cust',
6735         'label'
6736     ),
6737     oils_i18n_gettext(
6738         'opac.default_sms_carrier',
6739         'Default SMS/Text Carrier',
6740         'cust',
6741         'description'
6742     ),
6743     'link',
6744     'csc'
6745 );
6746
6747 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6748     'opac.default_sms_notify',
6749     'sms',
6750     TRUE,
6751     oils_i18n_gettext(
6752         'opac.default_sms_notify',
6753         'Default SMS/Text Number',
6754         'cust',
6755         'label'
6756     ),
6757     oils_i18n_gettext(
6758         'opac.default_sms_notify',
6759         'Default SMS/Text Number',
6760         'cust',
6761         'description'
6762     ),
6763     'string'
6764 );
6765
6766 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6767     'opac.default_phone',
6768     'opac',
6769     TRUE,
6770     oils_i18n_gettext(
6771         'opac.default_phone',
6772         'Default Phone Number',
6773         'cust',
6774         'label'
6775     ),
6776     oils_i18n_gettext(
6777         'opac.default_phone',
6778         'Default Phone Number',
6779         'cust',
6780         'description'
6781     ),
6782     'string'
6783 );
6784
6785 SELECT setval( 'config.sms_carrier_id_seq', 1000 );
6786 INSERT INTO config.sms_carrier VALUES
6787
6788     -- Testing
6789     (
6790         1,
6791         oils_i18n_gettext(
6792             1,
6793             'Local',
6794             'csc',
6795             'region'
6796         ),
6797         oils_i18n_gettext(
6798             1,
6799             'Test Carrier',
6800             'csc',
6801             'name'
6802         ),
6803         'opensrf+$number@localhost',
6804         FALSE
6805     ),
6806
6807     -- Canada & USA
6808     (
6809         2,
6810         oils_i18n_gettext(
6811             2,
6812             'Canada & USA',
6813             'csc',
6814             'region'
6815         ),
6816         oils_i18n_gettext(
6817             2,
6818             'Rogers Wireless',
6819             'csc',
6820             'name'
6821         ),
6822         '$number@pcs.rogers.com',
6823         TRUE
6824     ),
6825     (
6826         3,
6827         oils_i18n_gettext(
6828             3,
6829             'Canada & USA',
6830             'csc',
6831             'region'
6832         ),
6833         oils_i18n_gettext(
6834             3,
6835             'Rogers Wireless (Alternate)',
6836             'csc',
6837             'name'
6838         ),
6839         '1$number@mms.rogers.com',
6840         TRUE
6841     ),
6842     (
6843         4,
6844         oils_i18n_gettext(
6845             4,
6846             'Canada & USA',
6847             'csc',
6848             'region'
6849         ),
6850         oils_i18n_gettext(
6851             4,
6852             'Telus Mobility',
6853             'csc',
6854             'name'
6855         ),
6856         '$number@msg.telus.com',
6857         TRUE
6858     ),
6859
6860     -- Canada
6861     (
6862         5,
6863         oils_i18n_gettext(
6864             5,
6865             'Canada',
6866             'csc',
6867             'region'
6868         ),
6869         oils_i18n_gettext(
6870             5,
6871             'Koodo Mobile',
6872             'csc',
6873             'name'
6874         ),
6875         '$number@msg.telus.com',
6876         TRUE
6877     ),
6878     (
6879         6,
6880         oils_i18n_gettext(
6881             6,
6882             'Canada',
6883             'csc',
6884             'region'
6885         ),
6886         oils_i18n_gettext(
6887             6,
6888             'Fido',
6889             'csc',
6890             'name'
6891         ),
6892         '$number@fido.ca',
6893         TRUE
6894     ),
6895     (
6896         7,
6897         oils_i18n_gettext(
6898             7,
6899             'Canada',
6900             'csc',
6901             'region'
6902         ),
6903         oils_i18n_gettext(
6904             7,
6905             'Bell Mobility & Solo Mobile',
6906             'csc',
6907             'name'
6908         ),
6909         '$number@txt.bell.ca',
6910         TRUE
6911     ),
6912     (
6913         8,
6914         oils_i18n_gettext(
6915             8,
6916             'Canada',
6917             'csc',
6918             'region'
6919         ),
6920         oils_i18n_gettext(
6921             8,
6922             'Bell Mobility & Solo Mobile (Alternate)',
6923             'csc',
6924             'name'
6925         ),
6926         '$number@txt.bellmobility.ca',
6927         TRUE
6928     ),
6929     (
6930         9,
6931         oils_i18n_gettext(
6932             9,
6933             'Canada',
6934             'csc',
6935             'region'
6936         ),
6937         oils_i18n_gettext(
6938             9,
6939             'Aliant',
6940             'csc',
6941             'name'
6942         ),
6943         '$number@sms.wirefree.informe.ca',
6944         TRUE
6945     ),
6946     (
6947         10,
6948         oils_i18n_gettext(
6949             10,
6950             'Canada',
6951             'csc',
6952             'region'
6953         ),
6954         oils_i18n_gettext(
6955             10,
6956             'PC Telecom',
6957             'csc',
6958             'name'
6959         ),
6960         '$number@mobiletxt.ca',
6961         TRUE
6962     ),
6963     (
6964         11,
6965         oils_i18n_gettext(
6966             11,
6967             'Canada',
6968             'csc',
6969             'region'
6970         ),
6971         oils_i18n_gettext(
6972             11,
6973             'SaskTel',
6974             'csc',
6975             'name'
6976         ),
6977         '$number@sms.sasktel.com',
6978         TRUE
6979     ),
6980     (
6981         12,
6982         oils_i18n_gettext(
6983             12,
6984             'Canada',
6985             'csc',
6986             'region'
6987         ),
6988         oils_i18n_gettext(
6989             12,
6990             'MTS Mobility',
6991             'csc',
6992             'name'
6993         ),
6994         '$number@text.mtsmobility.com',
6995         TRUE
6996     ),
6997     (
6998         13,
6999         oils_i18n_gettext(
7000             13,
7001             'Canada',
7002             'csc',
7003             'region'
7004         ),
7005         oils_i18n_gettext(
7006             13,
7007             'Virgin Mobile',
7008             'csc',
7009             'name'
7010         ),
7011         '$number@vmobile.ca',
7012         TRUE
7013     ),
7014
7015     -- International
7016     (
7017         14,
7018         oils_i18n_gettext(
7019             14,
7020             'International',
7021             'csc',
7022             'region'
7023         ),
7024         oils_i18n_gettext(
7025             14,
7026             'Iridium',
7027             'csc',
7028             'name'
7029         ),
7030         '$number@msg.iridium.com',
7031         TRUE
7032     ),
7033     (
7034         15,
7035         oils_i18n_gettext(
7036             15,
7037             'International',
7038             'csc',
7039             'region'
7040         ),
7041         oils_i18n_gettext(
7042             15,
7043             'Globalstar',
7044             'csc',
7045             'name'
7046         ),
7047         '$number@msg.globalstarusa.com',
7048         TRUE
7049     ),
7050     (
7051         16,
7052         oils_i18n_gettext(
7053             16,
7054             'International',
7055             'csc',
7056             'region'
7057         ),
7058         oils_i18n_gettext(
7059             16,
7060             'Bulletin.net',
7061             'csc',
7062             'name'
7063         ),
7064         '$number@bulletinmessenger.net', -- International Formatted number
7065         TRUE
7066     ),
7067     (
7068         17,
7069         oils_i18n_gettext(
7070             17,
7071             'International',
7072             'csc',
7073             'region'
7074         ),
7075         oils_i18n_gettext(
7076             17,
7077             'Panacea Mobile',
7078             'csc',
7079             'name'
7080         ),
7081         '$number@api.panaceamobile.com',
7082         TRUE
7083     ),
7084
7085     -- USA
7086     (
7087         18,
7088         oils_i18n_gettext(
7089             18,
7090             'USA',
7091             'csc',
7092             'region'
7093         ),
7094         oils_i18n_gettext(
7095             18,
7096             'C Beyond',
7097             'csc',
7098             'name'
7099         ),
7100         '$number@cbeyond.sprintpcs.com',
7101         TRUE
7102     ),
7103     (
7104         19,
7105         oils_i18n_gettext(
7106             19,
7107             'Alaska, USA',
7108             'csc',
7109             'region'
7110         ),
7111         oils_i18n_gettext(
7112             19,
7113             'General Communications, Inc.',
7114             'csc',
7115             'name'
7116         ),
7117         '$number@mobile.gci.net',
7118         TRUE
7119     ),
7120     (
7121         20,
7122         oils_i18n_gettext(
7123             20,
7124             'California, USA',
7125             'csc',
7126             'region'
7127         ),
7128         oils_i18n_gettext(
7129             20,
7130             'Golden State Cellular',
7131             'csc',
7132             'name'
7133         ),
7134         '$number@gscsms.com',
7135         TRUE
7136     ),
7137     (
7138         21,
7139         oils_i18n_gettext(
7140             21,
7141             'Cincinnati, Ohio, USA',
7142             'csc',
7143             'region'
7144         ),
7145         oils_i18n_gettext(
7146             21,
7147             'Cincinnati Bell',
7148             'csc',
7149             'name'
7150         ),
7151         '$number@gocbw.com',
7152         TRUE
7153     ),
7154     (
7155         22,
7156         oils_i18n_gettext(
7157             22,
7158             'Hawaii, USA',
7159             'csc',
7160             'region'
7161         ),
7162         oils_i18n_gettext(
7163             22,
7164             'Hawaiian Telcom Wireless',
7165             'csc',
7166             'name'
7167         ),
7168         '$number@hawaii.sprintpcs.com',
7169         TRUE
7170     ),
7171     (
7172         23,
7173         oils_i18n_gettext(
7174             23,
7175             'Midwest, USA',
7176             'csc',
7177             'region'
7178         ),
7179         oils_i18n_gettext(
7180             23,
7181             'i wireless (T-Mobile)',
7182             'csc',
7183             'name'
7184         ),
7185         '$number.iws@iwspcs.net',
7186         TRUE
7187     ),
7188     (
7189         24,
7190         oils_i18n_gettext(
7191             24,
7192             'USA',
7193             'csc',
7194             'region'
7195         ),
7196         oils_i18n_gettext(
7197             24,
7198             'i-wireless (Sprint PCS)',
7199             'csc',
7200             'name'
7201         ),
7202         '$number@iwirelesshometext.com',
7203         TRUE
7204     ),
7205     (
7206         25,
7207         oils_i18n_gettext(
7208             25,
7209             'USA',
7210             'csc',
7211             'region'
7212         ),
7213         oils_i18n_gettext(
7214             25,
7215             'MetroPCS',
7216             'csc',
7217             'name'
7218         ),
7219         '$number@mymetropcs.com',
7220         TRUE
7221     ),
7222     (
7223         26,
7224         oils_i18n_gettext(
7225             26,
7226             'USA',
7227             'csc',
7228             'region'
7229         ),
7230         oils_i18n_gettext(
7231             26,
7232             'Kajeet',
7233             'csc',
7234             'name'
7235         ),
7236         '$number@mobile.kajeet.net',
7237         TRUE
7238     ),
7239     (
7240         27,
7241         oils_i18n_gettext(
7242             27,
7243             'USA',
7244             'csc',
7245             'region'
7246         ),
7247         oils_i18n_gettext(
7248             27,
7249             'Element Mobile',
7250             'csc',
7251             'name'
7252         ),
7253         '$number@SMS.elementmobile.net',
7254         TRUE
7255     ),
7256     (
7257         28,
7258         oils_i18n_gettext(
7259             28,
7260             'USA',
7261             'csc',
7262             'region'
7263         ),
7264         oils_i18n_gettext(
7265             28,
7266             'Esendex',
7267             'csc',
7268             'name'
7269         ),
7270         '$number@echoemail.net',
7271         TRUE
7272     ),
7273     (
7274         29,
7275         oils_i18n_gettext(
7276             29,
7277             'USA',
7278             'csc',
7279             'region'
7280         ),
7281         oils_i18n_gettext(
7282             29,
7283             'Boost Mobile',
7284             'csc',
7285             'name'
7286         ),
7287         '$number@myboostmobile.com',
7288         TRUE
7289     ),
7290     (
7291         30,
7292         oils_i18n_gettext(
7293             30,
7294             'USA',
7295             'csc',
7296             'region'
7297         ),
7298         oils_i18n_gettext(
7299             30,
7300             'BellSouth',
7301             'csc',
7302             'name'
7303         ),
7304         '$number@bellsouth.com',
7305         TRUE
7306     ),
7307     (
7308         31,
7309         oils_i18n_gettext(
7310             31,
7311             'USA',
7312             'csc',
7313             'region'
7314         ),
7315         oils_i18n_gettext(
7316             31,
7317             'Bluegrass Cellular',
7318             'csc',
7319             'name'
7320         ),
7321         '$number@sms.bluecell.com',
7322         TRUE
7323     ),
7324     (
7325         32,
7326         oils_i18n_gettext(
7327             32,
7328             'USA',
7329             'csc',
7330             'region'
7331         ),
7332         oils_i18n_gettext(
7333             32,
7334             'AT&T Enterprise Paging',
7335             'csc',
7336             'name'
7337         ),
7338         '$number@page.att.net',
7339         TRUE
7340     ),
7341     (
7342         33,
7343         oils_i18n_gettext(
7344             33,
7345             'USA',
7346             'csc',
7347             'region'
7348         ),
7349         oils_i18n_gettext(
7350             33,
7351             'AT&T Mobility/Wireless',
7352             'csc',
7353             'name'
7354         ),
7355         '$number@txt.att.net',
7356         TRUE
7357     ),
7358     (
7359         34,
7360         oils_i18n_gettext(
7361             34,
7362             'USA',
7363             'csc',
7364             'region'
7365         ),
7366         oils_i18n_gettext(
7367             34,
7368             'AT&T Global Smart Messaging Suite',
7369             'csc',
7370             'name'
7371         ),
7372         '$number@sms.smartmessagingsuite.com',
7373         TRUE
7374     ),
7375     (
7376         35,
7377         oils_i18n_gettext(
7378             35,
7379             'USA',
7380             'csc',
7381             'region'
7382         ),
7383         oils_i18n_gettext(
7384             35,
7385             'Alltel (Allied Wireless)',
7386             'csc',
7387             'name'
7388         ),
7389         '$number@sms.alltelwireless.com',
7390         TRUE
7391     ),
7392     (
7393         36,
7394         oils_i18n_gettext(
7395             36,
7396             'USA',
7397             'csc',
7398             'region'
7399         ),
7400         oils_i18n_gettext(
7401             36,
7402             'Alaska Communications',
7403             'csc',
7404             'name'
7405         ),
7406         '$number@msg.acsalaska.com',
7407         TRUE
7408     ),
7409     (
7410         37,
7411         oils_i18n_gettext(
7412             37,
7413             'USA',
7414             'csc',
7415             'region'
7416         ),
7417         oils_i18n_gettext(
7418             37,
7419             'Ameritech',
7420             'csc',
7421             'name'
7422         ),
7423         '$number@paging.acswireless.com',
7424         TRUE
7425     ),
7426     (
7427         38,
7428         oils_i18n_gettext(
7429             38,
7430             'USA',
7431             'csc',
7432             'region'
7433         ),
7434         oils_i18n_gettext(
7435             38,
7436             'Cingular (GoPhone prepaid)',
7437             'csc',
7438             'name'
7439         ),
7440         '$number@cingulartext.com',
7441         TRUE
7442     ),
7443     (
7444         39,
7445         oils_i18n_gettext(
7446             39,
7447             'USA',
7448             'csc',
7449             'region'
7450         ),
7451         oils_i18n_gettext(
7452             39,
7453             'Cingular (Postpaid)',
7454             'csc',
7455             'name'
7456         ),
7457         '$number@cingular.com',
7458         TRUE
7459     ),
7460     (
7461         40,
7462         oils_i18n_gettext(
7463             40,
7464             'USA',
7465             'csc',
7466             'region'
7467         ),
7468         oils_i18n_gettext(
7469             40,
7470             'Cellular One (Dobson) / O2 / Orange',
7471             'csc',
7472             'name'
7473         ),
7474         '$number@mobile.celloneusa.com',
7475         TRUE
7476     ),
7477     (
7478         41,
7479         oils_i18n_gettext(
7480             41,
7481             'USA',
7482             'csc',
7483             'region'
7484         ),
7485         oils_i18n_gettext(
7486             41,
7487             'Cellular South',
7488             'csc',
7489             'name'
7490         ),
7491         '$number@csouth1.com',
7492         TRUE
7493     ),
7494     (
7495         42,
7496         oils_i18n_gettext(
7497             42,
7498             'USA',
7499             'csc',
7500             'region'
7501         ),
7502         oils_i18n_gettext(
7503             42,
7504             'Cellcom',
7505             'csc',
7506             'name'
7507         ),
7508         '$number@cellcom.quiktxt.com',
7509         TRUE
7510     ),
7511     (
7512         43,
7513         oils_i18n_gettext(
7514             43,
7515             'USA',
7516             'csc',
7517             'region'
7518         ),
7519         oils_i18n_gettext(
7520             43,
7521             'Chariton Valley Wireless',
7522             'csc',
7523             'name'
7524         ),
7525         '$number@sms.cvalley.net',
7526         TRUE
7527     ),
7528     (
7529         44,
7530         oils_i18n_gettext(
7531             44,
7532             'USA',
7533             'csc',
7534             'region'
7535         ),
7536         oils_i18n_gettext(
7537             44,
7538             'Cricket',
7539             'csc',
7540             'name'
7541         ),
7542         '$number@sms.mycricket.com',
7543         TRUE
7544     ),
7545     (
7546         45,
7547         oils_i18n_gettext(
7548             45,
7549             'USA',
7550             'csc',
7551             'region'
7552         ),
7553         oils_i18n_gettext(
7554             45,
7555             'Cleartalk Wireless',
7556             'csc',
7557             'name'
7558         ),
7559         '$number@sms.cleartalk.us',
7560         TRUE
7561     ),
7562     (
7563         46,
7564         oils_i18n_gettext(
7565             46,
7566             'USA',
7567             'csc',
7568             'region'
7569         ),
7570         oils_i18n_gettext(
7571             46,
7572             'Edge Wireless',
7573             'csc',
7574             'name'
7575         ),
7576         '$number@sms.edgewireless.com',
7577         TRUE
7578     ),
7579     (
7580         47,
7581         oils_i18n_gettext(
7582             47,
7583             'USA',
7584             'csc',
7585             'region'
7586         ),
7587         oils_i18n_gettext(
7588             47,
7589             'Syringa Wireless',
7590             'csc',
7591             'name'
7592         ),
7593         '$number@rinasms.com',
7594         TRUE
7595     ),
7596     (
7597         48,
7598         oils_i18n_gettext(
7599             48,
7600             'USA',
7601             'csc',
7602             'region'
7603         ),
7604         oils_i18n_gettext(
7605             48,
7606             'T-Mobile',
7607             'csc',
7608             'name'
7609         ),
7610         '$number@tmomail.net',
7611         TRUE
7612     ),
7613     (
7614         49,
7615         oils_i18n_gettext(
7616             49,
7617             'USA',
7618             'csc',
7619             'region'
7620         ),
7621         oils_i18n_gettext(
7622             49,
7623             'Straight Talk / PagePlus Cellular',
7624             'csc',
7625             'name'
7626         ),
7627         '$number@vtext.com',
7628         TRUE
7629     ),
7630     (
7631         50,
7632         oils_i18n_gettext(
7633             50,
7634             'USA',
7635             'csc',
7636             'region'
7637         ),
7638         oils_i18n_gettext(
7639             50,
7640             'South Central Communications',
7641             'csc',
7642             'name'
7643         ),
7644         '$number@rinasms.com',
7645         TRUE
7646     ),
7647     (
7648         51,
7649         oils_i18n_gettext(
7650             51,
7651             'USA',
7652             'csc',
7653             'region'
7654         ),
7655         oils_i18n_gettext(
7656             51,
7657             'Simple Mobile',
7658             'csc',
7659             'name'
7660         ),
7661         '$number@smtext.com',
7662         TRUE
7663     ),
7664     (
7665         52,
7666         oils_i18n_gettext(
7667             52,
7668             'USA',
7669             'csc',
7670             'region'
7671         ),
7672         oils_i18n_gettext(
7673             52,
7674             'Sprint (PCS)',
7675             'csc',
7676             'name'
7677         ),
7678         '$number@messaging.sprintpcs.com',
7679         TRUE
7680     ),
7681     (
7682         53,
7683         oils_i18n_gettext(
7684             53,
7685             'USA',
7686             'csc',
7687             'region'
7688         ),
7689         oils_i18n_gettext(
7690             53,
7691             'Nextel',
7692             'csc',
7693             'name'
7694         ),
7695         '$number@messaging.nextel.com',
7696         TRUE
7697     ),
7698     (
7699         54,
7700         oils_i18n_gettext(
7701             54,
7702             'USA',
7703             'csc',
7704             'region'
7705         ),
7706         oils_i18n_gettext(
7707             54,
7708             'Pioneer Cellular',
7709             'csc',
7710             'name'
7711         ),
7712         '$number@zsend.com', -- nine digit number
7713         TRUE
7714     ),
7715     (
7716         55,
7717         oils_i18n_gettext(
7718             55,
7719             'USA',
7720             'csc',
7721             'region'
7722         ),
7723         oils_i18n_gettext(
7724             55,
7725             'Qwest Wireless',
7726             'csc',
7727             'name'
7728         ),
7729         '$number@qwestmp.com',
7730         TRUE
7731     ),
7732     (
7733         56,
7734         oils_i18n_gettext(
7735             56,
7736             'USA',
7737             'csc',
7738             'region'
7739         ),
7740         oils_i18n_gettext(
7741             56,
7742             'US Cellular',
7743             'csc',
7744             'name'
7745         ),
7746         '$number@email.uscc.net',
7747         TRUE
7748     ),
7749     (
7750         57,
7751         oils_i18n_gettext(
7752             57,
7753             'USA',
7754             'csc',
7755             'region'
7756         ),
7757         oils_i18n_gettext(
7758             57,
7759             'Unicel',
7760             'csc',
7761             'name'
7762         ),
7763         '$number@utext.com',
7764         TRUE
7765     ),
7766     (
7767         58,
7768         oils_i18n_gettext(
7769             58,
7770             'USA',
7771             'csc',
7772             'region'
7773         ),
7774         oils_i18n_gettext(
7775             58,
7776             'Teleflip',
7777             'csc',
7778             'name'
7779         ),
7780         '$number@teleflip.com',
7781         TRUE
7782     ),
7783     (
7784         59,
7785         oils_i18n_gettext(
7786             59,
7787             'USA',
7788             'csc',
7789             'region'
7790         ),
7791         oils_i18n_gettext(
7792             59,
7793             'Virgin Mobile',
7794             'csc',
7795             'name'
7796         ),
7797         '$number@vmobl.com',
7798         TRUE
7799     ),
7800     (
7801         60,
7802         oils_i18n_gettext(
7803             60,
7804             'USA',
7805             'csc',
7806             'region'
7807         ),
7808         oils_i18n_gettext(
7809             60,
7810             'Verizon Wireless',
7811             'csc',
7812             'name'
7813         ),
7814         '$number@vtext.com',
7815         TRUE
7816     ),
7817     (
7818         61,
7819         oils_i18n_gettext(
7820             61,
7821             'USA',
7822             'csc',
7823             'region'
7824         ),
7825         oils_i18n_gettext(
7826             61,
7827             'USA Mobility',
7828             'csc',
7829             'name'
7830         ),
7831         '$number@usamobility.net',
7832         TRUE
7833     ),
7834     (
7835         62,
7836         oils_i18n_gettext(
7837             62,
7838             'USA',
7839             'csc',
7840             'region'
7841         ),
7842         oils_i18n_gettext(
7843             62,
7844             'Viaero',
7845             'csc',
7846             'name'
7847         ),
7848         '$number@viaerosms.com',
7849         TRUE
7850     ),
7851     (
7852         63,
7853         oils_i18n_gettext(
7854             63,
7855             'USA',
7856             'csc',
7857             'region'
7858         ),
7859         oils_i18n_gettext(
7860             63,
7861             'TracFone',
7862             'csc',
7863             'name'
7864         ),
7865         '$number@mmst5.tracfone.com',
7866         TRUE
7867     ),
7868     (
7869         64,
7870         oils_i18n_gettext(
7871             64,
7872             'USA',
7873             'csc',
7874             'region'
7875         ),
7876         oils_i18n_gettext(
7877             64,
7878             'Centennial Wireless',
7879             'csc',
7880             'name'
7881         ),
7882         '$number@cwemail.com',
7883         TRUE
7884     ),
7885
7886     -- South Korea and USA
7887     (
7888         65,
7889         oils_i18n_gettext(
7890             65,
7891             'South Korea and USA',
7892             'csc',
7893             'region'
7894         ),
7895         oils_i18n_gettext(
7896             65,
7897             'Helio',
7898             'csc',
7899             'name'
7900         ),
7901         '$number@myhelio.com',
7902         TRUE
7903     )
7904 ;
7905
7906 INSERT INTO permission.perm_list ( id, code, description ) VALUES
7907     (
7908         519,
7909         'ADMIN_SMS_CARRIER',
7910         oils_i18n_gettext(
7911             519,
7912             'Allows a user to add/create/delete SMS Carrier entries.',
7913             'ppl',
7914             'description'
7915         )
7916     )
7917 ;
7918
7919 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
7920     SELECT
7921         pgt.id, perm.id, aout.depth, TRUE
7922     FROM
7923         permission.grp_tree pgt,
7924         permission.perm_list perm,
7925         actor.org_unit_type aout
7926     WHERE
7927         pgt.name = 'Global Administrator' AND
7928         aout.name = 'Consortium' AND
7929         perm.code = 'ADMIN_SMS_CARRIER';
7930
7931 INSERT INTO action_trigger.reactor (
7932     module,
7933     description
7934 ) VALUES (
7935     'SendSMS',
7936     'Send an SMS text message based on a user-defined template'
7937 );
7938
7939 INSERT INTO action_trigger.event_definition (
7940     active,
7941     owner,
7942     name,
7943     hook,
7944     validator,
7945     reactor,
7946     cleanup_success,
7947     delay,
7948     delay_field,
7949     group_field,
7950     template
7951 ) VALUES (
7952     true,
7953     1, -- admin
7954     'Hold Ready for Pickup SMS Notification',
7955     'hold.available',
7956     'HoldIsAvailable',
7957     'SendSMS',
7958     'CreateHoldNotification',
7959     '00:30:00',
7960     'shelf_time',
7961     'sms_notify',
7962     '[%- USE date -%]
7963 [%- user = target.0.usr -%]
7964 From: [%- params.sender_email || default_sender %]
7965 To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
7966 Subject: [% target.size %] hold(s) ready
7967
7968 [% FOR hold IN target %][%-
7969   bibxml = helpers.xml_doc( hold.current_copy.call_number.record.marc );
7970   title = "";
7971   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a"]'');
7972     title = title _ part.textContent;
7973   END;
7974   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
7975 %][% hold.usr.first_given_name %]:[% title %] @ [% hold.pickup_lib.name %]
7976 [% END %]
7977 '
7978 );
7979
7980 INSERT INTO action_trigger.environment (
7981     event_def,
7982     path
7983 ) VALUES (
7984     currval('action_trigger.event_definition_id_seq'),
7985     'current_copy.call_number.record.simple_record'
7986 ), (
7987     currval('action_trigger.event_definition_id_seq'),
7988     'usr'
7989 ), (
7990     currval('action_trigger.event_definition_id_seq'),
7991     'pickup_lib.billing_address'
7992 );
7993
7994 INSERT INTO action_trigger.hook(
7995     key,
7996     core_type,
7997     description,
7998     passive
7999 ) VALUES (
8000     'acn.format.sms_text',
8001     'acn',
8002     oils_i18n_gettext(
8003         'acn.format.sms_text',
8004         'A text message has been requested for a call number.',
8005         'ath',
8006         'description'
8007     ),
8008     FALSE
8009 );
8010
8011 INSERT INTO action_trigger.event_definition (
8012     active,
8013     owner,
8014     name,
8015     hook,
8016     validator,
8017     reactor,
8018     template
8019 ) VALUES (
8020     true,
8021     1, -- admin
8022     'SMS Call Number',
8023     'acn.format.sms_text',
8024     'NOOP_True',
8025     'SendSMS',
8026     '[%- USE date -%]
8027 From: [%- params.sender_email || default_sender %]
8028 To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
8029 Subject: Call Number
8030
8031 [%-
8032   bibxml = helpers.xml_doc( target.record.marc );
8033   title = "";
8034   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a" or @code="b"]'');
8035     title = title _ part.textContent;
8036   END;
8037   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
8038 %]
8039 Call Number: [% target.label %]
8040 Location: [% helpers.get_most_populous_location( target.id ).name %]
8041 Library: [% target.owning_lib.name %]
8042 [%- IF title %]
8043 Title: [% title %]
8044 [%- END %]
8045 [%- IF author %]
8046 Author: [% author %]
8047 [%- END %]
8048 '
8049 );
8050
8051 INSERT INTO action_trigger.environment (
8052     event_def,
8053     path
8054 ) VALUES (
8055     currval('action_trigger.event_definition_id_seq'),
8056     'record.simple_record'
8057 ), (
8058     currval('action_trigger.event_definition_id_seq'),
8059     'owning_lib.billing_address'
8060 );
8061
8062
8063 -- 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';
8064
8065
8066 SELECT evergreen.upgrade_deps_block_check('0667', :eg_version);
8067
8068 ALTER TABLE config.standing_penalty ADD staff_alert BOOL NOT NULL DEFAULT FALSE;
8069
8070 -- 20 is ALERT_NOTE
8071 -- for backwards compat, set all blocking penalties to alerts
8072 UPDATE config.standing_penalty SET staff_alert = TRUE 
8073     WHERE id = 20 OR block_list IS NOT NULL;
8074
8075 -- Evergreen DB patch 0668.schema.fix_indb_hold_permit.sql
8076 --
8077 -- FIXME: insert description of change, if needed
8078 --
8079
8080
8081 -- check whether patch can be applied
8082 SELECT evergreen.upgrade_deps_block_check('0668', :eg_version);
8083
8084 -- FIXME: add/check SQL statements to perform the upgrade
8085 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$
8086 DECLARE
8087     matchpoint_id        INT;
8088     user_object        actor.usr%ROWTYPE;
8089     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
8090     standing_penalty    config.standing_penalty%ROWTYPE;
8091     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
8092     transit_source        actor.org_unit%ROWTYPE;
8093     item_object        asset.copy%ROWTYPE;
8094     item_cn_object     asset.call_number%ROWTYPE;
8095     item_status_object  config.copy_status%ROWTYPE;
8096     item_location_object    asset.copy_location%ROWTYPE;
8097     ou_skip              actor.org_unit_setting%ROWTYPE;
8098     result            action.matrix_test_result;
8099     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
8100     use_active_date   TEXT;
8101     age_protect_date  TIMESTAMP WITH TIME ZONE;
8102     hold_count        INT;
8103     hold_transit_prox    INT;
8104     frozen_hold_count    INT;
8105     context_org_list    INT[];
8106     done            BOOL := FALSE;
8107 BEGIN
8108     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8109     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
8110
8111     result.success := TRUE;
8112
8113     -- Fail if we couldn't find a user
8114     IF user_object.id IS NULL THEN
8115         result.fail_part := 'no_user';
8116         result.success := FALSE;
8117         done := TRUE;
8118         RETURN NEXT result;
8119         RETURN;
8120     END IF;
8121
8122     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8123
8124     -- Fail if we couldn't find a copy
8125     IF item_object.id IS NULL THEN
8126         result.fail_part := 'no_item';
8127         result.success := FALSE;
8128         done := TRUE;
8129         RETURN NEXT result;
8130         RETURN;
8131     END IF;
8132
8133     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
8134     result.matchpoint := matchpoint_id;
8135
8136     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
8137
8138     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
8139     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
8140         result.fail_part := 'circ.holds.target_skip_me';
8141         result.success := FALSE;
8142         done := TRUE;
8143         RETURN NEXT result;
8144         RETURN;
8145     END IF;
8146
8147     -- Fail if user is barred
8148     IF user_object.barred IS TRUE THEN
8149         result.fail_part := 'actor.usr.barred';
8150         result.success := FALSE;
8151         done := TRUE;
8152         RETURN NEXT result;
8153         RETURN;
8154     END IF;
8155
8156     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8157     SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status;
8158     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8159
8160     -- Fail if we couldn't find any matchpoint (requires a default)
8161     IF matchpoint_id IS NULL THEN
8162         result.fail_part := 'no_matchpoint';
8163         result.success := FALSE;
8164         done := TRUE;
8165         RETURN NEXT result;
8166         RETURN;
8167     END IF;
8168
8169     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
8170
8171     IF hold_test.holdable IS FALSE THEN
8172         result.fail_part := 'config.hold_matrix_test.holdable';
8173         result.success := FALSE;
8174         done := TRUE;
8175         RETURN NEXT result;
8176     END IF;
8177
8178     IF item_object.holdable IS FALSE THEN
8179         result.fail_part := 'item.holdable';
8180         result.success := FALSE;
8181         done := TRUE;
8182         RETURN NEXT result;
8183     END IF;
8184
8185     IF item_status_object.holdable IS FALSE THEN
8186         result.fail_part := 'status.holdable';
8187         result.success := FALSE;
8188         done := TRUE;
8189         RETURN NEXT result;
8190     END IF;
8191
8192     IF item_location_object.holdable IS FALSE THEN
8193         result.fail_part := 'location.holdable';
8194         result.success := FALSE;
8195         done := TRUE;
8196         RETURN NEXT result;
8197     END IF;
8198
8199     IF hold_test.transit_range IS NOT NULL THEN
8200         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
8201         IF hold_test.distance_is_from_owner THEN
8202             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;
8203         ELSE
8204             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
8205         END IF;
8206
8207         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
8208
8209         IF NOT FOUND THEN
8210             result.fail_part := 'transit_range';
8211             result.success := FALSE;
8212             done := TRUE;
8213             RETURN NEXT result;
8214         END IF;
8215     END IF;
8216  
8217     FOR standing_penalty IN
8218         SELECT  DISTINCT csp.*
8219           FROM  actor.usr_standing_penalty usp
8220                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8221           WHERE usr = match_user
8222                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8223                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8224                 AND csp.block_list LIKE '%HOLD%' LOOP
8225
8226         result.fail_part := standing_penalty.name;
8227         result.success := FALSE;
8228         done := TRUE;
8229         RETURN NEXT result;
8230     END LOOP;
8231
8232     IF hold_test.stop_blocked_user IS TRUE THEN
8233         FOR standing_penalty IN
8234             SELECT  DISTINCT csp.*
8235               FROM  actor.usr_standing_penalty usp
8236                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8237               WHERE usr = match_user
8238                     AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8239                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8240                     AND csp.block_list LIKE '%CIRC%' LOOP
8241     
8242             result.fail_part := standing_penalty.name;
8243             result.success := FALSE;
8244             done := TRUE;
8245             RETURN NEXT result;
8246         END LOOP;
8247     END IF;
8248
8249     IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
8250         SELECT    INTO hold_count COUNT(*)
8251           FROM    action.hold_request
8252           WHERE    usr = match_user
8253             AND fulfillment_time IS NULL
8254             AND cancel_time IS NULL
8255             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
8256
8257         IF hold_count >= hold_test.max_holds THEN
8258             result.fail_part := 'config.hold_matrix_test.max_holds';
8259             result.success := FALSE;
8260             done := TRUE;
8261             RETURN NEXT result;
8262         END IF;
8263     END IF;
8264
8265     IF item_object.age_protect IS NOT NULL THEN
8266         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
8267         IF hold_test.distance_is_from_owner THEN
8268             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
8269         ELSE
8270             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
8271         END IF;
8272         IF use_active_date = 'true' THEN
8273             age_protect_date := COALESCE(item_object.active_date, NOW());
8274         ELSE
8275             age_protect_date := item_object.create_date;
8276         END IF;
8277         IF age_protect_date + age_protect_object.age > NOW() THEN
8278             IF hold_test.distance_is_from_owner THEN
8279                 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8280                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
8281             ELSE
8282                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
8283             END IF;
8284
8285             IF hold_transit_prox > age_protect_object.prox THEN
8286                 result.fail_part := 'config.rule_age_hold_protect.prox';
8287                 result.success := FALSE;
8288                 done := TRUE;
8289                 RETURN NEXT result;
8290             END IF;
8291         END IF;
8292     END IF;
8293
8294     IF NOT done THEN
8295         RETURN NEXT result;
8296     END IF;
8297
8298     RETURN;
8299 END;
8300 $func$ LANGUAGE plpgsql;
8301
8302
8303 -- Evergreen DB patch 0669.data.recall_and_force_holds.sql
8304 --
8305 -- FIXME: insert description of change, if needed
8306 --
8307
8308
8309 -- check whether patch can be applied
8310 SELECT evergreen.upgrade_deps_block_check('0669', :eg_version);
8311
8312 -- FIXME: add/check SQL statements to perform the upgrade
8313 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8314  ( 517, 'COPY_HOLDS_FORCE', oils_i18n_gettext( 517, 
8315     'Allow a user to place a force hold on a specific copy', 'ppl', 'description' )),
8316  ( 518, 'COPY_HOLDS_RECALL', oils_i18n_gettext( 518, 
8317     'Allow a user to place a cataloging recall on a specific copy', 'ppl', 'description' ));
8318
8319
8320 -- Evergreen DB patch 0670.data.mark-email-and-phone-invalid.sql
8321 --
8322 -- Add org unit settings and standing penalty types to support
8323 -- the mark email/phone invalid features.
8324 --
8325
8326 -- check whether patch can be applied
8327 SELECT evergreen.upgrade_deps_block_check('0670', :eg_version);
8328
8329
8330 INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) VALUES
8331     (
8332         31,
8333         'INVALID_PATRON_EMAIL_ADDRESS',
8334         oils_i18n_gettext(
8335             31,
8336             'Patron had an invalid email address',
8337             'csp',
8338             'label'
8339         ),
8340         TRUE,
8341         0
8342     ),
8343     (
8344         32,
8345         'INVALID_PATRON_DAY_PHONE',
8346         oils_i18n_gettext(
8347             32,
8348             'Patron had an invalid daytime phone number',
8349             'csp',
8350             'label'
8351         ),
8352         TRUE,
8353         0
8354     ),
8355     (
8356         33,
8357         'INVALID_PATRON_EVENING_PHONE',
8358         oils_i18n_gettext(
8359             33,
8360             'Patron had an invalid evening phone number',
8361             'csp',
8362             'label'
8363         ),
8364         TRUE,
8365         0
8366     ),
8367     (
8368         34,
8369         'INVALID_PATRON_OTHER_PHONE',
8370         oils_i18n_gettext(
8371             34,
8372             'Patron had an invalid other phone number',
8373             'csp',
8374             'label'
8375         ),
8376         TRUE,
8377         0
8378     );
8379
8380
8381
8382 SELECT evergreen.upgrade_deps_block_check('0671', :eg_version);
8383
8384 ALTER TABLE asset.copy_location
8385     ADD COLUMN checkin_alert BOOL NOT NULL DEFAULT FALSE;
8386
8387 -- Evergreen DB patch 0673.data.acq-cancel-reason-cleanup.sql
8388 --
8389
8390 -- check whether patch can be applied
8391 SELECT evergreen.upgrade_deps_block_check('0673', :eg_version);
8392
8393 DELETE FROM
8394     acq.cancel_reason
8395 WHERE
8396     -- any entries with id >= 2000 were added locally.  
8397     id < 2000 
8398
8399     -- these cancel_reason's are actively used by the system
8400     AND id NOT IN (1, 2, 3, 1002, 1003, 1004, 1005, 1010, 1024, 1211, 1221, 1246, 1283)
8401
8402     -- don't delete any cancel_reason's that may be in use locally
8403     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.user_request WHERE cancel_reason IS NOT NULL)
8404     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.purchase_order WHERE cancel_reason IS NOT NULL)
8405     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem WHERE cancel_reason IS NOT NULL)
8406     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem_detail WHERE cancel_reason IS NOT NULL)
8407     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_lineitem_history WHERE cancel_reason IS NOT NULL)
8408     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_purchase_order_history WHERE cancel_reason IS NOT NULL);
8409
8410
8411 SELECT evergreen.upgrade_deps_block_check('0674', :eg_version);
8412
8413 ALTER TABLE config.copy_status
8414           ADD COLUMN restrict_copy_delete BOOL NOT NULL DEFAULT FALSE;
8415
8416 UPDATE config.copy_status
8417 SET restrict_copy_delete = TRUE
8418 WHERE id IN (1,3,6,8);
8419
8420 INSERT INTO permission.perm_list (id, code, description) VALUES (
8421     520,
8422     'COPY_DELETE_WARNING.override',
8423     'Allow a user to override warnings about deleting copies in problematic situations.'
8424 );
8425
8426
8427 SELECT evergreen.upgrade_deps_block_check('0675', :eg_version);
8428
8429 -- set expected row count to low value to avoid problem
8430 -- where use of this function by the circ tagging feature
8431 -- results in full scans of asset.call_number
8432 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
8433     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
8434 $$ LANGUAGE SQL ROWS 10;
8435
8436
8437 SELECT evergreen.upgrade_deps_block_check('0676', :eg_version);
8438
8439 INSERT INTO config.global_flag (name, label, enabled, value) VALUES (
8440     'opac.use_autosuggest',
8441     '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)',
8442     TRUE,
8443     'opac_visible'
8444 );
8445
8446 CREATE TABLE metabib.browse_entry (
8447     id BIGSERIAL PRIMARY KEY,
8448     value TEXT unique,
8449     index_vector tsvector
8450 );
8451 --Skip this, will be created differently later
8452 --CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIST (index_vector);
8453 CREATE TRIGGER metabib_browse_entry_fti_trigger
8454     BEFORE INSERT OR UPDATE ON metabib.browse_entry
8455     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
8456
8457
8458 CREATE TABLE metabib.browse_entry_def_map (
8459     id BIGSERIAL PRIMARY KEY,
8460     entry BIGINT REFERENCES metabib.browse_entry (id),
8461     def INT REFERENCES config.metabib_field (id),
8462     source BIGINT REFERENCES biblio.record_entry (id)
8463 );
8464
8465 ALTER TABLE config.metabib_field ADD COLUMN browse_field BOOLEAN DEFAULT TRUE NOT NULL;
8466 ALTER TABLE config.metabib_field ADD COLUMN browse_xpath TEXT;
8467
8468 ALTER TABLE config.metabib_class ADD COLUMN bouyant BOOLEAN DEFAULT FALSE NOT NULL;
8469 ALTER TABLE config.metabib_class ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
8470 ALTER TABLE config.metabib_field ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
8471
8472 -- one good exception to default true:
8473 UPDATE config.metabib_field
8474     SET browse_field = FALSE
8475     WHERE (field_class = 'keyword' AND name = 'keyword') OR
8476         (field_class = 'subject' AND name = 'complete');
8477
8478 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
8479 -- We're only touching it here to add a DELETE statement to the IF NEW.deleted
8480 -- block.
8481
8482 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
8483 DECLARE
8484     transformed_xml TEXT;
8485     prev_xfrm       TEXT;
8486     normalizer      RECORD;
8487     xfrm            config.xml_transform%ROWTYPE;
8488     attr_value      TEXT;
8489     new_attrs       HSTORE := ''::HSTORE;
8490     attr_def        config.record_attr_definition%ROWTYPE;
8491 BEGIN
8492
8493     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
8494         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
8495         DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
8496         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
8497         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
8498         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
8499         RETURN NEW; -- and we're done
8500     END IF;
8501
8502     IF TG_OP = 'UPDATE' THEN -- re-ingest?
8503         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
8504
8505         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
8506             RETURN NEW;
8507         END IF;
8508     END IF;
8509
8510     -- Record authority linking
8511     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
8512     IF NOT FOUND THEN
8513         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
8514     END IF;
8515
8516     -- Flatten and insert the mfr data
8517     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
8518     IF NOT FOUND THEN
8519         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
8520
8521         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
8522         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
8523         IF NOT FOUND THEN
8524             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
8525
8526                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
8527                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
8528                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
8529                       WHERE record = NEW.id
8530                             AND tag LIKE attr_def.tag
8531                             AND CASE
8532                                 WHEN attr_def.sf_list IS NOT NULL 
8533                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
8534                                 ELSE TRUE
8535                                 END
8536                       GROUP BY tag
8537                       ORDER BY tag
8538                       LIMIT 1;
8539
8540                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
8541                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
8542
8543                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
8544
8545                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
8546             
8547                     -- See if we can skip the XSLT ... it's expensive
8548                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
8549                         -- Can't skip the transform
8550                         IF xfrm.xslt <> '---' THEN
8551                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
8552                         ELSE
8553                             transformed_xml := NEW.marc;
8554                         END IF;
8555             
8556                         prev_xfrm := xfrm.name;
8557                     END IF;
8558
8559                     IF xfrm.name IS NULL THEN
8560                         -- just grab the marcxml (empty) transform
8561                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
8562                         prev_xfrm := xfrm.name;
8563                     END IF;
8564
8565                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
8566
8567                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
8568                     SELECT  m.value INTO attr_value
8569                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
8570                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
8571                       WHERE v.subfield = attr_def.phys_char_sf
8572                       LIMIT 1; -- Just in case ...
8573
8574                 END IF;
8575
8576                 -- apply index normalizers to attr_value
8577                 FOR normalizer IN
8578                     SELECT  n.func AS func,
8579                             n.param_count AS param_count,
8580                             m.params AS params
8581                       FROM  config.index_normalizer n
8582                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
8583                       WHERE attr = attr_def.name
8584                       ORDER BY m.pos LOOP
8585                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
8586                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
8587                             CASE
8588                                 WHEN normalizer.param_count > 0
8589                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
8590                                     ELSE ''
8591                                 END ||
8592                             ')' INTO attr_value;
8593         
8594                 END LOOP;
8595
8596                 -- Add the new value to the hstore
8597                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
8598
8599             END LOOP;
8600
8601             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
8602                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
8603             ELSE
8604                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
8605             END IF;
8606
8607         END IF;
8608     END IF;
8609
8610     -- Gather and insert the field entry data
8611     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
8612
8613     -- Located URI magic
8614     IF TG_OP = 'INSERT' THEN
8615         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
8616         IF NOT FOUND THEN
8617             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
8618         END IF;
8619     ELSE
8620         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
8621         IF NOT FOUND THEN
8622             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
8623         END IF;
8624     END IF;
8625
8626     -- (re)map metarecord-bib linking
8627     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
8628         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
8629         IF NOT FOUND THEN
8630             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
8631         END IF;
8632     ELSE -- we're doing an update, and we're not deleted, remap
8633         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
8634         IF NOT FOUND THEN
8635             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
8636         END IF;
8637     END IF;
8638
8639     RETURN NEW;
8640 END;
8641 $func$ LANGUAGE PLPGSQL;
8642
8643 CREATE OR REPLACE FUNCTION metabib.browse_normalize(facet_text TEXT, mapped_field INT) RETURNS TEXT AS $$
8644 DECLARE
8645     normalizer  RECORD;
8646 BEGIN
8647
8648     FOR normalizer IN
8649         SELECT  n.func AS func,
8650                 n.param_count AS param_count,
8651                 m.params AS params
8652           FROM  config.index_normalizer n
8653                 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
8654           WHERE m.field = mapped_field AND m.pos < 0
8655           ORDER BY m.pos LOOP
8656
8657             EXECUTE 'SELECT ' || normalizer.func || '(' ||
8658                 quote_literal( facet_text ) ||
8659                 CASE
8660                     WHEN normalizer.param_count > 0
8661                         THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
8662                         ELSE ''
8663                     END ||
8664                 ')' INTO facet_text;
8665
8666     END LOOP;
8667
8668     RETURN facet_text;
8669 END;
8670
8671 $$ LANGUAGE PLPGSQL;
8672
8673 DROP FUNCTION biblio.extract_metabib_field_entry(bigint, text);
8674 DROP FUNCTION biblio.extract_metabib_field_entry(bigint);
8675
8676 DROP TYPE metabib.field_entry_template;
8677 CREATE TYPE metabib.field_entry_template AS (
8678         field_class     TEXT,
8679         field           INT,
8680         facet_field     BOOL,
8681         search_field    BOOL,
8682         browse_field   BOOL,
8683         source          BIGINT,
8684         value           TEXT
8685 );
8686
8687
8688 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
8689 DECLARE
8690     bib     biblio.record_entry%ROWTYPE;
8691     idx     config.metabib_field%ROWTYPE;
8692     xfrm        config.xml_transform%ROWTYPE;
8693     prev_xfrm   TEXT;
8694     transformed_xml TEXT;
8695     xml_node    TEXT;
8696     xml_node_list   TEXT[];
8697     facet_text  TEXT;
8698     browse_text TEXT;
8699     raw_text    TEXT;
8700     curr_text   TEXT;
8701     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
8702     output_row  metabib.field_entry_template%ROWTYPE;
8703 BEGIN
8704
8705     -- Get the record
8706     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
8707
8708     -- Loop over the indexing entries
8709     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
8710
8711         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
8712
8713         -- See if we can skip the XSLT ... it's expensive
8714         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
8715             -- Can't skip the transform
8716             IF xfrm.xslt <> '---' THEN
8717                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
8718             ELSE
8719                 transformed_xml := bib.marc;
8720             END IF;
8721
8722             prev_xfrm := xfrm.name;
8723         END IF;
8724
8725         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8726
8727         raw_text := NULL;
8728         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
8729             CONTINUE WHEN xml_node !~ E'^\\s*<';
8730
8731             curr_text := ARRAY_TO_STRING(
8732                 oils_xpath( '//text()',
8733                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
8734                         REGEXP_REPLACE( -- This escapes embeded <s
8735                             xml_node,
8736                             $re$(>[^<]+)(<)([^>]+<)$re$,
8737                             E'\\1&lt;\\3',
8738                             'g'
8739                         ),
8740                         '&(?!amp;)',
8741                         '&amp;',
8742                         'g'
8743                     )
8744                 ),
8745                 ' '
8746             );
8747
8748             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
8749
8750             IF raw_text IS NOT NULL THEN
8751                 raw_text := raw_text || joiner;
8752             END IF;
8753
8754             raw_text := COALESCE(raw_text,'') || curr_text;
8755
8756             -- autosuggest/metabib.browse_entry
8757             IF idx.browse_field THEN
8758
8759                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
8760                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8761                 ELSE
8762                     browse_text := curr_text;
8763                 END IF;
8764
8765                 output_row.field_class = idx.field_class;
8766                 output_row.field = idx.id;
8767                 output_row.source = rid;
8768                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
8769
8770                 output_row.browse_field = TRUE;
8771                 RETURN NEXT output_row;
8772                 output_row.browse_field = FALSE;
8773             END IF;
8774
8775             -- insert raw node text for faceting
8776             IF idx.facet_field THEN
8777
8778                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
8779                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
8780                 ELSE
8781                     facet_text := curr_text;
8782                 END IF;
8783
8784                 output_row.field_class = idx.field_class;
8785                 output_row.field = -1 * idx.id;
8786                 output_row.source = rid;
8787                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
8788
8789                 output_row.facet_field = TRUE;
8790                 RETURN NEXT output_row;
8791                 output_row.facet_field = FALSE;
8792             END IF;
8793
8794         END LOOP;
8795
8796         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
8797
8798         -- insert combined node text for searching
8799         IF idx.search_field THEN
8800             output_row.field_class = idx.field_class;
8801             output_row.field = idx.id;
8802             output_row.source = rid;
8803             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
8804
8805             output_row.search_field = TRUE;
8806             RETURN NEXT output_row;
8807         END IF;
8808
8809     END LOOP;
8810
8811 END;
8812 $func$ LANGUAGE PLPGSQL;
8813
8814 -- default to a space joiner
8815 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
8816     SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
8817     $func$ LANGUAGE SQL;
8818
8819
8820 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
8821 DECLARE
8822     fclass          RECORD;
8823     ind_data        metabib.field_entry_template%ROWTYPE;
8824     mbe_row         metabib.browse_entry%ROWTYPE;
8825     mbe_id          BIGINT;
8826 BEGIN
8827     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
8828     IF NOT FOUND THEN
8829         FOR fclass IN SELECT * FROM config.metabib_class LOOP
8830             -- RAISE NOTICE 'Emptying out %', fclass.name;
8831             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
8832         END LOOP;
8833         DELETE FROM metabib.facet_entry WHERE source = bib_id;
8834         DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
8835     END IF;
8836
8837     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
8838         IF ind_data.field < 0 THEN
8839             ind_data.field = -1 * ind_data.field;
8840         END IF;
8841
8842         IF ind_data.facet_field THEN
8843             INSERT INTO metabib.facet_entry (field, source, value)
8844                 VALUES (ind_data.field, ind_data.source, ind_data.value);
8845         END IF;
8846
8847         IF ind_data.browse_field THEN
8848             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
8849             IF FOUND THEN
8850                 mbe_id := mbe_row.id;
8851             ELSE
8852                 INSERT INTO metabib.browse_entry (value) VALUES
8853                     (metabib.browse_normalize(ind_data.value, ind_data.field));
8854                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
8855             END IF;
8856
8857             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
8858                 VALUES (mbe_id, ind_data.field, ind_data.source);
8859         END IF;
8860
8861         IF ind_data.search_field THEN
8862             EXECUTE $$
8863                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
8864                     VALUES ($$ ||
8865                         quote_literal(ind_data.field) || $$, $$ ||
8866                         quote_literal(ind_data.source) || $$, $$ ||
8867                         quote_literal(ind_data.value) ||
8868                     $$);$$;
8869         END IF;
8870
8871     END LOOP;
8872
8873     RETURN;
8874 END;
8875 $func$ LANGUAGE PLPGSQL;
8876
8877 -- This mimics a specific part of QueryParser, turning the first part of a
8878 -- classed search (search_class) into a set of classes and possibly fields.
8879 -- search_class might look like "author" or "title|proper" or "ti|uniform"
8880 -- or "au" or "au|corporate|personal" or anything like that, where the first
8881 -- element of the list you get by separating on the "|" character is either
8882 -- a registered class (config.metabib_class) or an alias
8883 -- (config.metabib_search_alias), and the rest of any such elements are
8884 -- fields (config.metabib_field).
8885 CREATE OR REPLACE
8886     FUNCTION metabib.search_class_to_registered_components(search_class TEXT)
8887     RETURNS SETOF RECORD AS $func$
8888 DECLARE
8889     search_parts        TEXT[];
8890     field_name          TEXT;
8891     search_part_count   INTEGER;
8892     rec                 RECORD;
8893     registered_class    config.metabib_class%ROWTYPE;
8894     registered_alias    config.metabib_search_alias%ROWTYPE;
8895     registered_field    config.metabib_field%ROWTYPE;
8896 BEGIN
8897     search_parts := REGEXP_SPLIT_TO_ARRAY(search_class, E'\\|');
8898
8899     search_part_count := ARRAY_LENGTH(search_parts, 1);
8900     IF search_part_count = 0 THEN
8901         RETURN;
8902     ELSE
8903         SELECT INTO registered_class
8904             * FROM config.metabib_class WHERE name = search_parts[1];
8905         IF FOUND THEN
8906             IF search_part_count < 2 THEN   -- all fields
8907                 rec := (registered_class.name, NULL::INTEGER);
8908                 RETURN NEXT rec;
8909                 RETURN; -- done
8910             END IF;
8911             FOR field_name IN SELECT *
8912                 FROM UNNEST(search_parts[2:search_part_count]) LOOP
8913                 SELECT INTO registered_field
8914                     * FROM config.metabib_field
8915                     WHERE name = field_name AND
8916                         field_class = registered_class.name;
8917                 IF FOUND THEN
8918                     rec := (registered_class.name, registered_field.id);
8919                     RETURN NEXT rec;
8920                 END IF;
8921             END LOOP;
8922         ELSE
8923             -- maybe we have an alias?
8924             SELECT INTO registered_alias
8925                 * FROM config.metabib_search_alias WHERE alias=search_parts[1];
8926             IF NOT FOUND THEN
8927                 RETURN;
8928             ELSE
8929                 IF search_part_count < 2 THEN   -- return w/e the alias says
8930                     rec := (
8931                         registered_alias.field_class, registered_alias.field
8932                     );
8933                     RETURN NEXT rec;
8934                     RETURN; -- done
8935                 ELSE
8936                     FOR field_name IN SELECT *
8937                         FROM UNNEST(search_parts[2:search_part_count]) LOOP
8938                         SELECT INTO registered_field
8939                             * FROM config.metabib_field
8940                             WHERE name = field_name AND
8941                                 field_class = registered_alias.field_class;
8942                         IF FOUND THEN
8943                             rec := (
8944                                 registered_alias.field_class,
8945                                 registered_field.id
8946                             );
8947                             RETURN NEXT rec;
8948                         END IF;
8949                     END LOOP;
8950                 END IF;
8951             END IF;
8952         END IF;
8953     END IF;
8954 END;
8955 $func$ LANGUAGE PLPGSQL;
8956
8957
8958 CREATE OR REPLACE
8959     FUNCTION metabib.suggest_browse_entries(
8960         query_text      TEXT,   -- 'foo' or 'foo & ba:*',ready for to_tsquery()
8961         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
8962         headline_opts   TEXT,   -- markup options for ts_headline()
8963         visibility_org  INTEGER,-- null if you don't want opac visibility test
8964         query_limit     INTEGER,-- use in LIMIT clause of interal query
8965         normalization   INTEGER -- argument to TS_RANK_CD()
8966     ) RETURNS TABLE (
8967         value                   TEXT,   -- plain
8968         field                   INTEGER,
8969         bouyant_and_class_match BOOL,
8970         field_match             BOOL,
8971         field_weight            INTEGER,
8972         rank                    REAL,
8973         bouyant                 BOOL,
8974         match                   TEXT    -- marked up
8975     ) AS $func$
8976 DECLARE
8977     query                   TSQUERY;
8978     opac_visibility_join    TEXT;
8979     search_class_join       TEXT;
8980     r_fields                RECORD;
8981 BEGIN
8982     query := TO_TSQUERY('keyword', query_text);
8983
8984     IF visibility_org IS NOT NULL THEN
8985         opac_visibility_join := '
8986     JOIN asset.opac_visible_copies aovc ON (
8987         aovc.record = mbedm.source AND
8988         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
8989     )';
8990     ELSE
8991         opac_visibility_join := '';
8992     END IF;
8993
8994     -- The following determines whether we only provide suggestsons matching
8995     -- the user's selected search_class, or whether we show other suggestions
8996     -- too. The reason for MIN() is that for search_classes like
8997     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
8998     -- implication is that if title as a class doesn't have restrict,
8999     -- nor does the proper field, but the uniform field does, you're going
9000     -- to get 'false' for your overall evaluation of 'should we restrict?'
9001     -- To invert that, change from MIN() to MAX().
9002
9003     SELECT
9004         INTO r_fields
9005             MIN(cmc.restrict::INT) AS restrict_class,
9006             MIN(cmf.restrict::INT) AS restrict_field
9007         FROM metabib.search_class_to_registered_components(search_class)
9008             AS _registered (field_class TEXT, field INT)
9009         JOIN
9010             config.metabib_class cmc ON (cmc.name = _registered.field_class)
9011         LEFT JOIN
9012             config.metabib_field cmf ON (cmf.id = _registered.field);
9013
9014     -- evaluate 'should we restrict?'
9015     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
9016         search_class_join := '
9017     JOIN
9018         metabib.search_class_to_registered_components($2)
9019         AS _registered (field_class TEXT, field INT) ON (
9020             (_registered.field IS NULL AND
9021                 _registered.field_class = cmf.field_class) OR
9022             (_registered.field = cmf.id)
9023         )
9024     ';
9025     ELSE
9026         search_class_join := '
9027     LEFT JOIN
9028         metabib.search_class_to_registered_components($2)
9029         AS _registered (field_class TEXT, field INT) ON (
9030             _registered.field_class = cmc.name
9031         )
9032     ';
9033     END IF;
9034
9035     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $1, $3) FROM (SELECT DISTINCT
9036         mbe.value,
9037         cmf.id,
9038         cmc.bouyant AND _registered.field_class IS NOT NULL,
9039         _registered.field = cmf.id,
9040         cmf.weight,
9041         TS_RANK_CD(mbe.index_vector, $1, $6),
9042         cmc.bouyant
9043     FROM metabib.browse_entry_def_map mbedm
9044     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
9045     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
9046     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
9047     '  || search_class_join || opac_visibility_join ||
9048     ' WHERE $1 @@ mbe.index_vector
9049     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
9050     LIMIT $5) x
9051     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
9052     '   -- sic, repeat the order by clause in the outer select too
9053     USING
9054         query, search_class, headline_opts,
9055         visibility_org, query_limit, normalization
9056         ;
9057
9058     -- sort order:
9059     --  bouyant AND chosen class = match class
9060     --  chosen field = match field
9061     --  field weight
9062     --  rank
9063     --  bouyancy
9064     --  value itself
9065
9066 END;
9067 $func$ LANGUAGE PLPGSQL;
9068
9069 -- The advantage of this over the stock regexp_split_to_array() is that it
9070 -- won't degrade unicode strings.
9071 CREATE OR REPLACE FUNCTION evergreen.regexp_split_to_array(TEXT, TEXT)
9072 RETURNS TEXT[] AS $$
9073     return encode_array_literal([split $_[1], $_[0]]);
9074 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
9075
9076
9077 -- Adds some logic for browse_entry to split on non-word chars for index_vector, post-normalize
9078 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
9079 DECLARE
9080     normalizer      RECORD;
9081     value           TEXT := '';
9082 BEGIN
9083
9084     value := NEW.value;
9085
9086     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
9087         FOR normalizer IN
9088             SELECT  n.func AS func,
9089                     n.param_count AS param_count,
9090                     m.params AS params
9091               FROM  config.index_normalizer n
9092                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
9093               WHERE field = NEW.field AND m.pos < 0
9094               ORDER BY m.pos LOOP
9095                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
9096                     quote_literal( value ) ||
9097                     CASE
9098                         WHEN normalizer.param_count > 0
9099                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
9100                             ELSE ''
9101                         END ||
9102                     ')' INTO value;
9103
9104         END LOOP;
9105
9106         NEW.value := value;
9107     END IF;
9108
9109     IF NEW.index_vector = ''::tsvector THEN
9110         RETURN NEW;
9111     END IF;
9112
9113     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
9114         FOR normalizer IN
9115             SELECT  n.func AS func,
9116                     n.param_count AS param_count,
9117                     m.params AS params
9118               FROM  config.index_normalizer n
9119                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
9120               WHERE field = NEW.field AND m.pos >= 0
9121               ORDER BY m.pos LOOP
9122                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
9123                     quote_literal( value ) ||
9124                     CASE
9125                         WHEN normalizer.param_count > 0
9126                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
9127                             ELSE ''
9128                         END ||
9129                     ')' INTO value;
9130
9131         END LOOP;
9132     END IF;
9133
9134     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
9135         value :=  ARRAY_TO_STRING(
9136             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
9137         );
9138     END IF;
9139
9140     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
9141
9142     RETURN NEW;
9143 END;
9144 $$ LANGUAGE PLPGSQL;
9145
9146 -- Evergreen DB patch 0677.schema.circ_limits.sql
9147 --
9148 -- FIXME: insert description of change, if needed
9149 --
9150
9151
9152 -- check whether patch can be applied
9153 SELECT evergreen.upgrade_deps_block_check('0677', :eg_version);
9154
9155 -- FIXME: add/check SQL statements to perform the upgrade
9156 -- Limit groups for circ counting
9157 CREATE TABLE config.circ_limit_group (
9158     id          SERIAL  PRIMARY KEY,
9159     name        TEXT    UNIQUE NOT NULL,
9160     description TEXT
9161 );
9162
9163 -- Limit sets
9164 CREATE TABLE config.circ_limit_set (
9165     id          SERIAL  PRIMARY KEY,
9166     name        TEXT    UNIQUE NOT NULL,
9167     owning_lib  INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
9168     items_out   INT     NOT NULL, -- Total current active circulations must be less than this. 0 means skip counting (always pass)
9169     depth       INT     NOT NULL DEFAULT 0, -- Depth count starts at
9170     global      BOOL    NOT NULL DEFAULT FALSE, -- If enabled, include everything below depth, otherwise ancestors/descendants only
9171     description TEXT
9172 );
9173
9174 -- Linkage between matchpoints and limit sets
9175 CREATE TABLE config.circ_matrix_limit_set_map (
9176     id          SERIAL  PRIMARY KEY,
9177     matchpoint  INT     NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9178     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9179     fallthrough BOOL    NOT NULL DEFAULT FALSE, -- If true fallthrough will grab this rule as it goes along
9180     active      BOOL    NOT NULL DEFAULT TRUE,
9181     CONSTRAINT circ_limit_set_once_per_matchpoint UNIQUE (matchpoint, limit_set)
9182 );
9183
9184 -- Linkage between limit sets and circ mods
9185 CREATE TABLE config.circ_limit_set_circ_mod_map (
9186     id          SERIAL  PRIMARY KEY,
9187     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9188     circ_mod    TEXT    NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
9189     CONSTRAINT cm_once_per_set UNIQUE (limit_set, circ_mod)
9190 );
9191
9192 -- Linkage between limit sets and limit groups
9193 CREATE TABLE config.circ_limit_set_group_map (
9194     id          SERIAL  PRIMARY KEY,
9195     limit_set    INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9196     limit_group INT     NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9197     check_only  BOOL    NOT NULL DEFAULT FALSE, -- If true, don't accumulate this limit_group for storing with the circulation
9198     CONSTRAINT clg_once_per_set UNIQUE (limit_set, limit_group)
9199 );
9200
9201 -- Linkage between limit groups and circulations
9202 CREATE TABLE action.circulation_limit_group_map (
9203     circ        BIGINT      NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9204     limit_group INT         NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9205     PRIMARY KEY (circ, limit_group)
9206 );
9207
9208 -- Function for populating the circ/limit group mappings
9209 CREATE OR REPLACE FUNCTION action.link_circ_limit_groups ( BIGINT, INT[] ) RETURNS VOID AS $func$
9210     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));
9211 $func$ LANGUAGE SQL;
9212
9213 DROP TYPE IF EXISTS action.circ_matrix_test_result CASCADE;
9214 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[] );
9215
9216 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$
9217 DECLARE
9218     user_object             actor.usr%ROWTYPE;
9219     standing_penalty        config.standing_penalty%ROWTYPE;
9220     item_object             asset.copy%ROWTYPE;
9221     item_status_object      config.copy_status%ROWTYPE;
9222     item_location_object    asset.copy_location%ROWTYPE;
9223     result                  action.circ_matrix_test_result;
9224     circ_test               action.found_circ_matrix_matchpoint;
9225     circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
9226     circ_limit_set          config.circ_limit_set%ROWTYPE;
9227     hold_ratio              action.hold_stats%ROWTYPE;
9228     penalty_type            TEXT;
9229     items_out               INT;
9230     context_org_list        INT[];
9231     done                    BOOL := FALSE;
9232 BEGIN
9233     -- Assume success unless we hit a failure condition
9234     result.success := TRUE;
9235
9236     -- Need user info to look up matchpoints
9237     SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
9238
9239     -- (Insta)Fail if we couldn't find the user
9240     IF user_object.id IS NULL THEN
9241         result.fail_part := 'no_user';
9242         result.success := FALSE;
9243         done := TRUE;
9244         RETURN NEXT result;
9245         RETURN;
9246     END IF;
9247
9248     -- Need item info to look up matchpoints
9249     SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
9250
9251     -- (Insta)Fail if we couldn't find the item 
9252     IF item_object.id IS NULL THEN
9253         result.fail_part := 'no_item';
9254         result.success := FALSE;
9255         done := TRUE;
9256         RETURN NEXT result;
9257         RETURN;
9258     END IF;
9259
9260     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
9261
9262     circ_matchpoint             := circ_test.matchpoint;
9263     result.matchpoint           := circ_matchpoint.id;
9264     result.circulate            := circ_matchpoint.circulate;
9265     result.duration_rule        := circ_matchpoint.duration_rule;
9266     result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
9267     result.max_fine_rule        := circ_matchpoint.max_fine_rule;
9268     result.hard_due_date        := circ_matchpoint.hard_due_date;
9269     result.renewals             := circ_matchpoint.renewals;
9270     result.grace_period         := circ_matchpoint.grace_period;
9271     result.buildrows            := circ_test.buildrows;
9272
9273     -- (Insta)Fail if we couldn't find a matchpoint
9274     IF circ_test.success = false THEN
9275         result.fail_part := 'no_matchpoint';
9276         result.success := FALSE;
9277         done := TRUE;
9278         RETURN NEXT result;
9279         RETURN;
9280     END IF;
9281
9282     -- All failures before this point are non-recoverable
9283     -- Below this point are possibly overridable failures
9284
9285     -- Fail if the user is barred
9286     IF user_object.barred IS TRUE THEN
9287         result.fail_part := 'actor.usr.barred';
9288         result.success := FALSE;
9289         done := TRUE;
9290         RETURN NEXT result;
9291     END IF;
9292
9293     -- Fail if the item can't circulate
9294     IF item_object.circulate IS FALSE THEN
9295         result.fail_part := 'asset.copy.circulate';
9296         result.success := FALSE;
9297         done := TRUE;
9298         RETURN NEXT result;
9299     END IF;
9300
9301     -- Fail if the item isn't in a circulateable status on a non-renewal
9302     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
9303         result.fail_part := 'asset.copy.status';
9304         result.success := FALSE;
9305         done := TRUE;
9306         RETURN NEXT result;
9307     -- Alternately, fail if the item isn't checked out on a renewal
9308     ELSIF renewal AND item_object.status <> 1 THEN
9309         result.fail_part := 'asset.copy.status';
9310         result.success := FALSE;
9311         done := TRUE;
9312         RETURN NEXT result;
9313     END IF;
9314
9315     -- Fail if the item can't circulate because of the shelving location
9316     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
9317     IF item_location_object.circulate IS FALSE THEN
9318         result.fail_part := 'asset.copy_location.circulate';
9319         result.success := FALSE;
9320         done := TRUE;
9321         RETURN NEXT result;
9322     END IF;
9323
9324     -- Use Circ OU for penalties and such
9325     SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
9326
9327     IF renewal THEN
9328         penalty_type = '%RENEW%';
9329     ELSE
9330         penalty_type = '%CIRC%';
9331     END IF;
9332
9333     FOR standing_penalty IN
9334         SELECT  DISTINCT csp.*
9335           FROM  actor.usr_standing_penalty usp
9336                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
9337           WHERE usr = match_user
9338                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
9339                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
9340                 AND csp.block_list LIKE penalty_type LOOP
9341
9342         result.fail_part := standing_penalty.name;
9343         result.success := FALSE;
9344         done := TRUE;
9345         RETURN NEXT result;
9346     END LOOP;
9347
9348     -- Fail if the test is set to hard non-circulating
9349     IF circ_matchpoint.circulate IS FALSE THEN
9350         result.fail_part := 'config.circ_matrix_test.circulate';
9351         result.success := FALSE;
9352         done := TRUE;
9353         RETURN NEXT result;
9354     END IF;
9355
9356     -- Fail if the total copy-hold ratio is too low
9357     IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
9358         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
9359         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
9360             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
9361             result.success := FALSE;
9362             done := TRUE;
9363             RETURN NEXT result;
9364         END IF;
9365     END IF;
9366
9367     -- Fail if the available copy-hold ratio is too low
9368     IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
9369         IF hold_ratio.hold_count IS NULL THEN
9370             SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
9371         END IF;
9372         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
9373             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
9374             result.success := FALSE;
9375             done := TRUE;
9376             RETURN NEXT result;
9377         END IF;
9378     END IF;
9379
9380     -- Fail if the user has too many items out by defined limit sets
9381     FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
9382       JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
9383       WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
9384         ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
9385         ) LOOP
9386             IF circ_limit_set.items_out > 0 AND NOT renewal THEN
9387                 SELECT INTO context_org_list ARRAY_AGG(aou.id)
9388                   FROM actor.org_unit_full_path( circ_ou ) aou
9389                     JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
9390                   WHERE aout.depth >= circ_limit_set.depth;
9391                 IF circ_limit_set.global THEN
9392                     WITH RECURSIVE descendant_depth AS (
9393                         SELECT  ou.id,
9394                             ou.parent_ou
9395                         FROM  actor.org_unit ou
9396                         WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
9397                             UNION
9398                         SELECT  ou.id,
9399                             ou.parent_ou
9400                         FROM  actor.org_unit ou
9401                             JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
9402                     ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
9403                 END IF;
9404                 SELECT INTO items_out COUNT(DISTINCT circ.id)
9405                   FROM action.circulation circ
9406                     JOIN asset.copy copy ON (copy.id = circ.target_copy)
9407                     LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
9408                   WHERE circ.usr = match_user
9409                     AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
9410                     AND circ.checkin_time IS NULL
9411                     AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
9412                     AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
9413                         OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
9414                     );
9415                 IF items_out >= circ_limit_set.items_out THEN
9416                     result.fail_part := 'config.circ_matrix_circ_mod_test';
9417                     result.success := FALSE;
9418                     done := TRUE;
9419                     RETURN NEXT result;
9420                 END IF;
9421             END IF;
9422             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;
9423     END LOOP;
9424
9425     -- If we passed everything, return the successful matchpoint
9426     IF NOT done THEN
9427         RETURN NEXT result;
9428     END IF;
9429
9430     RETURN;
9431 END;
9432 $func$ LANGUAGE plpgsql;
9433
9434 -- We need to re-create these, as they got dropped with the type above.
9435 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
9436     SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
9437 $func$ LANGUAGE SQL;
9438
9439 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
9440     SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
9441 $func$ LANGUAGE SQL;
9442
9443 -- Temp function for migrating circ mod limits.
9444 CREATE OR REPLACE FUNCTION evergreen.temp_migrate_circ_mod_limits() RETURNS VOID AS $func$
9445 DECLARE
9446     circ_mod_group config.circ_matrix_circ_mod_test%ROWTYPE;
9447     current_set INT;
9448     circ_mod_count INT;
9449 BEGIN
9450     FOR circ_mod_group IN SELECT * FROM config.circ_matrix_circ_mod_test LOOP
9451         INSERT INTO config.circ_limit_set(name, owning_lib, items_out, depth, global, description)
9452             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'
9453                 FROM config.circ_matrix_matchpoint WHERE id = circ_mod_group.matchpoint
9454             RETURNING id INTO current_set;
9455         INSERT INTO config.circ_matrix_limit_set_map(matchpoint, limit_set, fallthrough, active) VALUES (circ_mod_group.matchpoint, current_set, false, true);
9456         INSERT INTO config.circ_limit_set_circ_mod_map(limit_set, circ_mod)
9457             SELECT current_set, circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = circ_mod_group.id;
9458         SELECT INTO circ_mod_count count(id) FROM config.circ_limit_set_circ_mod_map WHERE limit_set = current_set;
9459         RAISE NOTICE 'Created limit set with id % and % circ modifiers attached to matchpoint %', current_set, circ_mod_count, circ_mod_group.matchpoint;
9460     END LOOP;
9461 END;
9462 $func$ LANGUAGE plpgsql;
9463
9464 -- Run the temp function
9465 SELECT * FROM evergreen.temp_migrate_circ_mod_limits();
9466
9467 -- Drop the temp function
9468 DROP FUNCTION evergreen.temp_migrate_circ_mod_limits();
9469
9470 --Drop the old tables
9471 --Not sure we want to do this. Keeping them may help "something went wrong" correction.
9472 --DROP TABLE IF EXISTS config.circ_matrix_circ_mod_test_map, config.circ_matrix_circ_mod_test;
9473
9474
9475 -- Evergreen DB patch 0678.data.vandelay-default-merge-profiles.sql
9476
9477 -- check whether patch can be applied
9478 SELECT evergreen.upgrade_deps_block_check('0678', :eg_version);
9479
9480 INSERT INTO vandelay.merge_profile (owner, name, replace_spec) 
9481     VALUES (1, 'Match-Only Merge', '901c');
9482
9483 INSERT INTO vandelay.merge_profile (owner, name, preserve_spec) 
9484     VALUES (1, 'Full Overlay', '901c');
9485
9486 -- Evergreen DB patch 0681.schema.user-activity.sql
9487 --
9488
9489 -- check whether patch can be applied
9490 SELECT evergreen.upgrade_deps_block_check('0681', :eg_version);
9491
9492 -- SCHEMA --
9493
9494 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
9495
9496 CREATE TABLE config.usr_activity_type (
9497     id          SERIAL                      PRIMARY KEY, 
9498     ewho        TEXT,
9499     ewhat       TEXT,
9500     ehow        TEXT,
9501     label       TEXT                        NOT NULL, -- i18n
9502     egroup      config.usr_activity_group   NOT NULL,
9503     enabled     BOOL                        NOT NULL DEFAULT TRUE,
9504     transient   BOOL                        NOT NULL DEFAULT FALSE,
9505     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
9506 );
9507
9508 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
9509     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
9510
9511 CREATE TABLE actor.usr_activity (
9512     id          BIGSERIAL   PRIMARY KEY,
9513     usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
9514     etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
9515     event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
9516 );
9517
9518 -- remove transient activity entries on insert of new entries
9519 CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
9520 BEGIN
9521     DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
9522         WHERE atype.transient AND 
9523             NEW.etype = atype.id AND
9524             act.etype = atype.id AND
9525             act.usr = NEW.usr;
9526     RETURN NEW;
9527 END;
9528 $$ LANGUAGE PLPGSQL;
9529
9530 CREATE TRIGGER remove_transient_usr_activity
9531     BEFORE INSERT ON actor.usr_activity
9532     FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
9533
9534 -- given a set of activity criteria, find the most approprate activity type
9535 CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
9536         ewho TEXT, 
9537         ewhat TEXT, 
9538         ehow TEXT
9539     ) RETURNS SETOF config.usr_activity_type AS $$
9540 SELECT * FROM config.usr_activity_type 
9541     WHERE 
9542         enabled AND 
9543         (ewho  IS NULL OR ewho  = $1) AND
9544         (ewhat IS NULL OR ewhat = $2) AND
9545         (ehow  IS NULL OR ehow  = $3) 
9546     ORDER BY 
9547         -- BOOL comparisons sort false to true
9548         COALESCE(ewho, '')  != COALESCE($1, ''),
9549         COALESCE(ewhat,'')  != COALESCE($2, ''),
9550         COALESCE(ehow, '')  != COALESCE($3, '') 
9551     LIMIT 1;
9552 $$ LANGUAGE SQL;
9553
9554 -- given a set of activity criteria, finds the best
9555 -- activity type and inserts the activity entry
9556 CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
9557         usr INT,
9558         ewho TEXT, 
9559         ewhat TEXT, 
9560         ehow TEXT
9561     ) RETURNS SETOF actor.usr_activity AS $$
9562 DECLARE
9563     new_row actor.usr_activity%ROWTYPE;
9564 BEGIN
9565     SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
9566     IF FOUND THEN
9567         new_row.usr := usr;
9568         INSERT INTO actor.usr_activity (usr, etype) 
9569             VALUES (usr, new_row.etype)
9570             RETURNING * INTO new_row;
9571         RETURN NEXT new_row;
9572     END IF;
9573 END;
9574 $$ LANGUAGE plpgsql;
9575
9576 -- SEED DATA --
9577
9578 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
9579
9580      -- authen/authz actions
9581      -- note: "opensrf" is the default ingress/ehow
9582      (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
9583     ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
9584     ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
9585     ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
9586     ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
9587     ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
9588     ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
9589     ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
9590
9591     ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
9592     ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
9593     ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
9594     ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
9595     ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
9596     ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
9597     ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
9598
9599      -- authen/authz actions w/ known uses of "who"
9600     ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
9601     ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
9602     ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
9603     ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
9604     ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
9605     ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
9606     ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
9607
9608     ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
9609     ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
9610     -- ...
9611     ;
9612
9613 -- reserve the first 1000 slots
9614 SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
9615
9616 INSERT INTO config.org_unit_setting_type 
9617     (name, label, description, grp, datatype) 
9618     VALUES (
9619         'circ.patron.usr_activity_retrieve.max',
9620          oils_i18n_gettext(
9621             'circ.patron.usr_activity_retrieve.max',
9622             'Max user activity entries to retrieve (staff client)',
9623             'coust', 
9624             'label'
9625         ),
9626         oils_i18n_gettext(
9627             'circ.patron.usr_activity_retrieve.max',
9628             '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.',
9629             'coust', 
9630             'description'
9631         ),
9632         'gui',
9633         'integer'
9634     );
9635
9636
9637 SELECT evergreen.upgrade_deps_block_check('0682', :eg_version);
9638
9639 CREATE TABLE asset.copy_location_group (
9640     id              SERIAL  PRIMARY KEY,
9641     name            TEXT    NOT NULL, -- i18n
9642     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9643     pos             INT     NOT NULL DEFAULT 0,
9644     top             BOOL    NOT NULL DEFAULT FALSE,
9645     opac_visible    BOOL    NOT NULL DEFAULT TRUE,
9646     CONSTRAINT lgroup_once_per_owner UNIQUE (owner,name)
9647 );
9648
9649 CREATE TABLE asset.copy_location_group_map (
9650     id       SERIAL PRIMARY KEY,
9651     location    INT     NOT NULL REFERENCES asset.copy_location (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9652     lgroup      INT     NOT NULL REFERENCES asset.copy_location_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
9653     CONSTRAINT  lgroup_once_per_group UNIQUE (lgroup,location)
9654 );
9655
9656 -- check whether patch can be applied
9657 SELECT evergreen.upgrade_deps_block_check('0683', :eg_version);
9658
9659 INSERT INTO action_trigger.event_params (event_def, param, value)
9660     VALUES (5, 'check_email_notify', 1);
9661 INSERT INTO action_trigger.event_params (event_def, param, value)
9662     VALUES (7, 'check_email_notify', 1);
9663 INSERT INTO action_trigger.event_params (event_def, param, value)
9664     VALUES (9, 'check_email_notify', 1);
9665 INSERT INTO action_trigger.validator (module,description) VALUES
9666     ('HoldNotifyCheck',
9667     oils_i18n_gettext(
9668         'HoldNotifyCheck',
9669         'Check Hold notification flag(s)',
9670         'atval',
9671         'description'
9672     ));
9673 UPDATE action_trigger.event_definition SET validator = 'HoldNotifyCheck' WHERE id = 9;
9674
9675 -- NOT COVERED: Adding check_sms_notify to the proper trigger. It doesn't have a static id.
9676
9677 -- check whether patch can be applied
9678 SELECT evergreen.upgrade_deps_block_check('0684', :eg_version);
9679
9680 -- schema --
9681
9682 -- Replace the constraints with more flexible ENUM's
9683 ALTER TABLE vandelay.queue DROP CONSTRAINT queue_queue_type_check;
9684 ALTER TABLE vandelay.bib_queue DROP CONSTRAINT bib_queue_queue_type_check;
9685 ALTER TABLE vandelay.authority_queue DROP CONSTRAINT authority_queue_queue_type_check;
9686
9687 CREATE TYPE vandelay.bib_queue_queue_type AS ENUM ('bib', 'acq');
9688 CREATE TYPE vandelay.authority_queue_queue_type AS ENUM ('authority');
9689
9690 -- dropped column is also implemented by the child tables
9691 ALTER TABLE vandelay.queue DROP COLUMN queue_type; 
9692
9693 -- to recover after using the undo sql from below
9694 -- alter table vandelay.bib_queue  add column queue_type text default 'bib' not null;
9695 -- alter table vandelay.authority_queue  add column queue_type text default 'authority' not null;
9696
9697 -- modify the child tables to use the ENUMs
9698 ALTER TABLE vandelay.bib_queue 
9699     ALTER COLUMN queue_type DROP DEFAULT,
9700     ALTER COLUMN queue_type TYPE vandelay.bib_queue_queue_type 
9701         USING (queue_type::vandelay.bib_queue_queue_type),
9702     ALTER COLUMN queue_type SET DEFAULT 'bib';
9703
9704 ALTER TABLE vandelay.authority_queue 
9705     ALTER COLUMN queue_type DROP DEFAULT,
9706     ALTER COLUMN queue_type TYPE vandelay.authority_queue_queue_type 
9707         USING (queue_type::vandelay.authority_queue_queue_type),
9708     ALTER COLUMN queue_type SET DEFAULT 'authority';
9709
9710 -- give lineitems a pointer to their vandelay queued_record
9711
9712 ALTER TABLE acq.lineitem ADD COLUMN queued_record BIGINT
9713     REFERENCES vandelay.queued_bib_record (id) 
9714     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
9715
9716 ALTER TABLE acq.acq_lineitem_history ADD COLUMN queued_record BIGINT
9717     REFERENCES vandelay.queued_bib_record (id) 
9718     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
9719
9720 -- seed data --
9721
9722 INSERT INTO permission.perm_list ( id, code, description ) 
9723     VALUES ( 
9724         521, 
9725         'IMPORT_ACQ_LINEITEM_BIB_RECORD_UPLOAD', 
9726         oils_i18n_gettext( 
9727             521,
9728             'Allows a user to create new bibs directly from an ACQ MARC file upload', 
9729             'ppl', 
9730             'description' 
9731         )
9732     );
9733
9734
9735 INSERT INTO vandelay.import_error ( code, description ) 
9736     VALUES ( 
9737         'import.record.perm_failure', 
9738         oils_i18n_gettext(
9739             'import.record.perm_failure', 
9740             'Perm failure creating a record', 'vie', 'description') 
9741     );
9742
9743
9744
9745
9746 -- Evergreen DB patch 0685.data.bluray_vr_format.sql
9747 --
9748 -- FIXME: insert description of change, if needed
9749 --
9750
9751
9752 -- check whether patch can be applied
9753 SELECT evergreen.upgrade_deps_block_check('0685', :eg_version);
9754
9755 -- FIXME: add/check SQL statements to perform the upgrade
9756 DO $FUNC$
9757 DECLARE
9758     same_marc BOOL;
9759 BEGIN
9760     -- Check if it is already there
9761     PERFORM * FROM config.marc21_physical_characteristic_value_map v
9762         JOIN config.marc21_physical_characteristic_subfield_map s ON v.ptype_subfield = s.id
9763         WHERE s.ptype_key = 'v' AND s.subfield = 'e' AND s.start_pos = '4' AND s.length = '1'
9764             AND v.value = 's';
9765
9766     -- If it is, bail.
9767     IF FOUND THEN
9768         RETURN;
9769     END IF;
9770
9771     -- Otherwise, insert it
9772     INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label)
9773     SELECT 's',id,'Blu-ray'
9774         FROM config.marc21_physical_characteristic_subfield_map
9775         WHERE ptype_key = 'v' AND subfield = 'e' AND start_pos = '4' AND length = '1';
9776
9777     -- And reingest the blue-ray items so that things see the new value
9778     SELECT INTO same_marc enabled FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc';
9779     UPDATE config.internal_flag SET enabled = true WHERE name = 'ingest.reingest.force_on_same_marc';
9780     UPDATE biblio.record_entry SET marc=marc WHERE id IN (SELECT record
9781         FROM
9782             metabib.full_rec a JOIN metabib.full_rec b USING (record)
9783         WHERE
9784             a.tag = 'LDR' AND a.value LIKE '______g%'
9785         AND b.tag = '007' AND b.value LIKE 'v___s%');
9786     UPDATE config.internal_flag SET enabled = same_marc WHERE name = 'ingest.reingest.force_on_same_marc';
9787 END;
9788 $FUNC$;
9789
9790
9791 -- Evergreen DB patch 0686.schema.auditor_boost.sql
9792 --
9793 -- FIXME: insert description of change, if needed
9794 --
9795 -- check whether patch can be applied
9796 SELECT evergreen.upgrade_deps_block_check('0686', :eg_version);
9797
9798 -- FIXME: add/check SQL statements to perform the upgrade
9799 -- These three functions are for capturing, getting, and clearing user and workstation information
9800
9801 -- Set the User AND workstation in one call. Tis faster. And less calls.
9802 -- First argument is user, second is workstation
9803 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
9804     $_SHARED{"eg_audit_user"} = $_[0];
9805     $_SHARED{"eg_audit_ws"} = $_[1];
9806 $$ LANGUAGE plperl;
9807
9808 -- Get the User AND workstation in one call. Less calls, useful for joins ;)
9809 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
9810     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
9811 $$ LANGUAGE plperl;
9812
9813 -- Clear the audit info, for whatever reason
9814 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
9815     delete($_SHARED{"eg_audit_user"});
9816     delete($_SHARED{"eg_audit_ws"});
9817 $$ LANGUAGE plperl;
9818
9819 CREATE OR REPLACE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9820 BEGIN
9821     EXECUTE $$
9822         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
9823             audit_id    BIGINT                          PRIMARY KEY,
9824             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
9825             audit_action        TEXT                            NOT NULL,
9826             audit_user  INT,
9827             audit_ws    INT,
9828             LIKE $$ || sch || $$.$$ || tbl || $$
9829         );
9830     $$;
9831         RETURN TRUE;
9832 END;
9833 $creator$ LANGUAGE 'plpgsql';
9834
9835 CREATE OR REPLACE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9836 DECLARE
9837     column_list TEXT[];
9838 BEGIN
9839     SELECT INTO column_list array_agg(a.attname)
9840         FROM pg_catalog.pg_attribute a
9841             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
9842             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9843         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
9844
9845     EXECUTE $$
9846         CREATE OR REPLACE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
9847         RETURNS TRIGGER AS $func$
9848         BEGIN
9849             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, $$
9850             || array_to_string(column_list, ', ') || $$ )
9851                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
9852                     now(),
9853                     SUBSTR(TG_OP,1,1),
9854                     eg_user,
9855                     eg_ws,
9856                     OLD.$$ || array_to_string(column_list, ', OLD.') || $$
9857                 FROM auditor.get_audit_info();
9858             RETURN NULL;
9859         END;
9860         $func$ LANGUAGE 'plpgsql';
9861     $$;
9862     RETURN TRUE;
9863 END;
9864 $creator$ LANGUAGE 'plpgsql';
9865
9866 CREATE OR REPLACE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
9867 DECLARE
9868     column_list TEXT[];
9869 BEGIN
9870     SELECT INTO column_list array_agg(a.attname)
9871         FROM pg_catalog.pg_attribute a
9872             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
9873             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9874         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
9875
9876     EXECUTE $$
9877         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
9878             SELECT -1 AS audit_id,
9879                    now() AS audit_time,
9880                    '-' AS audit_action,
9881                    -1 AS audit_user,
9882                    -1 AS audit_ws,
9883                    $$ || array_to_string(column_list, ', ') || $$
9884               FROM $$ || sch || $$.$$ || tbl || $$
9885                 UNION ALL
9886             SELECT audit_id, audit_time, audit_action, audit_user, audit_ws,
9887             $$ || array_to_string(column_list, ', ') || $$
9888               FROM auditor.$$ || sch || $$_$$ || tbl || $$_history;
9889     $$;
9890     RETURN TRUE;
9891 END;
9892 $creator$ LANGUAGE 'plpgsql';
9893
9894 -- Corrects all column discrepencies between audit table and core table:
9895 -- Adds missing columns
9896 -- Removes leftover columns
9897 -- Updates types
9898 -- Also, ensures all core auditor columns exist.
9899 CREATE OR REPLACE FUNCTION auditor.fix_columns() RETURNS VOID AS $BODY$
9900 DECLARE
9901     current_table TEXT = ''; -- Storage for post-loop main table name
9902     current_audit_table TEXT = ''; -- Storage for post-loop audit table name
9903     query TEXT = ''; -- Storage for built query
9904     cr RECORD; -- column record object
9905     alter_t BOOL = false; -- Has the alter table command been appended yet
9906     auditor_cores TEXT[] = ARRAY[]::TEXT[]; -- Core auditor function list (filled inside of loop)
9907     core_column TEXT; -- The current core column we are adding
9908 BEGIN
9909     FOR cr IN
9910         WITH audit_tables AS ( -- Basic grab of auditor tables. Anything in the auditor namespace, basically. With oids.
9911             SELECT c.oid AS audit_oid, c.relname AS audit_table
9912             FROM pg_catalog.pg_class c
9913             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9914             WHERE relkind='r' AND nspname = 'auditor'
9915         ),
9916         table_set AS ( -- Union of auditor tables with their "main" tables. With oids.
9917             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
9918             FROM pg_catalog.pg_class c
9919             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
9920             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
9921             WHERE relkind = 'r'
9922         ),
9923         column_lists AS ( -- All columns associated with the auditor or main table, grouped by the main table's oid.
9924             SELECT DISTINCT ON (main_oid, attname) t.main_oid, a.attname
9925             FROM table_set t
9926             JOIN pg_catalog.pg_attribute a ON a.attrelid IN (t.main_oid, t.audit_oid)
9927             WHERE attnum > 0 AND NOT attisdropped
9928         ),
9929         column_defs AS ( -- The motherload, every audit table and main table plus column names and defs.
9930             SELECT audit_table,
9931                    main_namespace,
9932                    main_table,
9933                    a.attname AS main_column, -- These two will be null for columns that have since been deleted, or for auditor core columns
9934                    pg_catalog.format_type(a.atttypid, a.atttypmod) AS main_column_def,
9935                    b.attname AS audit_column, -- These two will be null for columns that have since been added
9936                    pg_catalog.format_type(b.atttypid, b.atttypmod) AS audit_column_def
9937             FROM table_set t
9938             JOIN column_lists c USING (main_oid)
9939             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
9940             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
9941         )
9942         -- Nice sorted output from the above
9943         SELECT * FROM column_defs WHERE main_column_def IS DISTINCT FROM audit_column_def ORDER BY main_namespace, main_table, main_column, audit_column
9944     LOOP
9945         IF current_table <> (cr.main_namespace || '.' || cr.main_table) THEN -- New table?
9946             FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Update missing core auditor columns
9947                 IF NOT alter_t THEN -- Add ALTER TABLE if we haven't already
9948                     query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9949                     alter_t:=TRUE;
9950                 ELSE
9951                     query:=query || $$,$$;
9952                 END IF;
9953                 -- 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.
9954                 query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
9955             END LOOP;
9956             IF alter_t THEN -- Open alter table = needs a semicolon
9957                 query:=query || $$; $$;
9958                 alter_t:=FALSE;
9959                 IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
9960                     -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
9961                     -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
9962                     EXECUTE query;
9963                     EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
9964                         $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
9965                     EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
9966                         $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
9967                     query:='';
9968                 END IF;
9969             END IF;
9970             -- New table means we reset the list of needed auditor core columns
9971             auditor_cores = ARRAY['audit_id bigint', 'audit_time timestamp with time zone', 'audit_action text', 'audit_user integer', 'audit_ws integer'];
9972             -- And store some values for use later, because we can't rely on cr in all places.
9973             current_table:=cr.main_namespace || '.' || cr.main_table;
9974             current_audit_table:=cr.audit_table;
9975         END IF;
9976         IF cr.main_column IS NULL AND cr.audit_column LIKE 'audit_%' THEN -- Core auditor column?
9977             -- Remove core from list of cores
9978             SELECT INTO auditor_cores array_agg(core) FROM unnest(auditor_cores) AS core WHERE core != (cr.audit_column || ' ' || cr.audit_column_def);
9979         ELSIF cr.main_column IS NULL THEN -- Main column doesn't exist, and it isn't an auditor column. Needs dropping from the auditor.
9980             IF NOT alter_t THEN
9981                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9982                 alter_t:=TRUE;
9983             ELSE
9984                 query:=query || $$,$$;
9985             END IF;
9986             query:=query || $$ DROP COLUMN $$ || cr.audit_column;
9987         ELSIF cr.audit_column IS NULL AND cr.main_column IS NOT NULL THEN -- New column auditor doesn't have. Add it.
9988             IF NOT alter_t THEN
9989                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9990                 alter_t:=TRUE;
9991             ELSE
9992                 query:=query || $$,$$;
9993             END IF;
9994             query:=query || $$ ADD COLUMN $$ || cr.main_column || $$ $$ || cr.main_column_def;
9995         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.
9996             IF NOT alter_t THEN
9997                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
9998                 alter_t:=TRUE;
9999             ELSE
10000                 query:=query || $$,$$;
10001             END IF;
10002             query:=query || $$ ALTER COLUMN $$ || cr.audit_column || $$ TYPE $$ || cr.main_column_def;
10003         END IF;
10004     END LOOP;
10005     FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Repeat this outside of the loop to catch the last table
10006         IF NOT alter_t THEN
10007             query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
10008             alter_t:=TRUE;
10009         ELSE
10010             query:=query || $$,$$;
10011         END IF;
10012         -- 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.
10013         query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
10014     END LOOP;
10015     IF alter_t THEN -- Open alter table = needs a semicolon
10016         query:=query || $$;$$;
10017         IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
10018             -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
10019             -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
10020             EXECUTE query;
10021             EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
10022                 $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
10023             EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
10024                 $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
10025             query:='';
10026         END IF;
10027     END IF;
10028     EXECUTE query;
10029 END;
10030 $BODY$ LANGUAGE plpgsql;
10031
10032 -- Update it all routine
10033 CREATE OR REPLACE FUNCTION auditor.update_auditors() RETURNS boolean AS $BODY$
10034 DECLARE
10035     auditor_name TEXT;
10036     table_schema TEXT;
10037     table_name TEXT;
10038 BEGIN
10039     -- Drop Lifecycle view(s) before potential column changes
10040     FOR auditor_name IN
10041         SELECT c.relname
10042             FROM pg_catalog.pg_class c
10043                 JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10044             WHERE relkind = 'v' AND n.nspname = 'auditor' LOOP
10045         EXECUTE $$ DROP VIEW auditor.$$ || auditor_name || $$;$$;
10046     END LOOP;
10047     -- Fix all column discrepencies
10048     PERFORM auditor.fix_columns();
10049     -- Re-create trigger functions and lifecycle views
10050     FOR table_schema, table_name IN
10051         WITH audit_tables AS (
10052             SELECT c.oid AS audit_oid, c.relname AS audit_table
10053             FROM pg_catalog.pg_class c
10054             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10055             WHERE relkind='r' AND nspname = 'auditor'
10056         ),
10057         table_set AS (
10058             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
10059             FROM pg_catalog.pg_class c
10060             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
10061             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
10062             WHERE relkind = 'r'
10063         )
10064         SELECT main_namespace, main_table FROM table_set LOOP
10065         
10066         PERFORM auditor.create_auditor_func(table_schema, table_name);
10067         PERFORM auditor.create_auditor_lifecycle(table_schema, table_name);
10068     END LOOP;
10069     RETURN TRUE;
10070 END;
10071 $BODY$ LANGUAGE plpgsql;
10072
10073 -- Go ahead and update them all now
10074 SELECT auditor.update_auditors();
10075
10076
10077 -- Evergreen DB patch 0687.schema.enhance_reingest.sql
10078 --
10079 -- FIXME: insert description of change, if needed
10080 --
10081
10082
10083 -- check whether patch can be applied
10084 SELECT evergreen.upgrade_deps_block_check('0687', :eg_version);
10085 SELECT evergreen.upgrade_deps_block_check('0711', :eg_version); -- introduces
10086 -- changes to metabib.reingest_metabib_field_entries() that must happen here
10087 -- rather than later in a separate CREATE OR REPLACE FUNCTION statement.
10088
10089 -- FIXME: add/check SQL statements to perform the upgrade
10090 -- New function def
10091 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$
10092 DECLARE
10093     fclass          RECORD;
10094     ind_data        metabib.field_entry_template%ROWTYPE;
10095     mbe_row         metabib.browse_entry%ROWTYPE;
10096     mbe_id          BIGINT;
10097     normalized_value    TEXT;
10098 BEGIN
10099     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10100     IF NOT FOUND THEN
10101         IF NOT skip_search THEN
10102             FOR fclass IN SELECT * FROM config.metabib_class LOOP
10103                 -- RAISE NOTICE 'Emptying out %', fclass.name;
10104                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10105             END LOOP;
10106         END IF;
10107         IF NOT skip_facet THEN
10108             DELETE FROM metabib.facet_entry WHERE source = bib_id;
10109         END IF;
10110         IF NOT skip_browse THEN
10111             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
10112         END IF;
10113     END IF;
10114
10115     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10116         IF ind_data.field < 0 THEN
10117             ind_data.field = -1 * ind_data.field;
10118         END IF;
10119
10120         IF ind_data.facet_field AND NOT skip_facet THEN
10121             INSERT INTO metabib.facet_entry (field, source, value)
10122                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10123         END IF;
10124
10125         IF ind_data.browse_field AND NOT skip_browse THEN
10126             -- A caveat about this SELECT: this should take care of replacing
10127             -- old mbe rows when data changes, but not if normalization (by
10128             -- which I mean specifically the output of
10129             -- evergreen.oils_tsearch2()) changes.  It may or may not be
10130             -- expensive to add a comparison of index_vector to index_vector
10131             -- to the WHERE clause below.
10132             normalized_value := metabib.browse_normalize(
10133                 ind_data.value, ind_data.field
10134             );
10135
10136             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = normalized_value;
10137             IF FOUND THEN
10138                 mbe_id := mbe_row.id;
10139             ELSE
10140                 INSERT INTO metabib.browse_entry (value) VALUES (normalized_value);
10141                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
10142             END IF;
10143
10144             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
10145                 VALUES (mbe_id, ind_data.field, ind_data.source);
10146         END IF;
10147
10148         IF ind_data.search_field AND NOT skip_search THEN
10149             EXECUTE $$
10150                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10151                     VALUES ($$ ||
10152                         quote_literal(ind_data.field) || $$, $$ ||
10153                         quote_literal(ind_data.source) || $$, $$ ||
10154                         quote_literal(ind_data.value) ||
10155                     $$);$$;
10156         END IF;
10157
10158     END LOOP;
10159
10160     RETURN;
10161 END;
10162 $func$ LANGUAGE PLPGSQL;
10163
10164 -- Delete old one
10165 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
10166
10167 -- Evergreen DB patch 0688.data.circ_history_export_csv.sql
10168 --
10169 -- FIXME: insert description of change, if needed
10170 --
10171
10172 -- check whether patch can be applied
10173 SELECT evergreen.upgrade_deps_block_check('0688', :eg_version);
10174
10175 INSERT INTO action_trigger.hook (key, core_type, description, passive)
10176 VALUES (
10177     'circ.format.history.csv',
10178     'circ',
10179     oils_i18n_gettext(
10180         'circ.format.history.csv',
10181         'Produce CSV of circulation history',
10182         'ath',
10183         'description'
10184     ),
10185     FALSE
10186 );
10187
10188 INSERT INTO action_trigger.event_definition (
10189     active, owner, name, hook, reactor, validator, group_field, template) 
10190 VALUES (
10191     TRUE, 1, 'Circ History CSV', 'circ.format.history.csv', 'ProcessTemplate', 'NOOP_True', 'usr',
10192 $$
10193 Title,Author,Call Number,Barcode,Format
10194 [%-
10195 FOR circ IN target;
10196     bibxml = helpers.unapi_bre(circ.target_copy.call_number.record, {flesh => '{mra}'});
10197     title = "";
10198     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10199         title = title _ part.textContent;
10200     END;
10201     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10202     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value') %]
10203
10204     [%- helpers.csv_datum(title) -%],
10205     [%- helpers.csv_datum(author) -%],
10206     [%- helpers.csv_datum(circ.target_copy.call_number.label) -%],
10207     [%- helpers.csv_datum(circ.target_copy.barcode) -%],
10208     [%- helpers.csv_datum(item_type) %]
10209 [%- END -%]
10210 $$
10211 );
10212
10213 INSERT INTO action_trigger.environment (event_def, path)
10214     VALUES (
10215         currval('action_trigger.event_definition_id_seq'),
10216         'target_copy.call_number'
10217     );
10218
10219
10220 -- Evergreen DB patch 0689.data.record_print_format_update.sql
10221 --
10222 -- Updates print and email templates for bib record actions
10223 --
10224
10225 -- check whether patch can be applied
10226 SELECT evergreen.upgrade_deps_block_check('0689', :eg_version);
10227
10228 UPDATE action_trigger.event_definition SET template = $$
10229 <div>
10230     <style> li { padding: 8px; margin 5px; }</style>
10231     <ol>
10232     [% FOR cbreb IN target %]
10233     [% FOR item IN cbreb.items;
10234         bre_id = item.target_biblio_record_entry;
10235
10236         bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10237         FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10238             title = title _ part.textContent;
10239         END;
10240
10241         author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10242         item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10243         publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10244         pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10245         isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10246         issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10247         upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10248         %]
10249
10250         <li>
10251             Bib ID# [% bre_id %]<br/>
10252             [% IF isbn %]ISBN: [% isbn %]<br/>[% END %]
10253             [% IF issn %]ISSN: [% issn %]<br/>[% END %]
10254             [% IF upc  %]UPC:  [% upc %]<br/>[% END %]
10255             Title: [% title %]<br />
10256             Author: [% author %]<br />
10257             Publication Info: [% publisher %] [% pubdate %]<br/>
10258             Item Type: [% item_type %]
10259         </li>
10260     [% END %]
10261     [% END %]
10262     </ol>
10263 </div>
10264 $$ 
10265 WHERE hook = 'biblio.format.record_entry.print' AND id < 100; -- sample data
10266
10267
10268 UPDATE action_trigger.event_definition SET delay = '00:00:00', template = $$
10269 [%- SET user = target.0.owner -%]
10270 To: [%- params.recipient_email || user.email %]
10271 From: [%- params.sender_email || default_sender %]
10272 Subject: Bibliographic Records
10273
10274 [% FOR cbreb IN target %]
10275 [% FOR item IN cbreb.items;
10276     bre_id = item.target_biblio_record_entry;
10277
10278     bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10279     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10280         title = title _ part.textContent;
10281     END;
10282
10283     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10284     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10285     publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10286     pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10287     isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10288     issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10289     upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10290 %]
10291
10292 [% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
10293 [% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
10294 [% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
10295 [% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
10296 Title: [% title %]
10297 Author: [% author %]
10298 Publication Info: [% publisher %] [% pubdate %]
10299 Item Type: [% item_type %]
10300
10301 [% END %]
10302 [% END %]
10303 $$ 
10304 WHERE hook = 'biblio.format.record_entry.email' AND id < 100; -- sample data
10305
10306 -- remove a swath of unused environment entries
10307
10308 DELETE FROM action_trigger.environment env 
10309     USING action_trigger.event_definition def 
10310     WHERE env.event_def = def.id AND 
10311         env.path != 'items' AND 
10312         def.hook = 'biblio.format.record_entry.print' AND 
10313         def.id < 100; -- sample data
10314
10315 DELETE FROM action_trigger.environment env 
10316     USING action_trigger.event_definition def 
10317     WHERE env.event_def = def.id AND 
10318         env.path != 'items' AND 
10319         env.path != 'owner' AND 
10320         def.hook = 'biblio.format.record_entry.email' AND 
10321         def.id < 100; -- sample data
10322
10323 -- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
10324 --
10325 -- Rewrite the in-database unapi functions to include per-object limits and
10326 -- offsets, such as a maximum number of copies and call numbers for given
10327 -- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
10328 -- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
10329 -- call number).
10330 --
10331 -- Add some notion of "preferred library" that will provide copy counts
10332 -- and optionally affect the sorting of returned copies.
10333 --
10334 -- Sort copies by availability, preferring the most available copies.
10335 --
10336 -- Return located URIs.
10337 --
10338 --
10339
10340 -- check whether patch can be applied
10341 SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
10342
10343 -- The simplest way to apply all of these changes is just to replace the unapi
10344 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
10345 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
10346 -- if it does, then the attempt to create the unapi schema in the following
10347 -- transaction will also fail. Not graceful, but safe!
10348 DROP SCHEMA IF EXISTS unapi CASCADE;
10349
10350 CREATE SCHEMA unapi;
10351
10352 CREATE OR REPLACE FUNCTION evergreen.org_top()
10353 RETURNS SETOF actor.org_unit AS $$
10354     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
10355 $$ LANGUAGE SQL STABLE
10356 ROWS 1;
10357
10358 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
10359 RETURNS anyarray AS $$
10360     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
10361 $$ LANGUAGE SQL STABLE;
10362
10363 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
10364 RETURNS INTEGER AS $$
10365     WITH search_libs AS (
10366         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
10367     )
10368     SELECT COALESCE(
10369         (SELECT -10000 FROM actor.org_unit
10370          WHERE $1 = $3 AND id = $3 AND $2 IN (
10371                 SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
10372              )
10373         ),
10374         (SELECT distance FROM search_libs WHERE id = $1),
10375         10000
10376     );
10377 $$ LANGUAGE SQL STABLE;
10378
10379 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
10380 RETURNS INTEGER AS $$
10381     WITH totally_available AS (
10382         SELECT id, 0 AS avail_rank
10383         FROM config.copy_status
10384         WHERE opac_visible IS TRUE
10385             AND copy_active IS TRUE
10386             AND id != 1 -- "Checked out"
10387     ), almost_available AS (
10388         SELECT id, 10 AS avail_rank
10389         FROM config.copy_status
10390         WHERE holdable IS TRUE
10391             AND opac_visible IS TRUE
10392             AND copy_active IS FALSE
10393             OR id = 1 -- "Checked out"
10394     )
10395     SELECT COALESCE(
10396         (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
10397         (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
10398         100
10399     );
10400 $$ LANGUAGE SQL STABLE;
10401
10402 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
10403     bibid BIGINT, 
10404     ouid INT,
10405     depth INT DEFAULT NULL,
10406     slimit HSTORE DEFAULT NULL,
10407     soffset HSTORE DEFAULT NULL,
10408     pref_lib INT DEFAULT NULL
10409 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
10410     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
10411         SELECT acn.id, aou.name, acn.label_sortkey,
10412             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
10413             RANK() OVER w
10414         FROM asset.call_number acn
10415             JOIN asset.copy acp ON (acn.id = acp.call_number)
10416             JOIN actor.org_unit_descendants( $2, COALESCE(
10417                 $3, (
10418                     SELECT depth
10419                     FROM actor.org_unit_type aout
10420                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
10421                     WHERE ou.id = $2
10422                 ), $6)
10423             ) AS aou ON (acp.circ_lib = aou.id)
10424         WHERE acn.record = $1
10425             AND acn.deleted IS FALSE
10426             AND acp.deleted IS FALSE
10427         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
10428         WINDOW w AS (
10429             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
10430         )
10431     ) AS ua
10432     GROUP BY ua.id, ua.name, ua.label_sortkey
10433     ORDER BY rank, ua.name, ua.label_sortkey
10434     LIMIT ($4 -> 'acn')::INT
10435     OFFSET ($5 -> 'acn')::INT;
10436 $$
10437 LANGUAGE SQL STABLE;
10438
10439 CREATE OR REPLACE FUNCTION evergreen.located_uris (
10440     bibid BIGINT, 
10441     ouid INT,
10442     pref_lib INT DEFAULT NULL
10443 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
10444     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10445       FROM asset.call_number acn
10446            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10447            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10448            INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
10449       WHERE acn.record = $1
10450           AND acn.deleted IS FALSE
10451           AND auri.active IS TRUE
10452     UNION
10453     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10454       FROM asset.call_number acn
10455            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10456            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10457            INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
10458       WHERE acn.record = $1
10459           AND acn.deleted IS FALSE
10460           AND auri.active IS TRUE;
10461 $$
10462 LANGUAGE SQL STABLE;
10463
10464 CREATE TABLE unapi.bre_output_layout (
10465     name                TEXT    PRIMARY KEY,
10466     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
10467     mime_type           TEXT    NOT NULL,
10468     feed_top            TEXT    NOT NULL,
10469     holdings_element    TEXT,
10470     title_element       TEXT,
10471     description_element TEXT,
10472     creator_element     TEXT,
10473     update_ts_element   TEXT
10474 );
10475
10476 INSERT INTO unapi.bre_output_layout
10477     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
10478         VALUES
10479     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
10480     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
10481     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
10482 ;
10483
10484 -- Dummy functions, so we can create the real ones out of order
10485 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;
10486 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;
10487 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;
10488 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;
10489 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;
10490 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;
10491 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;
10492 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;
10493 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;
10494 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;
10495 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;
10496 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;
10497 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;
10498 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;
10499 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;
10500 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;
10501 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;
10502 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;
10503 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;
10504 CREATE OR REPLACE FUNCTION unapi.bre (
10505     obj_id BIGINT,
10506     format TEXT,
10507     ename TEXT,
10508     includes TEXT[],
10509     org TEXT,
10510     depth INT DEFAULT NULL,
10511     slimit HSTORE DEFAULT NULL,
10512     soffset HSTORE DEFAULT NULL,
10513     include_xmlns BOOL DEFAULT TRUE,
10514     pref_lib INT DEFAULT NULL
10515 )
10516 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10517 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;
10518 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;
10519 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;
10520
10521 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10522     bid BIGINT,
10523     ouid INT,
10524     org TEXT,
10525     depth INT DEFAULT NULL,
10526     includes TEXT[] DEFAULT NULL::TEXT[],
10527     slimit HSTORE DEFAULT NULL,
10528     soffset HSTORE DEFAULT NULL,
10529     include_xmlns BOOL DEFAULT TRUE,
10530     pref_lib INT DEFAULT NULL
10531 )
10532 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10533
10534 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;
10535
10536 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$
10537 DECLARE
10538     key     TEXT;
10539     output  XML;
10540 BEGIN
10541     key :=
10542         'id'        || COALESCE(obj_id::TEXT,'') ||
10543         'format'    || COALESCE(format::TEXT,'') ||
10544         'ename'     || COALESCE(ename::TEXT,'') ||
10545         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
10546         'org'       || COALESCE(org::TEXT,'') ||
10547         'depth'     || COALESCE(depth::TEXT,'') ||
10548         'slimit'    || COALESCE(slimit::TEXT,'') ||
10549         'soffset'   || COALESCE(soffset::TEXT,'') ||
10550         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
10551     -- RAISE NOTICE 'memoize key: %', key;
10552
10553     key := MD5(key);
10554     -- RAISE NOTICE 'memoize hash: %', key;
10555
10556     -- XXX cache logic ... memcached? table?
10557
10558     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;
10559     RETURN output;
10560 END;
10561 $F$ LANGUAGE PLPGSQL STABLE;
10562
10563 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$
10564 DECLARE
10565     layout          unapi.bre_output_layout%ROWTYPE;
10566     transform       config.xml_transform%ROWTYPE;
10567     item_format     TEXT;
10568     tmp_xml         TEXT;
10569     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
10570     ouid            INT;
10571     element_list    TEXT[];
10572 BEGIN
10573
10574     IF org = '-' OR org IS NULL THEN
10575         SELECT shortname INTO org FROM evergreen.org_top();
10576     END IF;
10577
10578     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10579     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10580
10581     IF layout.name IS NULL THEN
10582         RETURN NULL::XML;
10583     END IF;
10584
10585     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
10586     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
10587
10588     -- Gather the bib xml
10589     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
10590
10591     IF layout.title_element IS NOT NULL THEN
10592         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
10593     END IF;
10594
10595     IF layout.description_element IS NOT NULL THEN
10596         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
10597     END IF;
10598
10599     IF layout.creator_element IS NOT NULL THEN
10600         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
10601     END IF;
10602
10603     IF layout.update_ts_element IS NOT NULL THEN
10604         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;
10605     END IF;
10606
10607     IF unapi_url IS NOT NULL THEN
10608         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;
10609     END IF;
10610
10611     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
10612
10613     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
10614     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
10615         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
10616     END LOOP;
10617
10618     RETURN tmp_xml::XML;
10619 END;
10620 $F$ LANGUAGE PLPGSQL STABLE;
10621
10622 CREATE OR REPLACE FUNCTION unapi.bre (
10623     obj_id BIGINT,
10624     format TEXT,
10625     ename TEXT,
10626     includes TEXT[],
10627     org TEXT,
10628     depth INT DEFAULT NULL,
10629     slimit HSTORE DEFAULT NULL,
10630     soffset HSTORE DEFAULT NULL,
10631     include_xmlns BOOL DEFAULT TRUE,
10632     pref_lib INT DEFAULT NULL
10633 )
10634 RETURNS XML AS $F$
10635 DECLARE
10636     me      biblio.record_entry%ROWTYPE;
10637     layout  unapi.bre_output_layout%ROWTYPE;
10638     xfrm    config.xml_transform%ROWTYPE;
10639     ouid    INT;
10640     tmp_xml TEXT;
10641     top_el  TEXT;
10642     output  XML;
10643     hxml    XML;
10644     axml    XML;
10645 BEGIN
10646
10647     IF org = '-' OR org IS NULL THEN
10648         SELECT shortname INTO org FROM evergreen.org_top();
10649     END IF;
10650
10651     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10652
10653     IF ouid IS NULL THEN
10654         RETURN NULL::XML;
10655     END IF;
10656
10657     IF format = 'holdings_xml' THEN -- the special case
10658         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
10659         RETURN output;
10660     END IF;
10661
10662     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10663
10664     IF layout.name IS NULL THEN
10665         RETURN NULL::XML;
10666     END IF;
10667
10668     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
10669
10670     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
10671
10672     -- grab SVF if we need them
10673     IF ('mra' = ANY (includes)) THEN 
10674         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
10675     ELSE
10676         axml := NULL::XML;
10677     END IF;
10678
10679     -- grab holdings if we need them
10680     IF ('holdings_xml' = ANY (includes)) THEN 
10681         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
10682     ELSE
10683         hxml := NULL::XML;
10684     END IF;
10685
10686
10687     -- generate our item node
10688
10689
10690     IF format = 'marcxml' THEN
10691         tmp_xml := me.marc;
10692         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
10693            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
10694         END IF; 
10695     ELSE
10696         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
10697     END IF;
10698
10699     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
10700
10701     IF axml IS NOT NULL THEN 
10702         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
10703     END IF;
10704
10705     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
10706         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
10707     END IF;
10708
10709     IF ('bre.unapi' = ANY (includes)) THEN 
10710         output := REGEXP_REPLACE(
10711             tmp_xml,
10712             '</' || top_el || '>(.*?)',
10713             XMLELEMENT(
10714                 name abbr,
10715                 XMLATTRIBUTES(
10716                     'http://www.w3.org/1999/xhtml' AS xmlns,
10717                     'unapi-id' AS class,
10718                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
10719                 )
10720             )::TEXT || '</' || top_el || E'>\\1'
10721         );
10722     ELSE
10723         output := tmp_xml;
10724     END IF;
10725
10726     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
10727     RETURN output;
10728 END;
10729 $F$ LANGUAGE PLPGSQL STABLE;
10730
10731 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10732     bid BIGINT,
10733     ouid INT,
10734     org TEXT,
10735     depth INT DEFAULT NULL,
10736     includes TEXT[] DEFAULT NULL::TEXT[],
10737     slimit HSTORE DEFAULT NULL,
10738     soffset HSTORE DEFAULT NULL,
10739     include_xmlns BOOL DEFAULT TRUE,
10740     pref_lib INT DEFAULT NULL
10741 )
10742 RETURNS XML AS $F$
10743      SELECT  XMLELEMENT(
10744                  name holdings,
10745                  XMLATTRIBUTES(
10746                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10747                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
10748                  ),
10749                  XMLELEMENT(
10750                      name counts,
10751                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
10752                          SELECT  XMLELEMENT(
10753                                      name count,
10754                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10755                                  )::text
10756                            FROM  asset.opac_ou_record_copy_count($2,  $1)
10757                                      UNION
10758                          SELECT  XMLELEMENT(
10759                                      name count,
10760                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10761                                  )::text
10762                            FROM  asset.staff_ou_record_copy_count($2, $1)
10763                                      UNION
10764                          SELECT  XMLELEMENT(
10765                                      name count,
10766                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10767                                  )::text
10768                            FROM  asset.opac_ou_record_copy_count($9,  $1)
10769                                      ORDER BY 1
10770                      )x)
10771                  ),
10772                  CASE 
10773                      WHEN ('bmp' = ANY ($5)) THEN
10774                         XMLELEMENT(
10775                             name monograph_parts,
10776                             (SELECT XMLAGG(bmp) FROM (
10777                                 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)
10778                                   FROM  biblio.monograph_part
10779                                   WHERE record = $1
10780                             )x)
10781                         )
10782                      ELSE NULL
10783                  END,
10784                  XMLELEMENT(
10785                      name volumes,
10786                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
10787                         -- Physical copies
10788                         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
10789                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
10790                         UNION ALL
10791                         -- Located URIs
10792                         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
10793                         FROM evergreen.located_uris($1, $2, $9) AS uris
10794                      )x)
10795                  ),
10796                  CASE WHEN ('ssub' = ANY ($5)) THEN 
10797                      XMLELEMENT(
10798                          name subscriptions,
10799                          (SELECT XMLAGG(ssub) FROM (
10800                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
10801                               FROM  serial.subscription
10802                               WHERE record_entry = $1
10803                         )x)
10804                      )
10805                  ELSE NULL END,
10806                  CASE WHEN ('acp' = ANY ($5)) THEN 
10807                      XMLELEMENT(
10808                          name foreign_copies,
10809                          (SELECT XMLAGG(acp) FROM (
10810                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
10811                               FROM  biblio.peer_bib_copy_map p
10812                                     JOIN asset.copy c ON (p.target_copy = c.id)
10813                               WHERE NOT c.deleted AND p.peer_record = $1
10814                             LIMIT ($6 -> 'acp')::INT
10815                             OFFSET ($7 -> 'acp')::INT
10816                         )x)
10817                      )
10818                  ELSE NULL END
10819              );
10820 $F$ LANGUAGE SQL STABLE;
10821
10822 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$
10823         SELECT  XMLELEMENT(
10824                     name subscription,
10825                     XMLATTRIBUTES(
10826                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10827                         'tag:open-ils.org:U2@ssub/' || id AS id,
10828                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
10829                         start_date AS start, end_date AS end, expected_date_offset
10830                     ),
10831                     CASE 
10832                         WHEN ('sdist' = ANY ($4)) THEN
10833                             XMLELEMENT( name distributions,
10834                                 (SELECT XMLAGG(sdist) FROM (
10835                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
10836                                       FROM  serial.distribution
10837                                       WHERE subscription = ssub.id
10838                                 )x)
10839                             )
10840                         ELSE NULL
10841                     END
10842                 )
10843           FROM  serial.subscription ssub
10844           WHERE id = $1
10845           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
10846 $F$ LANGUAGE SQL STABLE;
10847
10848 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$
10849         SELECT  XMLELEMENT(
10850                     name distribution,
10851                     XMLATTRIBUTES(
10852                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10853                         'tag:open-ils.org:U2@sdist/' || id AS id,
10854                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
10855                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
10856                         unit_label_prefix, label, unit_label_suffix, summary_method
10857                     ),
10858                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
10859                     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,
10860                     CASE 
10861                         WHEN ('sstr' = ANY ($4)) THEN
10862                             XMLELEMENT( name streams,
10863                                 (SELECT XMLAGG(sstr) FROM (
10864                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10865                                       FROM  serial.stream
10866                                       WHERE distribution = sdist.id
10867                                 )x)
10868                             )
10869                         ELSE NULL
10870                     END,
10871                     XMLELEMENT( name summaries,
10872                         CASE 
10873                             WHEN ('sbsum' = ANY ($4)) THEN
10874                                 (SELECT XMLAGG(sbsum) FROM (
10875                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10876                                       FROM  serial.basic_summary
10877                                       WHERE distribution = sdist.id
10878                                 )x)
10879                             ELSE NULL
10880                         END,
10881                         CASE 
10882                             WHEN ('sisum' = ANY ($4)) THEN
10883                                 (SELECT XMLAGG(sisum) FROM (
10884                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10885                                       FROM  serial.index_summary
10886                                       WHERE distribution = sdist.id
10887                                 )x)
10888                             ELSE NULL
10889                         END,
10890                         CASE 
10891                             WHEN ('sssum' = ANY ($4)) THEN
10892                                 (SELECT XMLAGG(sssum) FROM (
10893                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10894                                       FROM  serial.supplement_summary
10895                                       WHERE distribution = sdist.id
10896                                 )x)
10897                             ELSE NULL
10898                         END
10899                     )
10900                 )
10901           FROM  serial.distribution sdist
10902           WHERE id = $1
10903           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
10904 $F$ LANGUAGE SQL STABLE;
10905
10906 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$
10907     SELECT  XMLELEMENT(
10908                 name stream,
10909                 XMLATTRIBUTES(
10910                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10911                     'tag:open-ils.org:U2@sstr/' || id AS id,
10912                     routing_label
10913                 ),
10914                 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,
10915                 CASE 
10916                     WHEN ('sitem' = ANY ($4)) THEN
10917                         XMLELEMENT( name items,
10918                             (SELECT XMLAGG(sitem) FROM (
10919                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
10920                                   FROM  serial.item
10921                                   WHERE stream = sstr.id
10922                             )x)
10923                         )
10924                     ELSE NULL
10925                 END
10926             )
10927       FROM  serial.stream sstr
10928       WHERE id = $1
10929       GROUP BY id, routing_label, distribution;
10930 $F$ LANGUAGE SQL STABLE;
10931
10932 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$
10933     SELECT  XMLELEMENT(
10934                 name issuance,
10935                 XMLATTRIBUTES(
10936                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10937                     'tag:open-ils.org:U2@siss/' || id AS id,
10938                     create_date, edit_date, label, date_published,
10939                     holding_code, holding_type, holding_link_id
10940                 ),
10941                 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,
10942                 CASE 
10943                     WHEN ('sitem' = ANY ($4)) THEN
10944                         XMLELEMENT( name items,
10945                             (SELECT XMLAGG(sitem) FROM (
10946                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
10947                                   FROM  serial.item
10948                                   WHERE issuance = sstr.id
10949                             )x)
10950                         )
10951                     ELSE NULL
10952                 END
10953             )
10954       FROM  serial.issuance sstr
10955       WHERE id = $1
10956       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
10957 $F$ LANGUAGE SQL STABLE;
10958
10959 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$
10960         SELECT  XMLELEMENT(
10961                     name serial_item,
10962                     XMLATTRIBUTES(
10963                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10964                         'tag:open-ils.org:U2@sitem/' || id AS id,
10965                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
10966                         date_expected, date_received
10967                     ),
10968                     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,
10969                     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,
10970                     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,
10971                     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
10972 --                    XMLELEMENT( name notes,
10973 --                        CASE 
10974 --                            WHEN ('acpn' = ANY ($4)) THEN
10975 --                                (SELECT XMLAGG(acpn) FROM (
10976 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
10977 --                                      FROM  asset.copy_note
10978 --                                      WHERE owning_copy = cp.id AND pub
10979 --                                )x)
10980 --                            ELSE NULL
10981 --                        END
10982 --                    )
10983                 )
10984           FROM  serial.item sitem
10985           WHERE id = $1;
10986 $F$ LANGUAGE SQL STABLE;
10987
10988
10989 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$
10990     SELECT  XMLELEMENT(
10991                 name serial_summary,
10992                 XMLATTRIBUTES(
10993                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10994                     'tag:open-ils.org:U2@sbsum/' || id AS id,
10995                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
10996                 ),
10997                 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
10998             )
10999       FROM  serial.supplement_summary ssum
11000       WHERE id = $1
11001       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11002 $F$ LANGUAGE SQL STABLE;
11003
11004 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$
11005     SELECT  XMLELEMENT(
11006                 name serial_summary,
11007                 XMLATTRIBUTES(
11008                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11009                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11010                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
11011                 ),
11012                 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
11013             )
11014       FROM  serial.basic_summary ssum
11015       WHERE id = $1
11016       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11017 $F$ LANGUAGE SQL STABLE;
11018
11019 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$
11020     SELECT  XMLELEMENT(
11021                 name serial_summary,
11022                 XMLATTRIBUTES(
11023                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11024                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11025                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
11026                 ),
11027                 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
11028             )
11029       FROM  serial.index_summary ssum
11030       WHERE id = $1
11031       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11032 $F$ LANGUAGE SQL STABLE;
11033
11034
11035 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$
11036 DECLARE
11037     output XML;
11038 BEGIN
11039     IF ename = 'circlib' THEN
11040         SELECT  XMLELEMENT(
11041                     name circlib,
11042                     XMLATTRIBUTES(
11043                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11044                         id AS ident
11045                     ),
11046                     name
11047                 ) INTO output
11048           FROM  actor.org_unit aou
11049           WHERE id = obj_id;
11050     ELSE
11051         EXECUTE $$SELECT  XMLELEMENT(
11052                     name $$ || ename || $$,
11053                     XMLATTRIBUTES(
11054                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11055                         'tag:open-ils.org:U2@aou/' || id AS id,
11056                         shortname, name, opac_visible
11057                     )
11058                 )
11059           FROM  actor.org_unit aou
11060          WHERE id = $1 $$ INTO output USING obj_id;
11061     END IF;
11062
11063     RETURN output;
11064
11065 END;
11066 $F$ LANGUAGE PLPGSQL STABLE;
11067
11068 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$
11069     SELECT  XMLELEMENT(
11070                 name location,
11071                 XMLATTRIBUTES(
11072                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11073                     id AS ident,
11074                     holdable,
11075                     opac_visible,
11076                     label_prefix AS prefix,
11077                     label_suffix AS suffix
11078                 ),
11079                 name
11080             )
11081       FROM  asset.copy_location
11082       WHERE id = $1;
11083 $F$ LANGUAGE SQL STABLE;
11084
11085 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$
11086     SELECT  XMLELEMENT(
11087                 name status,
11088                 XMLATTRIBUTES(
11089                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11090                     id AS ident,
11091                     holdable,
11092                     opac_visible
11093                 ),
11094                 name
11095             )
11096       FROM  config.copy_status
11097       WHERE id = $1;
11098 $F$ LANGUAGE SQL STABLE;
11099
11100 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$
11101         SELECT  XMLELEMENT(
11102                     name copy_note,
11103                     XMLATTRIBUTES(
11104                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11105                         create_date AS date,
11106                         title
11107                     ),
11108                     value
11109                 )
11110           FROM  asset.copy_note
11111           WHERE id = $1;
11112 $F$ LANGUAGE SQL STABLE;
11113
11114 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$
11115         SELECT  XMLELEMENT(
11116                     name statcat,
11117                     XMLATTRIBUTES(
11118                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11119                         sc.name,
11120                         sc.opac_visible
11121                     ),
11122                     asce.value
11123                 )
11124           FROM  asset.stat_cat_entry asce
11125                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
11126           WHERE asce.id = $1;
11127 $F$ LANGUAGE SQL STABLE;
11128
11129 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$
11130         SELECT  XMLELEMENT(
11131                     name monograph_part,
11132                     XMLATTRIBUTES(
11133                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11134                         'tag:open-ils.org:U2@bmp/' || id AS id,
11135                         id AS ident,
11136                         label,
11137                         label_sortkey,
11138                         'tag:open-ils.org:U2@bre/' || record AS record
11139                     ),
11140                     CASE 
11141                         WHEN ('acp' = ANY ($4)) THEN
11142                             XMLELEMENT( name copies,
11143                                 (SELECT XMLAGG(acp) FROM (
11144                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
11145                                       FROM  asset.copy cp
11146                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
11147                                       WHERE cpm.part = $1
11148                                           AND cp.deleted IS FALSE
11149                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
11150                                       LIMIT ($7 -> 'acp')::INT
11151                                       OFFSET ($8 -> 'acp')::INT
11152
11153                                 )x)
11154                             )
11155                         ELSE NULL
11156                     END,
11157                     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
11158                 )
11159           FROM  biblio.monograph_part
11160           WHERE id = $1
11161           GROUP BY id, label, label_sortkey, record;
11162 $F$ LANGUAGE SQL STABLE;
11163
11164 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$
11165         SELECT  XMLELEMENT(
11166                     name copy,
11167                     XMLATTRIBUTES(
11168                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11169                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11170                         create_date, edit_date, copy_number, circulate, deposit,
11171                         ref, holdable, deleted, deposit_amount, price, barcode,
11172                         circ_modifier, circ_as_type, opac_visible, age_protect
11173                     ),
11174                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11175                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11176                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11177                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11178                     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,
11179                     CASE 
11180                         WHEN ('acpn' = ANY ($4)) THEN
11181                             XMLELEMENT( name copy_notes,
11182                                 (SELECT XMLAGG(acpn) FROM (
11183                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11184                                       FROM  asset.copy_note
11185                                       WHERE owning_copy = cp.id AND pub
11186                                 )x)
11187                             )
11188                         ELSE NULL
11189                     END,
11190                     CASE 
11191                         WHEN ('ascecm' = ANY ($4)) THEN
11192                             XMLELEMENT( name statcats,
11193                                 (SELECT XMLAGG(ascecm) FROM (
11194                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11195                                       FROM  asset.stat_cat_entry_copy_map
11196                                       WHERE owning_copy = cp.id
11197                                 )x)
11198                             )
11199                         ELSE NULL
11200                     END,
11201                     CASE
11202                         WHEN ('bre' = ANY ($4)) THEN
11203                             XMLELEMENT( name foreign_records,
11204                                 (SELECT XMLAGG(bre) FROM (
11205                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11206                                       FROM  biblio.peer_bib_copy_map
11207                                       WHERE target_copy = cp.id
11208                                 )x)
11209
11210                             )
11211                         ELSE NULL
11212                     END,
11213                     CASE 
11214                         WHEN ('bmp' = ANY ($4)) THEN
11215                             XMLELEMENT( name monograph_parts,
11216                                 (SELECT XMLAGG(bmp) FROM (
11217                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11218                                       FROM  asset.copy_part_map
11219                                       WHERE target_copy = cp.id
11220                                 )x)
11221                             )
11222                         ELSE NULL
11223                     END,
11224                     CASE 
11225                         WHEN ('circ' = ANY ($4)) THEN
11226                             XMLELEMENT( name current_circulation,
11227                                 (SELECT XMLAGG(circ) FROM (
11228                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11229                                       FROM  action.circulation
11230                                       WHERE target_copy = cp.id
11231                                             AND checkin_time IS NULL
11232                                 )x)
11233                             )
11234                         ELSE NULL
11235                     END
11236                 )
11237           FROM  asset.copy cp
11238           WHERE id = $1
11239               AND cp.deleted IS FALSE
11240           GROUP BY id, status, location, circ_lib, call_number, create_date,
11241               edit_date, copy_number, circulate, deposit, ref, holdable,
11242               deleted, deposit_amount, price, barcode, circ_modifier,
11243               circ_as_type, opac_visible, age_protect;
11244 $F$ LANGUAGE SQL STABLE;
11245
11246 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$
11247         SELECT  XMLELEMENT(
11248                     name serial_unit,
11249                     XMLATTRIBUTES(
11250                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11251                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11252                         create_date, edit_date, copy_number, circulate, deposit,
11253                         ref, holdable, deleted, deposit_amount, price, barcode,
11254                         circ_modifier, circ_as_type, opac_visible, age_protect,
11255                         status_changed_time, floating, mint_condition,
11256                         detailed_contents, sort_key, summary_contents, cost 
11257                     ),
11258                     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),
11259                     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),
11260                     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),
11261                     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),
11262                     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,
11263                     XMLELEMENT( name copy_notes,
11264                         CASE 
11265                             WHEN ('acpn' = ANY ($4)) THEN
11266                                 (SELECT XMLAGG(acpn) FROM (
11267                                     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)
11268                                       FROM  asset.copy_note
11269                                       WHERE owning_copy = cp.id AND pub
11270                                 )x)
11271                             ELSE NULL
11272                         END
11273                     ),
11274                     XMLELEMENT( name statcats,
11275                         CASE 
11276                             WHEN ('ascecm' = ANY ($4)) THEN
11277                                 (SELECT XMLAGG(ascecm) FROM (
11278                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11279                                       FROM  asset.stat_cat_entry_copy_map
11280                                       WHERE owning_copy = cp.id
11281                                 )x)
11282                             ELSE NULL
11283                         END
11284                     ),
11285                     XMLELEMENT( name foreign_records,
11286                         CASE
11287                             WHEN ('bre' = ANY ($4)) THEN
11288                                 (SELECT XMLAGG(bre) FROM (
11289                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11290                                       FROM  biblio.peer_bib_copy_map
11291                                       WHERE target_copy = cp.id
11292                                 )x)
11293                             ELSE NULL
11294                         END
11295                     ),
11296                     CASE 
11297                         WHEN ('bmp' = ANY ($4)) THEN
11298                             XMLELEMENT( name monograph_parts,
11299                                 (SELECT XMLAGG(bmp) FROM (
11300                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11301                                       FROM  asset.copy_part_map
11302                                       WHERE target_copy = cp.id
11303                                 )x)
11304                             )
11305                         ELSE NULL
11306                     END,
11307                     CASE 
11308                         WHEN ('circ' = ANY ($4)) THEN
11309                             XMLELEMENT( name current_circulation,
11310                                 (SELECT XMLAGG(circ) FROM (
11311                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11312                                       FROM  action.circulation
11313                                       WHERE target_copy = cp.id
11314                                             AND checkin_time IS NULL
11315                                 )x)
11316                             )
11317                         ELSE NULL
11318                     END
11319                 )
11320           FROM  serial.unit cp
11321           WHERE id = $1
11322               AND cp.deleted IS FALSE
11323           GROUP BY id, status, location, circ_lib, call_number, create_date,
11324               edit_date, copy_number, circulate, floating, mint_condition,
11325               deposit, ref, holdable, deleted, deposit_amount, price,
11326               barcode, circ_modifier, circ_as_type, opac_visible,
11327               status_changed_time, detailed_contents, sort_key,
11328               summary_contents, cost, age_protect;
11329 $F$ LANGUAGE SQL STABLE;
11330
11331 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$
11332         SELECT  XMLELEMENT(
11333                     name volume,
11334                     XMLATTRIBUTES(
11335                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11336                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
11337                         acn.id AS vol_id, o.shortname AS lib,
11338                         o.opac_visible AS opac_visible,
11339                         deleted, label, label_sortkey, label_class, record
11340                     ),
11341                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
11342                     CASE 
11343                         WHEN ('acp' = ANY ($4)) THEN
11344                             CASE WHEN $6 IS NOT NULL THEN
11345                                 XMLELEMENT( name copies,
11346                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11347                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11348                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11349                                           FROM  asset.copy cp
11350                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
11351                                           WHERE cp.call_number = acn.id
11352                                               AND cp.deleted IS FALSE
11353                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11354                                           LIMIT ($7 -> 'acp')::INT
11355                                           OFFSET ($8 -> 'acp')::INT
11356                                     )x)
11357                                 )
11358                             ELSE
11359                                 XMLELEMENT( name copies,
11360                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11361                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11362                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11363                                           FROM  asset.copy cp
11364                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
11365                                           WHERE cp.call_number = acn.id
11366                                               AND cp.deleted IS FALSE
11367                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11368                                           LIMIT ($7 -> 'acp')::INT
11369                                           OFFSET ($8 -> 'acp')::INT
11370                                     )x)
11371                                 )
11372                             END
11373                         ELSE NULL
11374                     END,
11375                     XMLELEMENT(
11376                         name uris,
11377                         (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)
11378                     ),
11379                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11380                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11381                     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
11382                 ) AS x
11383           FROM  asset.call_number acn
11384                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
11385           WHERE acn.id = $1
11386               AND acn.deleted IS FALSE
11387           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
11388 $F$ LANGUAGE SQL STABLE;
11389
11390 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$
11391         SELECT  XMLELEMENT(
11392                     name call_number_prefix,
11393                     XMLATTRIBUTES(
11394                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11395                         id AS ident,
11396                         label,
11397                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11398                         label_sortkey
11399                     )
11400                 )
11401           FROM  asset.call_number_prefix
11402           WHERE id = $1;
11403 $F$ LANGUAGE SQL STABLE;
11404
11405 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$
11406         SELECT  XMLELEMENT(
11407                     name call_number_suffix,
11408                     XMLATTRIBUTES(
11409                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11410                         id AS ident,
11411                         label,
11412                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11413                         label_sortkey
11414                     )
11415                 )
11416           FROM  asset.call_number_suffix
11417           WHERE id = $1;
11418 $F$ LANGUAGE SQL STABLE;
11419
11420 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$
11421         SELECT  XMLELEMENT(
11422                     name uri,
11423                     XMLATTRIBUTES(
11424                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11425                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
11426                         use_restriction,
11427                         href,
11428                         label
11429                     ),
11430                     CASE 
11431                         WHEN ('acn' = ANY ($4)) THEN
11432                             XMLELEMENT( name copies,
11433                                 (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)
11434                             )
11435                         ELSE NULL
11436                     END
11437                 ) AS x
11438           FROM  asset.uri uri
11439           WHERE uri.id = $1
11440           GROUP BY uri.id, use_restriction, href, label;
11441 $F$ LANGUAGE SQL STABLE;
11442
11443 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$
11444         SELECT  XMLELEMENT(
11445                     name attributes,
11446                     XMLATTRIBUTES(
11447                         CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
11448                         'tag:open-ils.org:U2@mra/' || mra.id AS id,
11449                         'tag:open-ils.org:U2@bre/' || mra.id AS record
11450                     ),
11451                     (SELECT XMLAGG(foo.y)
11452                       FROM (SELECT XMLELEMENT(
11453                                 name field,
11454                                 XMLATTRIBUTES(
11455                                     key AS name,
11456                                     cvm.value AS "coded-value",
11457                                     rad.filter,
11458                                     rad.sorter
11459                                 ),
11460                                 x.value
11461                             )
11462                            FROM EACH(mra.attrs) AS x
11463                                 JOIN config.record_attr_definition rad ON (x.key = rad.name)
11464                                 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
11465                         )foo(y)
11466                     )
11467                 )
11468           FROM  metabib.record_attr mra
11469           WHERE mra.id = $1;
11470 $F$ LANGUAGE SQL STABLE;
11471
11472 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$
11473     SELECT XMLELEMENT(
11474         name circ,
11475         XMLATTRIBUTES(
11476             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11477             'tag:open-ils.org:U2@circ/' || id AS id,
11478             xact_start,
11479             due_date
11480         ),
11481         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,
11482         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
11483     )
11484     FROM action.circulation
11485     WHERE id = $1;
11486 $F$ LANGUAGE SQL STABLE;
11487
11488 /*
11489
11490  -- Some test queries
11491
11492 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11493 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11494 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11495
11496 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>');
11497
11498 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>');
11499 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>');
11500 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>');
11501 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>');
11502
11503 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
11504 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
11505 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
11506 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
11507 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
11508
11509 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
11510 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
11511
11512 */
11513
11514
11515 SELECT evergreen.upgrade_deps_block_check('0692', :eg_version);
11516
11517 INSERT INTO config.org_unit_setting_type
11518     (name, label, description, grp, datatype)
11519     VALUES (
11520         'circ.fines.charge_when_closed',
11521          oils_i18n_gettext(
11522             'circ.fines.charge_when_closed',
11523             'Charge fines on overdue circulations when closed',
11524             'coust',
11525             'label'
11526         ),
11527         oils_i18n_gettext(
11528             'circ.fines.charge_when_closed',
11529             '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.',
11530             'coust',
11531             'description'
11532         ),
11533         'circ',
11534         'bool'
11535     );
11536
11537 SELECT evergreen.upgrade_deps_block_check('0694', :eg_version);
11538
11539 INSERT into config.org_unit_setting_type
11540 ( name, grp, label, description, datatype, fm_class ) VALUES
11541
11542 ( 'ui.patron.edit.au.prefix.require', 'gui',
11543     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11544         'Require prefix field on patron registration',
11545         'coust', 'label'),
11546     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11547         'The prefix field will be required on the patron registration screen.',
11548         'coust', 'description'),
11549     'bool', null)
11550         
11551 ,( 'ui.patron.edit.au.prefix.show', 'gui',
11552     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11553         'Show prefix field on patron registration',
11554         'coust', 'label'),
11555     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11556         '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.',
11557         'coust', 'description'),
11558     'bool', null)
11559
11560 ,( 'ui.patron.edit.au.prefix.suggest', 'gui',
11561     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11562         'Suggest prefix field on patron registration',
11563         'coust', 'label'),
11564     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11565         '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.',
11566         'coust', 'description'),
11567     'bool', null)
11568 ;               
11569
11570
11571 -- Evergreen DB patch 0695.schema.custom_toolbars.sql
11572 --
11573 -- FIXME: insert description of change, if needed
11574 --
11575
11576 -- check whether patch can be applied
11577 SELECT evergreen.upgrade_deps_block_check('0695', :eg_version);
11578
11579 CREATE TABLE actor.toolbar (
11580     id          BIGSERIAL   PRIMARY KEY,
11581     ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
11582     org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
11583     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
11584     label       TEXT        NOT NULL,
11585     layout      TEXT        NOT NULL,
11586     CONSTRAINT only_one_type CHECK (
11587         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
11588         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
11589         (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
11590     ),
11591     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
11592 );
11593 CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
11594 CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
11595 CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
11596
11597 -- this one unrelated to toolbars but is a gap in the upgrade scripts
11598 INSERT INTO permission.perm_list ( id, code, description )
11599     SELECT
11600         522,
11601         'IMPORT_AUTHORITY_MARC',
11602         oils_i18n_gettext(
11603             522,
11604             'Allows a user to create new authority records',
11605             'ppl',
11606             'description'
11607         )
11608     WHERE NOT EXISTS (
11609         SELECT 1
11610         FROM permission.perm_list
11611         WHERE
11612             id = 522
11613     );
11614
11615 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
11616     523,
11617     'ADMIN_TOOLBAR',
11618     oils_i18n_gettext(
11619         523,
11620         'Allows a user to create, edit, and delete custom toolbars',
11621         'ppl',
11622         'description'
11623     )
11624 );
11625
11626 -- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
11627 -- 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';
11628
11629 INSERT INTO actor.toolbar(org,label,layout) VALUES
11630     ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
11631     ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
11632
11633 -- 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 ;
11634
11635 -- Evergreen DB patch 0696.no_plperl.sql
11636 --
11637 -- FIXME: insert description of change, if needed
11638 --
11639
11640 -- check whether patch can be applied
11641 SELECT evergreen.upgrade_deps_block_check('0696', :eg_version);
11642
11643 -- Re-create these as plperlu instead of plperl
11644 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
11645     $_SHARED{"eg_audit_user"} = $_[0];
11646     $_SHARED{"eg_audit_ws"} = $_[1];
11647 $$ LANGUAGE plperlu;
11648
11649 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
11650     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
11651 $$ LANGUAGE plperlu;
11652
11653 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
11654     delete($_SHARED{"eg_audit_user"});
11655     delete($_SHARED{"eg_audit_ws"});
11656 $$ LANGUAGE plperlu;
11657
11658 -- Evergreen DB patch 0697.data.place_currently_unfillable_hold.sql
11659 --
11660 -- FIXME: insert description of change, if needed
11661 --
11662
11663 -- check whether patch can be applied
11664 SELECT evergreen.upgrade_deps_block_check('0697', :eg_version);
11665
11666 -- FIXME: add/check SQL statements to perform the upgrade
11667 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11668  ( 524, 'PLACE_UNFILLABLE_HOLD', oils_i18n_gettext( 524,
11669     'Allows a user to place a hold that cannot currently be filled.', 'ppl', 'description' ));
11670
11671 -- Evergreen DB patch 0698.hold_default_pickup.sql
11672 --
11673 -- FIXME: insert description of change, if needed
11674 --
11675
11676 -- check whether patch can be applied
11677 SELECT evergreen.upgrade_deps_block_check('0698', :eg_version);
11678
11679 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
11680     VALUES ('opac.default_pickup_location', TRUE, 'Default Hold Pickup Location', 'Default location for holds pickup', 'integer');
11681
11682 SELECT evergreen.upgrade_deps_block_check('0699', :eg_version);
11683
11684 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, grp )
11685     VALUES (
11686         'ui.hide_copy_editor_fields',
11687         oils_i18n_gettext(
11688             'ui.hide_copy_editor_fields',
11689             'GUI: Hide these fields within the Item Attribute Editor',
11690             'coust',
11691             'label'
11692         ),
11693         oils_i18n_gettext(
11694             'ui.hide_copy_editor_fields',
11695             'This setting may be best maintained with the dedicated configuration'
11696             || ' interface within the Item Attribute Editor.  However, here it'
11697             || ' shows up as comma separated list of field identifiers to hide.',
11698             'coust',
11699             'description'
11700         ),
11701         'array',
11702         'gui'
11703     );
11704
11705
11706 SELECT evergreen.upgrade_deps_block_check('0700', :eg_version);
11707 SELECT evergreen.upgrade_deps_block_check('0706', :eg_version);
11708 SELECT evergreen.upgrade_deps_block_check('0710', :eg_version);
11709
11710
11711 CREATE OR REPLACE FUNCTION evergreen.could_be_serial_holding_code(TEXT) RETURNS BOOL AS $$
11712     use JSON::XS;
11713     use MARC::Field;
11714
11715     eval {
11716         my $holding_code = (new JSON::XS)->decode(shift);
11717         new MARC::Field('999', @$holding_code);
11718     };  
11719     return $@ ? 0 : 1;
11720 $$ LANGUAGE PLPERLU;
11721
11722 -- This throws away data, but only data that causes breakage anyway.
11723 UPDATE serial.issuance SET holding_code = NULL WHERE NOT could_be_serial_holding_code(holding_code);
11724
11725 -- If we don't do this, we have unprocessed triggers and we can't alter the table
11726 SET CONSTRAINTS serial.issuance_caption_and_pattern_fkey IMMEDIATE;
11727
11728 ALTER TABLE serial.issuance
11729     DROP CONSTRAINT IF EXISTS issuance_holding_code_check;
11730
11731 ALTER TABLE serial.issuance ADD CHECK (holding_code IS NULL OR could_be_serial_holding_code(holding_code));
11732
11733 INSERT INTO config.internal_flag (name, value, enabled) VALUES (
11734     'serial.rematerialize_on_same_holding_code', NULL, FALSE
11735 );
11736
11737 INSERT INTO config.org_unit_setting_type (
11738     name, label, grp, description, datatype
11739 ) VALUES (
11740     'serial.default_display_grouping',
11741     'Default display grouping for serials distributions presented in the OPAC.',
11742     'serial',
11743     'Default display grouping for serials distributions presented in the OPAC. This can be "enum" or "chron".',
11744     'string'
11745 );
11746
11747 ALTER TABLE serial.distribution
11748     ADD COLUMN display_grouping TEXT NOT NULL DEFAULT 'chron'
11749         CHECK (display_grouping IN ('enum', 'chron'));
11750
11751 -- why didn't we just make one summary table in the first place?
11752 CREATE VIEW serial.any_summary AS
11753     SELECT
11754         'basic' AS summary_type, id, distribution,
11755         generated_coverage, textual_holdings, show_generated
11756     FROM serial.basic_summary
11757     UNION
11758     SELECT
11759         'index' AS summary_type, id, distribution,
11760         generated_coverage, textual_holdings, show_generated
11761     FROM serial.index_summary
11762     UNION
11763     SELECT
11764         'supplement' AS summary_type, id, distribution,
11765         generated_coverage, textual_holdings, show_generated
11766     FROM serial.supplement_summary ;
11767
11768
11769 -- Given the IDs of two rows in actor.org_unit, *the second being an ancestor
11770 -- of the first*, return in array form the path from the ancestor to the
11771 -- descendant, with each point in the path being an org_unit ID.  This is
11772 -- useful for sorting org_units by their position in a depth-first (display
11773 -- order) representation of the tree.
11774 --
11775 -- This breaks with the precedent set by actor.org_unit_full_path() and others,
11776 -- and gets the parameters "backwards," but otherwise this function would
11777 -- not be very usable within json_query.
11778 CREATE OR REPLACE FUNCTION actor.org_unit_simple_path(INT, INT)
11779 RETURNS INT[] AS $$
11780     WITH RECURSIVE descendant_depth(id, path) AS (
11781         SELECT  aou.id,
11782                 ARRAY[aou.id]
11783           FROM  actor.org_unit aou
11784                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11785           WHERE aou.id = $2
11786             UNION ALL
11787         SELECT  aou.id,
11788                 dd.path || ARRAY[aou.id]
11789           FROM  actor.org_unit aou
11790                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11791                 JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
11792     ) SELECT dd.path
11793         FROM actor.org_unit aou
11794         JOIN descendant_depth dd USING (id)
11795         WHERE aou.id = $1 ORDER BY dd.path;
11796 $$ LANGUAGE SQL STABLE;
11797
11798 CREATE TABLE serial.materialized_holding_code (
11799     id BIGSERIAL PRIMARY KEY,
11800     issuance INTEGER NOT NULL REFERENCES serial.issuance (id) ON DELETE CASCADE,
11801     subfield CHAR,
11802     value TEXT
11803 );
11804
11805 CREATE OR REPLACE FUNCTION serial.materialize_holding_code() RETURNS TRIGGER
11806 AS $func$ 
11807 use strict;
11808
11809 use MARC::Field;
11810 use JSON::XS;
11811
11812 if (not defined $_TD->{new}{holding_code}) {
11813     elog(WARNING, 'NULL in "holding_code" column of serial.issuance allowed for now, but may not be useful');
11814     return;
11815 }
11816
11817 # Do nothing if holding_code has not changed...
11818
11819 if ($_TD->{new}{holding_code} eq $_TD->{old}{holding_code}) {
11820     # ... unless the following internal flag is set.
11821
11822     my $flag_rv = spi_exec_query(q{
11823         SELECT * FROM config.internal_flag
11824         WHERE name = 'serial.rematerialize_on_same_holding_code' AND enabled
11825     }, 1);
11826     return unless $flag_rv->{processed};
11827 }
11828
11829
11830 my $holding_code = (new JSON::XS)->decode($_TD->{new}{holding_code});
11831
11832 my $field = new MARC::Field('999', @$holding_code); # tag doesnt matter
11833
11834 my $dstmt = spi_prepare(
11835     'DELETE FROM serial.materialized_holding_code WHERE issuance = $1',
11836     'INT'
11837 );
11838 spi_exec_prepared($dstmt, $_TD->{new}{id});
11839
11840 my $istmt = spi_prepare(
11841     q{
11842         INSERT INTO serial.materialized_holding_code (
11843             issuance, subfield, value
11844         ) VALUES ($1, $2, $3)
11845     }, qw{INT CHAR TEXT}
11846 );
11847
11848 foreach ($field->subfields) {
11849     spi_exec_prepared(
11850         $istmt,
11851         $_TD->{new}{id},
11852         $_->[0],
11853         $_->[1]
11854     );
11855 }
11856
11857 return;
11858
11859 $func$ LANGUAGE 'plperlu';
11860
11861 CREATE INDEX assist_holdings_display
11862     ON serial.materialized_holding_code (issuance, subfield);
11863
11864 CREATE TRIGGER materialize_holding_code
11865     AFTER INSERT OR UPDATE ON serial.issuance
11866     FOR EACH ROW EXECUTE PROCEDURE serial.materialize_holding_code() ;
11867
11868 -- starting here, we materialize all existing holding codes.
11869
11870 UPDATE config.internal_flag
11871     SET enabled = TRUE
11872     WHERE name = 'serial.rematerialize_on_same_holding_code';
11873
11874 UPDATE serial.issuance SET holding_code = holding_code;
11875
11876 UPDATE config.internal_flag
11877     SET enabled = FALSE
11878     WHERE name = 'serial.rematerialize_on_same_holding_code';
11879
11880 -- finish holding code materialization process
11881
11882 -- fix up missing holding_code fields from serial.issuance
11883 UPDATE serial.issuance siss
11884     SET holding_type = scap.type
11885     FROM serial.caption_and_pattern scap
11886     WHERE scap.id = siss.caption_and_pattern AND siss.holding_type IS NULL;
11887
11888
11889 -- Evergreen DB patch 0701.schema.patron_stat_category_enhancements.sql
11890 --
11891 -- Enables users to set patron statistical categories as required,
11892 -- whether or not users can input free text for the category value.
11893 -- Enables administrators to set an entry as the default for any
11894 -- given patron statistical category and org unit.
11895 --
11896
11897 -- check whether patch can be applied
11898 SELECT evergreen.upgrade_deps_block_check('0701', :eg_version);
11899
11900 -- New table
11901
11902 CREATE TABLE actor.stat_cat_entry_default (
11903     id              SERIAL  PRIMARY KEY,
11904     stat_cat_entry  INT     NOT NULL REFERENCES actor.stat_cat_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11905     stat_cat        INT     NOT NULL REFERENCES actor.stat_cat (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11906     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11907     CONSTRAINT sced_once_per_owner UNIQUE (stat_cat,owner)
11908 );
11909
11910 COMMENT ON TABLE actor.stat_cat_entry_default IS $$
11911 User Statistical Category Default Entry
11912
11913 A library may choose one of the stat_cat entries to be the
11914 default entry.
11915 $$;
11916
11917 -- Add columns to existing tables
11918
11919 -- Patron stat cat required column
11920 ALTER TABLE actor.stat_cat
11921     ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
11922
11923 -- Patron stat cat allow_freetext column
11924 ALTER TABLE actor.stat_cat
11925     ADD COLUMN allow_freetext BOOL NOT NULL DEFAULT TRUE;
11926
11927 -- Add permissions
11928
11929 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11930     ( 525, 'CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 525, 
11931         'User may set a default entry in a patron statistical category', 'ppl', 'description' )),
11932     ( 526, 'UPDATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 526, 
11933         'User may reset a default entry in a patron statistical category', 'ppl', 'description' )),
11934     ( 527, 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 527, 
11935         'User may unset a default entry in a patron statistical category', 'ppl', 'description' ));
11936
11937 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
11938     SELECT
11939         pgt.id, perm.id, aout.depth, TRUE
11940     FROM
11941         permission.grp_tree pgt,
11942         permission.perm_list perm,
11943         actor.org_unit_type aout
11944     WHERE
11945         pgt.name = 'Circulation Administrator' AND
11946         aout.name = 'System' AND
11947         perm.code IN ('CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT');
11948
11949
11950 SELECT evergreen.upgrade_deps_block_check('0702', :eg_version);
11951
11952 INSERT INTO config.global_flag (name, enabled, label) 
11953     VALUES (
11954         'opac.org_unit.non_inherited_visibility',
11955         FALSE,
11956         oils_i18n_gettext(
11957             'opac.org_unit.non_inherited_visibility',
11958             'Org Units Do Not Inherit Visibility',
11959             'cgf',
11960             'label'
11961         )
11962     );
11963
11964 CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
11965
11966 CREATE TABLE actor.org_unit_custom_tree (
11967     id              SERIAL  PRIMARY KEY,
11968     active          BOOLEAN DEFAULT FALSE,
11969     purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
11970 );
11971
11972 CREATE TABLE actor.org_unit_custom_tree_node (
11973     id              SERIAL  PRIMARY KEY,
11974     tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
11975         org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
11976         parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
11977     sibling_order   INTEGER NOT NULL DEFAULT 0,
11978     CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
11979 );
11980     
11981
11982 /* UNDO
11983 BEGIN;
11984 DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
11985 DROP TABLE actor.org_unit_custom_tree_node;
11986 DROP TABLE actor.org_unit_custom_tree;
11987 DROP TYPE actor.org_unit_custom_tree_purpose;
11988 COMMIT;
11989 */
11990
11991 -- Evergreen DB patch 0704.schema.query_parser_fts.sql
11992 --
11993 -- Add pref_ou query filter for preferred library searching
11994 --
11995
11996 -- check whether patch can be applied
11997 SELECT evergreen.upgrade_deps_block_check('0704', :eg_version);
11998
11999 -- Create the new 11-parameter function, featuring param_pref_ou
12000 CREATE OR REPLACE FUNCTION search.query_parser_fts (
12001
12002     param_search_ou INT,
12003     param_depth     INT,
12004     param_query     TEXT,
12005     param_statuses  INT[],
12006     param_locations INT[],
12007     param_offset    INT,
12008     param_check     INT,
12009     param_limit     INT,
12010     metarecord      BOOL,
12011     staff           BOOL,
12012     param_pref_ou   INT DEFAULT NULL
12013 ) RETURNS SETOF search.search_result AS $func$
12014 DECLARE
12015
12016     current_res         search.search_result%ROWTYPE;
12017     search_org_list     INT[];
12018     luri_org_list       INT[];
12019     tmp_int_list        INT[];
12020
12021     check_limit         INT;
12022     core_limit          INT;
12023     core_offset         INT;
12024     tmp_int             INT;
12025
12026     core_result         RECORD;
12027     core_cursor         REFCURSOR;
12028     core_rel_query      TEXT;
12029
12030     total_count         INT := 0;
12031     check_count         INT := 0;
12032     deleted_count       INT := 0;
12033     visible_count       INT := 0;
12034     excluded_count      INT := 0;
12035
12036 BEGIN
12037
12038     check_limit := COALESCE( param_check, 1000 );
12039     core_limit  := COALESCE( param_limit, 25000 );
12040     core_offset := COALESCE( param_offset, 0 );
12041
12042     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
12043
12044     IF param_search_ou > 0 THEN
12045         IF param_depth IS NOT NULL THEN
12046             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
12047         ELSE
12048             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
12049         END IF;
12050
12051         SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
12052
12053     ELSIF param_search_ou < 0 THEN
12054         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
12055
12056         FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
12057             SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
12058             luri_org_list := luri_org_list || tmp_int_list;
12059         END LOOP;
12060
12061         SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
12062
12063     ELSIF param_search_ou = 0 THEN
12064         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
12065     END IF;
12066
12067     IF param_pref_ou IS NOT NULL THEN
12068         SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors(param_pref_ou);
12069         luri_org_list := luri_org_list || tmp_int_list;
12070     END IF;
12071
12072     OPEN core_cursor FOR EXECUTE param_query;
12073
12074     LOOP
12075
12076         FETCH core_cursor INTO core_result;
12077         EXIT WHEN NOT FOUND;
12078         EXIT WHEN total_count >= core_limit;
12079
12080         total_count := total_count + 1;
12081
12082         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
12083
12084         check_count := check_count + 1;
12085
12086         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12087         IF NOT FOUND THEN
12088             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
12089             deleted_count := deleted_count + 1;
12090             CONTINUE;
12091         END IF;
12092
12093         PERFORM 1
12094           FROM  biblio.record_entry b
12095                 JOIN config.bib_source s ON (b.source = s.id)
12096           WHERE s.transcendant
12097                 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12098
12099         IF FOUND THEN
12100             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
12101             visible_count := visible_count + 1;
12102
12103             current_res.id = core_result.id;
12104             current_res.rel = core_result.rel;
12105
12106             tmp_int := 1;
12107             IF metarecord THEN
12108                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12109             END IF;
12110
12111             IF tmp_int = 1 THEN
12112                 current_res.record = core_result.records[1];
12113             ELSE
12114                 current_res.record = NULL;
12115             END IF;
12116
12117             RETURN NEXT current_res;
12118
12119             CONTINUE;
12120         END IF;
12121
12122         PERFORM 1
12123           FROM  asset.call_number cn
12124                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
12125                 JOIN asset.uri uri ON (map.uri = uri.id)
12126           WHERE NOT cn.deleted
12127                 AND cn.label = '##URI##'
12128                 AND uri.active
12129                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
12130                 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12131                 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
12132           LIMIT 1;
12133
12134         IF FOUND THEN
12135             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
12136             visible_count := visible_count + 1;
12137
12138             current_res.id = core_result.id;
12139             current_res.rel = core_result.rel;
12140
12141             tmp_int := 1;
12142             IF metarecord THEN
12143                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12144             END IF;
12145
12146             IF tmp_int = 1 THEN
12147                 current_res.record = core_result.records[1];
12148             ELSE
12149                 current_res.record = NULL;
12150             END IF;
12151
12152             RETURN NEXT current_res;
12153
12154             CONTINUE;
12155         END IF;
12156
12157         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
12158
12159             PERFORM 1
12160               FROM  asset.call_number cn
12161                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12162               WHERE NOT cn.deleted
12163                     AND NOT cp.deleted
12164                     AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12165                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12166                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12167               LIMIT 1;
12168
12169             IF NOT FOUND THEN
12170                 PERFORM 1
12171                   FROM  biblio.peer_bib_copy_map pr
12172                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12173                   WHERE NOT cp.deleted
12174                         AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12175                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12176                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12177                   LIMIT 1;
12178
12179                 IF NOT FOUND THEN
12180                 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
12181                     excluded_count := excluded_count + 1;
12182                     CONTINUE;
12183                 END IF;
12184             END IF;
12185
12186         END IF;
12187
12188         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
12189
12190             PERFORM 1
12191               FROM  asset.call_number cn
12192                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12193               WHERE NOT cn.deleted
12194                     AND NOT cp.deleted
12195                     AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12196                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12197                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12198               LIMIT 1;
12199
12200             IF NOT FOUND THEN
12201                 PERFORM 1
12202                   FROM  biblio.peer_bib_copy_map pr
12203                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12204                   WHERE NOT cp.deleted
12205                         AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12206                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12207                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12208                   LIMIT 1;
12209
12210                 IF NOT FOUND THEN
12211                     -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
12212                     excluded_count := excluded_count + 1;
12213                     CONTINUE;
12214                 END IF;
12215             END IF;
12216
12217         END IF;
12218
12219         IF staff IS NULL OR NOT staff THEN
12220
12221             PERFORM 1
12222               FROM  asset.opac_visible_copies
12223               WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12224                     AND record IN ( SELECT * FROM unnest( core_result.records ) )
12225               LIMIT 1;
12226
12227             IF NOT FOUND THEN
12228                 PERFORM 1
12229                   FROM  biblio.peer_bib_copy_map pr
12230                         JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
12231                   WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12232                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12233                   LIMIT 1;
12234
12235                 IF NOT FOUND THEN
12236
12237                     -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12238                     excluded_count := excluded_count + 1;
12239                     CONTINUE;
12240                 END IF;
12241             END IF;
12242
12243         ELSE
12244
12245             PERFORM 1
12246               FROM  asset.call_number cn
12247                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12248               WHERE NOT cn.deleted
12249                     AND NOT cp.deleted
12250                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12251                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12252               LIMIT 1;
12253
12254             IF NOT FOUND THEN
12255
12256                 PERFORM 1
12257                   FROM  biblio.peer_bib_copy_map pr
12258                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12259                   WHERE NOT cp.deleted
12260                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12261                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12262                   LIMIT 1;
12263
12264                 IF NOT FOUND THEN
12265
12266                     PERFORM 1
12267                       FROM  asset.call_number cn
12268                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12269                       WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12270                             AND NOT cp.deleted
12271                       LIMIT 1;
12272
12273                     IF FOUND THEN
12274                         -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12275                         excluded_count := excluded_count + 1;
12276                         CONTINUE;
12277                     END IF;
12278                 END IF;
12279
12280             END IF;
12281
12282         END IF;
12283
12284         visible_count := visible_count + 1;
12285
12286         current_res.id = core_result.id;
12287         current_res.rel = core_result.rel;
12288
12289         tmp_int := 1;
12290         IF metarecord THEN
12291             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12292         END IF;
12293
12294         IF tmp_int = 1 THEN
12295             current_res.record = core_result.records[1];
12296         ELSE
12297             current_res.record = NULL;
12298         END IF;
12299
12300         RETURN NEXT current_res;
12301
12302         IF visible_count % 1000 = 0 THEN
12303             -- RAISE NOTICE ' % visible so far ... ', visible_count;
12304         END IF;
12305
12306     END LOOP;
12307
12308     current_res.id = NULL;
12309     current_res.rel = NULL;
12310     current_res.record = NULL;
12311     current_res.total = total_count;
12312     current_res.checked = check_count;
12313     current_res.deleted = deleted_count;
12314     current_res.visible = visible_count;
12315     current_res.excluded = excluded_count;
12316
12317     CLOSE core_cursor;
12318
12319     RETURN NEXT current_res;
12320
12321 END;
12322 $func$ LANGUAGE PLPGSQL;
12323
12324 -- Drop the old 10-parameter function
12325 DROP FUNCTION IF EXISTS search.query_parser_fts (
12326     INT, INT, TEXT, INT[], INT[], INT, INT, INT, BOOL, BOOL
12327 );
12328
12329 -- Evergreen DB patch 0705.data.custom-org-tree-perms.sql
12330 --
12331 -- check whether patch can be applied
12332 SELECT evergreen.upgrade_deps_block_check('0705', :eg_version);
12333
12334 INSERT INTO permission.perm_list (id, code, description)
12335     VALUES (
12336         528,
12337         'ADMIN_ORG_UNIT_CUSTOM_TREE',
12338         oils_i18n_gettext(
12339             528,
12340             'User may update custom org unit trees',
12341             'ppl',
12342             'description'
12343         )
12344     );
12345
12346 -- Evergreen DB patch 0707.schema.acq-vandelay-integration.sql
12347
12348 SELECT evergreen.upgrade_deps_block_check('0707', :eg_version);
12349
12350 -- seed data --
12351
12352 INSERT INTO permission.perm_list ( id, code, description )
12353     VALUES (
12354         529,
12355         'ADMIN_IMPORT_MATCH_SET',
12356         oils_i18n_gettext(
12357             529,
12358             'Allows a user to create/retrieve/update/delete vandelay match sets',
12359             'ppl',
12360             'description'
12361         )
12362     ), (
12363         530,
12364         'VIEW_IMPORT_MATCH_SET',
12365         oils_i18n_gettext(
12366             530,
12367             'Allows a user to view vandelay match sets',
12368             'ppl',
12369             'description'
12370         )
12371     );
12372
12373 -- This upgrade script fixed a typo in a previous one. It was corrected in the proper place in this file.
12374 -- Still, record the fact it has been "applied".
12375 SELECT evergreen.upgrade_deps_block_check('0708', :eg_version);
12376
12377 -- Evergreen DB patch 0709.data.misc_missing_perms.sql
12378
12379 SELECT evergreen.upgrade_deps_block_check('0709', :eg_version);
12380
12381 INSERT INTO permission.perm_list ( id, code, description ) 
12382     VALUES ( 
12383         531, 
12384         'ADMIN_ADDRESS_ALERT',
12385         oils_i18n_gettext( 
12386             531,
12387             'Allows a user to create/retrieve/update/delete address alerts',
12388             'ppl', 
12389             'description' 
12390         )
12391     ), ( 
12392         532, 
12393         'VIEW_ADDRESS_ALERT',
12394         oils_i18n_gettext( 
12395             532,
12396             'Allows a user to view address alerts',
12397             'ppl', 
12398             'description' 
12399         )
12400     ), ( 
12401         533, 
12402         'ADMIN_COPY_LOCATION_GROUP',
12403         oils_i18n_gettext( 
12404             533,
12405             'Allows a user to create/retrieve/update/delete copy location groups',
12406             'ppl', 
12407             'description' 
12408         )
12409     ), ( 
12410         534, 
12411         'ADMIN_USER_ACTIVITY_TYPE',
12412         oils_i18n_gettext( 
12413             534,
12414             'Allows a user to create/retrieve/update/delete user activity types',
12415             'ppl', 
12416             'description' 
12417         )
12418     );
12419
12420 COMMIT;
12421
12422 \qecho ************************************************************************
12423 \qecho The following transaction, wrapping upgrade 0672, may take a while.  If
12424 \qecho it takes an unduly long time, try it outside of a transaction.
12425 \qecho ************************************************************************
12426
12427 BEGIN;
12428
12429 -- Evergreen DB patch 0672.fix-nonfiling-titles.sql
12430 --
12431 -- Titles that begin with non-filing articles using apostrophes
12432 -- (for example, "L'armée") get spaces injected between the article
12433 -- and the subsequent text, which then breaks searching for titles
12434 -- beginning with those articles.
12435 --
12436 -- This patch adds a nonfiling title element to MODS32 that can then
12437 -- be used to retrieve the title proper without affecting the spaces
12438 -- in the title. It's what we want, what we really really want, for
12439 -- title searches.
12440 --
12441
12442
12443 -- check whether patch can be applied
12444 SELECT evergreen.upgrade_deps_block_check('0672', :eg_version);
12445
12446 -- Update the XPath definition before the titleNonfiling element exists;
12447 -- but are you really going to read through the whole XSL below before
12448 -- seeing this important bit?
12449 UPDATE config.metabib_field
12450     SET xpath = $$//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]$$,
12451         format = 'mods32'
12452     WHERE field_class = 'title' AND name = 'proper';
12453
12454 UPDATE config.xml_transform SET xslt=$$<?xml version="1.0" encoding="UTF-8"?>
12455 <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">
12456         <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
12457 <!--
12458 Revision 1.14 - Fixed template isValid and fields 010, 020, 022, 024, 028, and 037 to output additional identifier elements 
12459   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
12460
12461 Revision 1.13 - Changed order of output under cartographics to reflect schema  2006/11/28 tmee
12462         
12463 Revision 1.12 - Updated to reflect MODS 3.2 Mapping  2006/10/11 tmee
12464                 
12465 Revision 1.11 - The attribute objectPart moved from <languageTerm> to <language>
12466       2006/04/08  jrad
12467
12468 Revision 1.10 MODS 3.1 revisions to language and classification elements  
12469                                 (plus ability to find marc:collection embedded in wrapper elements such as SRU zs: wrappers)
12470                                 2006/02/06  ggar
12471
12472 Revision 1.9 subfield $y was added to field 242 2004/09/02 10:57 jrad
12473
12474 Revision 1.8 Subject chopPunctuation expanded and attribute fixes 2004/08/12 jrad
12475
12476 Revision 1.7 2004/03/25 08:29 jrad
12477
12478 Revision 1.6 various validation fixes 2004/02/20 ntra
12479
12480 Revision 1.5  2003/10/02 16:18:58  ntra
12481 MODS2 to MODS3 updates, language unstacking and 
12482 de-duping, chopPunctuation expanded
12483
12484 Revision 1.3  2003/04/03 00:07:19  ntra
12485 Revision 1.3 Additional Changes not related to MODS Version 2.0 by ntra
12486
12487 Revision 1.2  2003/03/24 19:37:42  ckeith
12488 Added Log Comment
12489
12490 -->
12491         <xsl:template match="/">
12492                 <xsl:choose>
12493                         <xsl:when test="//marc:collection">
12494                                 <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">
12495                                         <xsl:for-each select="//marc:collection/marc:record">
12496                                                 <mods version="3.2">
12497                                                         <xsl:call-template name="marcRecord"/>
12498                                                 </mods>
12499                                         </xsl:for-each>
12500                                 </modsCollection>
12501                         </xsl:when>
12502                         <xsl:otherwise>
12503                                 <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">
12504                                         <xsl:for-each select="//marc:record">
12505                                                 <xsl:call-template name="marcRecord"/>
12506                                         </xsl:for-each>
12507                                 </mods>
12508                         </xsl:otherwise>
12509                 </xsl:choose>
12510         </xsl:template>
12511         <xsl:template name="marcRecord">
12512                 <xsl:variable name="leader" select="marc:leader"/>
12513                 <xsl:variable name="leader6" select="substring($leader,7,1)"/>
12514                 <xsl:variable name="leader7" select="substring($leader,8,1)"/>
12515                 <xsl:variable name="controlField008" select="marc:controlfield[@tag='008']"/>
12516                 <xsl:variable name="typeOf008">
12517                         <xsl:choose>
12518                                 <xsl:when test="$leader6='a'">
12519                                         <xsl:choose>
12520                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">BK</xsl:when>
12521                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">SE</xsl:when>
12522                                         </xsl:choose>
12523                                 </xsl:when>
12524                                 <xsl:when test="$leader6='t'">BK</xsl:when>
12525                                 <xsl:when test="$leader6='p'">MM</xsl:when>
12526                                 <xsl:when test="$leader6='m'">CF</xsl:when>
12527                                 <xsl:when test="$leader6='e' or $leader6='f'">MP</xsl:when>
12528                                 <xsl:when test="$leader6='g' or $leader6='k' or $leader6='o' or $leader6='r'">VM</xsl:when>
12529                                 <xsl:when test="$leader6='c' or $leader6='d' or $leader6='i' or $leader6='j'">MU</xsl:when>
12530                         </xsl:choose>
12531                 </xsl:variable>
12532                 <xsl:for-each select="marc:datafield[@tag='245']">
12533                         <titleInfo>
12534                                 <xsl:variable name="title">
12535                                         <xsl:choose>
12536                                                 <xsl:when test="marc:subfield[@code='b']">
12537                                                         <xsl:call-template name="specialSubfieldSelect">
12538                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12539                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12540                                                         </xsl:call-template>
12541                                                 </xsl:when>
12542                                                 <xsl:otherwise>
12543                                                         <xsl:call-template name="subfieldSelect">
12544                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12545                                                         </xsl:call-template>
12546                                                 </xsl:otherwise>
12547                                         </xsl:choose>
12548                                 </xsl:variable>
12549                                 <xsl:variable name="titleChop">
12550                                         <xsl:call-template name="chopPunctuation">
12551                                                 <xsl:with-param name="chopString">
12552                                                         <xsl:value-of select="$title"/>
12553                                                 </xsl:with-param>
12554                                         </xsl:call-template>
12555                                 </xsl:variable>
12556                                 <xsl:choose>
12557                                         <xsl:when test="@ind2>0">
12558                                                 <nonSort>
12559                                                         <xsl:value-of select="substring($titleChop,1,@ind2)"/>
12560                                                 </nonSort>
12561                                                 <title>
12562                                                         <xsl:value-of select="substring($titleChop,@ind2+1)"/>
12563                                                 </title>
12564                                         </xsl:when>
12565                                         <xsl:otherwise>
12566                                                 <title>
12567                                                         <xsl:value-of select="$titleChop"/>
12568                                                 </title>
12569                                         </xsl:otherwise>
12570                                 </xsl:choose>
12571                                 <xsl:if test="marc:subfield[@code='b']">
12572                                         <subTitle>
12573                                                 <xsl:call-template name="chopPunctuation">
12574                                                         <xsl:with-param name="chopString">
12575                                                                 <xsl:call-template name="specialSubfieldSelect">
12576                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12577                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12578                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12579                                                                 </xsl:call-template>
12580                                                         </xsl:with-param>
12581                                                 </xsl:call-template>
12582                                         </subTitle>
12583                                 </xsl:if>
12584                                 <xsl:call-template name="part"></xsl:call-template>
12585                         </titleInfo>
12586                         <!-- A form of title that ignores non-filing characters; useful
12587                                  for not converting "L'Oreal" into "L' Oreal" at index time -->
12588                         <titleNonfiling>
12589                                 <xsl:variable name="title">
12590                                         <xsl:choose>
12591                                                 <xsl:when test="marc:subfield[@code='b']">
12592                                                         <xsl:call-template name="specialSubfieldSelect">
12593                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12594                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12595                                                         </xsl:call-template>
12596                                                 </xsl:when>
12597                                                 <xsl:otherwise>
12598                                                         <xsl:call-template name="subfieldSelect">
12599                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12600                                                         </xsl:call-template>
12601                                                 </xsl:otherwise>
12602                                         </xsl:choose>
12603                                 </xsl:variable>
12604                                 <title>
12605                                         <xsl:value-of select="$title"/>
12606                                 </title>
12607                                 <xsl:if test="marc:subfield[@code='b']">
12608                                         <subTitle>
12609                                                 <xsl:call-template name="chopPunctuation">
12610                                                         <xsl:with-param name="chopString">
12611                                                                 <xsl:call-template name="specialSubfieldSelect">
12612                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12613                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12614                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12615                                                                 </xsl:call-template>
12616                                                         </xsl:with-param>
12617                                                 </xsl:call-template>
12618                                         </subTitle>
12619                                 </xsl:if>
12620                                 <xsl:call-template name="part"></xsl:call-template>
12621                         </titleNonfiling>
12622                 </xsl:for-each>
12623                 <xsl:for-each select="marc:datafield[@tag='210']">
12624                         <titleInfo type="abbreviated">
12625                                 <title>
12626                                         <xsl:call-template name="chopPunctuation">
12627                                                 <xsl:with-param name="chopString">
12628                                                         <xsl:call-template name="subfieldSelect">
12629                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12630                                                         </xsl:call-template>
12631                                                 </xsl:with-param>
12632                                         </xsl:call-template>
12633                                 </title>
12634                                 <xsl:call-template name="subtitle"/>
12635                         </titleInfo>
12636                 </xsl:for-each>
12637                 <xsl:for-each select="marc:datafield[@tag='242']">
12638                         <titleInfo type="translated">
12639                                 <!--09/01/04 Added subfield $y-->
12640                                 <xsl:for-each select="marc:subfield[@code='y']">
12641                                         <xsl:attribute name="lang">
12642                                                 <xsl:value-of select="text()"/>
12643                                         </xsl:attribute>
12644                                 </xsl:for-each>
12645                                 <title>
12646                                         <xsl:call-template name="chopPunctuation">
12647                                                 <xsl:with-param name="chopString">
12648                                                         <xsl:call-template name="subfieldSelect">
12649                                                                 <!-- 1/04 removed $h, b -->
12650                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12651                                                         </xsl:call-template>
12652                                                 </xsl:with-param>
12653                                         </xsl:call-template>
12654                                 </title>
12655                                 <!-- 1/04 fix -->
12656                                 <xsl:call-template name="subtitle"/>
12657                                 <xsl:call-template name="part"/>
12658                         </titleInfo>
12659                 </xsl:for-each>
12660                 <xsl:for-each select="marc:datafield[@tag='246']">
12661                         <titleInfo type="alternative">
12662                                 <xsl:for-each select="marc:subfield[@code='i']">
12663                                         <xsl:attribute name="displayLabel">
12664                                                 <xsl:value-of select="text()"/>
12665                                         </xsl:attribute>
12666                                 </xsl:for-each>
12667                                 <title>
12668                                         <xsl:call-template name="chopPunctuation">
12669                                                 <xsl:with-param name="chopString">
12670                                                         <xsl:call-template name="subfieldSelect">
12671                                                                 <!-- 1/04 removed $h, $b -->
12672                                                                 <xsl:with-param name="codes">af</xsl:with-param>
12673                                                         </xsl:call-template>
12674                                                 </xsl:with-param>
12675                                         </xsl:call-template>
12676                                 </title>
12677                                 <xsl:call-template name="subtitle"/>
12678                                 <xsl:call-template name="part"/>
12679                         </titleInfo>
12680                 </xsl:for-each>
12681                 <xsl:for-each select="marc:datafield[@tag='130']|marc:datafield[@tag='240']|marc:datafield[@tag='730'][@ind2!='2']">
12682                         <titleInfo type="uniform">
12683                                 <title>
12684                                         <xsl:variable name="str">
12685                                                 <xsl:for-each select="marc:subfield">
12686                                                         <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'])))">
12687                                                                 <xsl:value-of select="text()"/>
12688                                                                 <xsl:text> </xsl:text>
12689                                                         </xsl:if>
12690                                                 </xsl:for-each>
12691                                         </xsl:variable>
12692                                         <xsl:call-template name="chopPunctuation">
12693                                                 <xsl:with-param name="chopString">
12694                                                         <xsl:value-of select="substring($str,1,string-length($str)-1)"/>
12695                                                 </xsl:with-param>
12696                                         </xsl:call-template>
12697                                 </title>
12698                                 <xsl:call-template name="part"/>
12699                         </titleInfo>
12700                 </xsl:for-each>
12701                 <xsl:for-each select="marc:datafield[@tag='740'][@ind2!='2']">
12702                         <titleInfo type="alternative">
12703                                 <title>
12704                                         <xsl:call-template name="chopPunctuation">
12705                                                 <xsl:with-param name="chopString">
12706                                                         <xsl:call-template name="subfieldSelect">
12707                                                                 <xsl:with-param name="codes">ah</xsl:with-param>
12708                                                         </xsl:call-template>
12709                                                 </xsl:with-param>
12710                                         </xsl:call-template>
12711                                 </title>
12712                                 <xsl:call-template name="part"/>
12713                         </titleInfo>
12714                 </xsl:for-each>
12715                 <xsl:for-each select="marc:datafield[@tag='100']">
12716                         <name type="personal">
12717                                 <xsl:call-template name="nameABCDQ"/>
12718                                 <xsl:call-template name="affiliation"/>
12719                                 <role>
12720                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12721                                 </role>
12722                                 <xsl:call-template name="role"/>
12723                         </name>
12724                 </xsl:for-each>
12725                 <xsl:for-each select="marc:datafield[@tag='110']">
12726                         <name type="corporate">
12727                                 <xsl:call-template name="nameABCDN"/>
12728                                 <role>
12729                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12730                                 </role>
12731                                 <xsl:call-template name="role"/>
12732                         </name>
12733                 </xsl:for-each>
12734                 <xsl:for-each select="marc:datafield[@tag='111']">
12735                         <name type="conference">
12736                                 <xsl:call-template name="nameACDEQ"/>
12737                                 <role>
12738                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12739                                 </role>
12740                                 <xsl:call-template name="role"/>
12741                         </name>
12742                 </xsl:for-each>
12743                 <xsl:for-each select="marc:datafield[@tag='700'][not(marc:subfield[@code='t'])]">
12744                         <name type="personal">
12745                                 <xsl:call-template name="nameABCDQ"/>
12746                                 <xsl:call-template name="affiliation"/>
12747                                 <xsl:call-template name="role"/>
12748                         </name>
12749                 </xsl:for-each>
12750                 <xsl:for-each select="marc:datafield[@tag='710'][not(marc:subfield[@code='t'])]">
12751                         <name type="corporate">
12752                                 <xsl:call-template name="nameABCDN"/>
12753                                 <xsl:call-template name="role"/>
12754                         </name>
12755                 </xsl:for-each>
12756                 <xsl:for-each select="marc:datafield[@tag='711'][not(marc:subfield[@code='t'])]">
12757                         <name type="conference">
12758                                 <xsl:call-template name="nameACDEQ"/>
12759                                 <xsl:call-template name="role"/>
12760                         </name>
12761                 </xsl:for-each>
12762                 <xsl:for-each select="marc:datafield[@tag='720'][not(marc:subfield[@code='t'])]">
12763                         <name>
12764                                 <xsl:if test="@ind1=1">
12765                                         <xsl:attribute name="type">
12766                                                 <xsl:text>personal</xsl:text>
12767                                         </xsl:attribute>
12768                                 </xsl:if>
12769                                 <namePart>
12770                                         <xsl:value-of select="marc:subfield[@code='a']"/>
12771                                 </namePart>
12772                                 <xsl:call-template name="role"/>
12773                         </name>
12774                 </xsl:for-each>
12775                 <typeOfResource>
12776                         <xsl:if test="$leader7='c'">
12777                                 <xsl:attribute name="collection">yes</xsl:attribute>
12778                         </xsl:if>
12779                         <xsl:if test="$leader6='d' or $leader6='f' or $leader6='p' or $leader6='t'">
12780                                 <xsl:attribute name="manuscript">yes</xsl:attribute>
12781                         </xsl:if>
12782                         <xsl:choose>
12783                                 <xsl:when test="$leader6='a' or $leader6='t'">text</xsl:when>
12784                                 <xsl:when test="$leader6='e' or $leader6='f'">cartographic</xsl:when>
12785                                 <xsl:when test="$leader6='c' or $leader6='d'">notated music</xsl:when>
12786                                 <xsl:when test="$leader6='i'">sound recording-nonmusical</xsl:when>
12787                                 <xsl:when test="$leader6='j'">sound recording-musical</xsl:when>
12788                                 <xsl:when test="$leader6='k'">still image</xsl:when>
12789                                 <xsl:when test="$leader6='g'">moving image</xsl:when>
12790                                 <xsl:when test="$leader6='r'">three dimensional object</xsl:when>
12791                                 <xsl:when test="$leader6='m'">software, multimedia</xsl:when>
12792                                 <xsl:when test="$leader6='p'">mixed material</xsl:when>
12793                         </xsl:choose>
12794                 </typeOfResource>
12795                 <xsl:if test="substring($controlField008,26,1)='d'">
12796                         <genre authority="marc">globe</genre>
12797                 </xsl:if>
12798                 <xsl:if test="marc:controlfield[@tag='007'][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
12799                         <genre authority="marc">remote sensing image</genre>
12800                 </xsl:if>
12801                 <xsl:if test="$typeOf008='MP'">
12802                         <xsl:variable name="controlField008-25" select="substring($controlField008,26,1)"></xsl:variable>
12803                         <xsl:choose>
12804                                 <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']">
12805                                         <genre authority="marc">map</genre>
12806                                 </xsl:when>
12807                                 <xsl:when test="$controlField008-25='e' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
12808                                         <genre authority="marc">atlas</genre>
12809                                 </xsl:when>
12810                         </xsl:choose>
12811                 </xsl:if>
12812                 <xsl:if test="$typeOf008='SE'">
12813                         <xsl:variable name="controlField008-21" select="substring($controlField008,22,1)"></xsl:variable>
12814                         <xsl:choose>
12815                                 <xsl:when test="$controlField008-21='d'">
12816                                         <genre authority="marc">database</genre>
12817                                 </xsl:when>
12818                                 <xsl:when test="$controlField008-21='l'">
12819                                         <genre authority="marc">loose-leaf</genre>
12820                                 </xsl:when>
12821                                 <xsl:when test="$controlField008-21='m'">
12822                                         <genre authority="marc">series</genre>
12823                                 </xsl:when>
12824                                 <xsl:when test="$controlField008-21='n'">
12825                                         <genre authority="marc">newspaper</genre>
12826                                 </xsl:when>
12827                                 <xsl:when test="$controlField008-21='p'">
12828                                         <genre authority="marc">periodical</genre>
12829                                 </xsl:when>
12830                                 <xsl:when test="$controlField008-21='w'">
12831                                         <genre authority="marc">web site</genre>
12832                                 </xsl:when>
12833                         </xsl:choose>
12834                 </xsl:if>
12835                 <xsl:if test="$typeOf008='BK' or $typeOf008='SE'">
12836                         <xsl:variable name="controlField008-24" select="substring($controlField008,25,4)"></xsl:variable>
12837                         <xsl:choose>
12838                                 <xsl:when test="contains($controlField008-24,'a')">
12839                                         <genre authority="marc">abstract or summary</genre>
12840                                 </xsl:when>
12841                                 <xsl:when test="contains($controlField008-24,'b')">
12842                                         <genre authority="marc">bibliography</genre>
12843                                 </xsl:when>
12844                                 <xsl:when test="contains($controlField008-24,'c')">
12845                                         <genre authority="marc">catalog</genre>
12846                                 </xsl:when>
12847                                 <xsl:when test="contains($controlField008-24,'d')">
12848                                         <genre authority="marc">dictionary</genre>
12849                                 </xsl:when>
12850                                 <xsl:when test="contains($controlField008-24,'e')">
12851                                         <genre authority="marc">encyclopedia</genre>
12852                                 </xsl:when>
12853                                 <xsl:when test="contains($controlField008-24,'f')">
12854                                         <genre authority="marc">handbook</genre>
12855                                 </xsl:when>
12856                                 <xsl:when test="contains($controlField008-24,'g')">
12857                                         <genre authority="marc">legal article</genre>
12858                                 </xsl:when>
12859                                 <xsl:when test="contains($controlField008-24,'i')">
12860                                         <genre authority="marc">index</genre>
12861                                 </xsl:when>
12862                                 <xsl:when test="contains($controlField008-24,'k')">
12863                                         <genre authority="marc">discography</genre>
12864                                 </xsl:when>
12865                                 <xsl:when test="contains($controlField008-24,'l')">
12866                                         <genre authority="marc">legislation</genre>
12867                                 </xsl:when>
12868                                 <xsl:when test="contains($controlField008-24,'m')">
12869                                         <genre authority="marc">theses</genre>
12870                                 </xsl:when>
12871                                 <xsl:when test="contains($controlField008-24,'n')">
12872                                         <genre authority="marc">survey of literature</genre>
12873                                 </xsl:when>
12874                                 <xsl:when test="contains($controlField008-24,'o')">
12875                                         <genre authority="marc">review</genre>
12876                                 </xsl:when>
12877                                 <xsl:when test="contains($controlField008-24,'p')">
12878                                         <genre authority="marc">programmed text</genre>
12879                                 </xsl:when>
12880                                 <xsl:when test="contains($controlField008-24,'q')">
12881                                         <genre authority="marc">filmography</genre>
12882                                 </xsl:when>
12883                                 <xsl:when test="contains($controlField008-24,'r')">
12884                                         <genre authority="marc">directory</genre>
12885                                 </xsl:when>
12886                                 <xsl:when test="contains($controlField008-24,'s')">
12887                                         <genre authority="marc">statistics</genre>
12888                                 </xsl:when>
12889                                 <xsl:when test="contains($controlField008-24,'t')">
12890                                         <genre authority="marc">technical report</genre>
12891                                 </xsl:when>
12892                                 <xsl:when test="contains($controlField008-24,'v')">
12893                                         <genre authority="marc">legal case and case notes</genre>
12894                                 </xsl:when>
12895                                 <xsl:when test="contains($controlField008-24,'w')">
12896                                         <genre authority="marc">law report or digest</genre>
12897                                 </xsl:when>
12898                                 <xsl:when test="contains($controlField008-24,'z')">
12899                                         <genre authority="marc">treaty</genre>
12900                                 </xsl:when>
12901                         </xsl:choose>
12902                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
12903                         <xsl:choose>
12904                                 <xsl:when test="$controlField008-29='1'">
12905                                         <genre authority="marc">conference publication</genre>
12906                                 </xsl:when>
12907                         </xsl:choose>
12908                 </xsl:if>
12909                 <xsl:if test="$typeOf008='CF'">
12910                         <xsl:variable name="controlField008-26" select="substring($controlField008,27,1)"></xsl:variable>
12911                         <xsl:choose>
12912                                 <xsl:when test="$controlField008-26='a'">
12913                                         <genre authority="marc">numeric data</genre>
12914                                 </xsl:when>
12915                                 <xsl:when test="$controlField008-26='e'">
12916                                         <genre authority="marc">database</genre>
12917                                 </xsl:when>
12918                                 <xsl:when test="$controlField008-26='f'">
12919                                         <genre authority="marc">font</genre>
12920                                 </xsl:when>
12921                                 <xsl:when test="$controlField008-26='g'">
12922                                         <genre authority="marc">game</genre>
12923                                 </xsl:when>
12924                         </xsl:choose>
12925                 </xsl:if>
12926                 <xsl:if test="$typeOf008='BK'">
12927                         <xsl:if test="substring($controlField008,25,1)='j'">
12928                                 <genre authority="marc">patent</genre>
12929                         </xsl:if>
12930                         <xsl:if test="substring($controlField008,31,1)='1'">
12931                                 <genre authority="marc">festschrift</genre>
12932                         </xsl:if>
12933                         <xsl:variable name="controlField008-34" select="substring($controlField008,35,1)"></xsl:variable>
12934                         <xsl:if test="$controlField008-34='a' or $controlField008-34='b' or $controlField008-34='c' or $controlField008-34='d'">
12935                                 <genre authority="marc">biography</genre>
12936                         </xsl:if>
12937                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
12938                         <xsl:choose>
12939                                 <xsl:when test="$controlField008-33='e'">
12940                                         <genre authority="marc">essay</genre>
12941                                 </xsl:when>
12942                                 <xsl:when test="$controlField008-33='d'">
12943                                         <genre authority="marc">drama</genre>
12944                                 </xsl:when>
12945                                 <xsl:when test="$controlField008-33='c'">
12946                                         <genre authority="marc">comic strip</genre>
12947                                 </xsl:when>
12948                                 <xsl:when test="$controlField008-33='l'">
12949                                         <genre authority="marc">fiction</genre>
12950                                 </xsl:when>
12951                                 <xsl:when test="$controlField008-33='h'">
12952                                         <genre authority="marc">humor, satire</genre>
12953                                 </xsl:when>
12954                                 <xsl:when test="$controlField008-33='i'">
12955                                         <genre authority="marc">letter</genre>
12956                                 </xsl:when>
12957                                 <xsl:when test="$controlField008-33='f'">
12958                                         <genre authority="marc">novel</genre>
12959                                 </xsl:when>
12960                                 <xsl:when test="$controlField008-33='j'">
12961                                         <genre authority="marc">short story</genre>
12962                                 </xsl:when>
12963                                 <xsl:when test="$controlField008-33='s'">
12964                                         <genre authority="marc">speech</genre>
12965                                 </xsl:when>
12966                         </xsl:choose>
12967                 </xsl:if>
12968                 <xsl:if test="$typeOf008='MU'">
12969                         <xsl:variable name="controlField008-30-31" select="substring($controlField008,31,2)"></xsl:variable>
12970                         <xsl:if test="contains($controlField008-30-31,'b')">
12971                                 <genre authority="marc">biography</genre>
12972                         </xsl:if>
12973                         <xsl:if test="contains($controlField008-30-31,'c')">
12974                                 <genre authority="marc">conference publication</genre>
12975                         </xsl:if>
12976                         <xsl:if test="contains($controlField008-30-31,'d')">
12977                                 <genre authority="marc">drama</genre>
12978                         </xsl:if>
12979                         <xsl:if test="contains($controlField008-30-31,'e')">
12980                                 <genre authority="marc">essay</genre>
12981                         </xsl:if>
12982                         <xsl:if test="contains($controlField008-30-31,'f')">
12983                                 <genre authority="marc">fiction</genre>
12984                         </xsl:if>
12985                         <xsl:if test="contains($controlField008-30-31,'o')">
12986                                 <genre authority="marc">folktale</genre>
12987                         </xsl:if>
12988                         <xsl:if test="contains($controlField008-30-31,'h')">
12989                                 <genre authority="marc">history</genre>
12990                         </xsl:if>
12991                         <xsl:if test="contains($controlField008-30-31,'k')">
12992                                 <genre authority="marc">humor, satire</genre>
12993                         </xsl:if>
12994                         <xsl:if test="contains($controlField008-30-31,'m')">
12995                                 <genre authority="marc">memoir</genre>
12996                         </xsl:if>
12997                         <xsl:if test="contains($controlField008-30-31,'p')">
12998                                 <genre authority="marc">poetry</genre>
12999                         </xsl:if>
13000                         <xsl:if test="contains($controlField008-30-31,'r')">
13001                                 <genre authority="marc">rehearsal</genre>
13002                         </xsl:if>
13003                         <xsl:if test="contains($controlField008-30-31,'g')">
13004                                 <genre authority="marc">reporting</genre>
13005                         </xsl:if>
13006                         <xsl:if test="contains($controlField008-30-31,'s')">
13007                                 <genre authority="marc">sound</genre>
13008                         </xsl:if>
13009                         <xsl:if test="contains($controlField008-30-31,'l')">
13010                                 <genre authority="marc">speech</genre>
13011                         </xsl:if>
13012                 </xsl:if>
13013                 <xsl:if test="$typeOf008='VM'">
13014                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
13015                         <xsl:choose>
13016                                 <xsl:when test="$controlField008-33='a'">
13017                                         <genre authority="marc">art original</genre>
13018                                 </xsl:when>
13019                                 <xsl:when test="$controlField008-33='b'">
13020                                         <genre authority="marc">kit</genre>
13021                                 </xsl:when>
13022                                 <xsl:when test="$controlField008-33='c'">
13023                                         <genre authority="marc">art reproduction</genre>
13024                                 </xsl:when>
13025                                 <xsl:when test="$controlField008-33='d'">
13026                                         <genre authority="marc">diorama</genre>
13027                                 </xsl:when>
13028                                 <xsl:when test="$controlField008-33='f'">
13029                                         <genre authority="marc">filmstrip</genre>
13030                                 </xsl:when>
13031                                 <xsl:when test="$controlField008-33='g'">
13032                                         <genre authority="marc">legal article</genre>
13033                                 </xsl:when>
13034                                 <xsl:when test="$controlField008-33='i'">
13035                                         <genre authority="marc">picture</genre>
13036                                 </xsl:when>
13037                                 <xsl:when test="$controlField008-33='k'">
13038                                         <genre authority="marc">graphic</genre>
13039                                 </xsl:when>
13040                                 <xsl:when test="$controlField008-33='l'">
13041                                         <genre authority="marc">technical drawing</genre>
13042                                 </xsl:when>
13043                                 <xsl:when test="$controlField008-33='m'">
13044                                         <genre authority="marc">motion picture</genre>
13045                                 </xsl:when>
13046                                 <xsl:when test="$controlField008-33='n'">
13047                                         <genre authority="marc">chart</genre>
13048                                 </xsl:when>
13049                                 <xsl:when test="$controlField008-33='o'">
13050                                         <genre authority="marc">flash card</genre>
13051                                 </xsl:when>
13052                                 <xsl:when test="$controlField008-33='p'">
13053                                         <genre authority="marc">microscope slide</genre>
13054                                 </xsl:when>
13055                                 <xsl:when test="$controlField008-33='q' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13056                                         <genre authority="marc">model</genre>
13057                                 </xsl:when>
13058                                 <xsl:when test="$controlField008-33='r'">
13059                                         <genre authority="marc">realia</genre>
13060                                 </xsl:when>
13061                                 <xsl:when test="$controlField008-33='s'">
13062                                         <genre authority="marc">slide</genre>
13063                                 </xsl:when>
13064                                 <xsl:when test="$controlField008-33='t'">
13065                                         <genre authority="marc">transparency</genre>
13066                                 </xsl:when>
13067                                 <xsl:when test="$controlField008-33='v'">
13068                                         <genre authority="marc">videorecording</genre>
13069                                 </xsl:when>
13070                                 <xsl:when test="$controlField008-33='w'">
13071                                         <genre authority="marc">toy</genre>
13072                                 </xsl:when>
13073                         </xsl:choose>
13074                 </xsl:if>
13075                 <xsl:for-each select="marc:datafield[@tag=655]">
13076                         <genre authority="marc">
13077                                 <xsl:attribute name="authority">
13078                                         <xsl:value-of select="marc:subfield[@code='2']"/>
13079                                 </xsl:attribute>
13080                                 <xsl:call-template name="subfieldSelect">
13081                                         <xsl:with-param name="codes">abvxyz</xsl:with-param>
13082                                         <xsl:with-param name="delimeter">-</xsl:with-param>
13083                                 </xsl:call-template>
13084                         </genre>
13085                 </xsl:for-each>
13086                 <originInfo>
13087                         <xsl:variable name="MARCpublicationCode" select="normalize-space(substring($controlField008,16,3))"></xsl:variable>
13088                         <xsl:if test="translate($MARCpublicationCode,'|','')">
13089                                 <place>
13090                                         <placeTerm>
13091                                                 <xsl:attribute name="type">code</xsl:attribute>
13092                                                 <xsl:attribute name="authority">marccountry</xsl:attribute>
13093                                                 <xsl:value-of select="$MARCpublicationCode"/>
13094                                         </placeTerm>
13095                                 </place>
13096                         </xsl:if>
13097                         <xsl:for-each select="marc:datafield[@tag=044]/marc:subfield[@code='c']">
13098                                 <place>
13099                                         <placeTerm>
13100                                                 <xsl:attribute name="type">code</xsl:attribute>
13101                                                 <xsl:attribute name="authority">iso3166</xsl:attribute>
13102                                                 <xsl:value-of select="."/>
13103                                         </placeTerm>
13104                                 </place>
13105                         </xsl:for-each>
13106                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='a']">
13107                                 <place>
13108                                         <placeTerm>
13109                                                 <xsl:attribute name="type">text</xsl:attribute>
13110                                                 <xsl:call-template name="chopPunctuationFront">
13111                                                         <xsl:with-param name="chopString">
13112                                                                 <xsl:call-template name="chopPunctuation">
13113                                                                         <xsl:with-param name="chopString" select="."/>
13114                                                                 </xsl:call-template>
13115                                                         </xsl:with-param>
13116                                                 </xsl:call-template>
13117                                         </placeTerm>
13118                                 </place>
13119                         </xsl:for-each>
13120                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='m']">
13121                                 <dateValid point="start">
13122                                         <xsl:value-of select="."/>
13123                                 </dateValid>
13124                         </xsl:for-each>
13125                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='n']">
13126                                 <dateValid point="end">
13127                                         <xsl:value-of select="."/>
13128                                 </dateValid>
13129                         </xsl:for-each>
13130                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='j']">
13131                                 <dateModified>
13132                                         <xsl:value-of select="."/>
13133                                 </dateModified>
13134                         </xsl:for-each>
13135                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='b' or @code='c' or @code='g']">
13136                                 <xsl:choose>
13137                                         <xsl:when test="@code='b'">
13138                                                 <publisher>
13139                                                         <xsl:call-template name="chopPunctuation">
13140                                                                 <xsl:with-param name="chopString" select="."/>
13141                                                                 <xsl:with-param name="punctuation">
13142                                                                         <xsl:text>:,;/ </xsl:text>
13143                                                                 </xsl:with-param>
13144                                                         </xsl:call-template>
13145                                                 </publisher>
13146                                         </xsl:when>
13147                                         <xsl:when test="@code='c'">
13148                                                 <dateIssued>
13149                                                         <xsl:call-template name="chopPunctuation">
13150                                                                 <xsl:with-param name="chopString" select="."/>
13151                                                         </xsl:call-template>
13152                                                 </dateIssued>
13153                                         </xsl:when>
13154                                         <xsl:when test="@code='g'">
13155                                                 <dateCreated>
13156                                                         <xsl:value-of select="."/>
13157                                                 </dateCreated>
13158                                         </xsl:when>
13159                                 </xsl:choose>
13160                         </xsl:for-each>
13161                         <xsl:variable name="dataField260c">
13162                                 <xsl:call-template name="chopPunctuation">
13163                                         <xsl:with-param name="chopString" select="marc:datafield[@tag=260]/marc:subfield[@code='c']"></xsl:with-param>
13164                                 </xsl:call-template>
13165                         </xsl:variable>
13166                         <xsl:variable name="controlField008-7-10" select="normalize-space(substring($controlField008, 8, 4))"></xsl:variable>
13167                         <xsl:variable name="controlField008-11-14" select="normalize-space(substring($controlField008, 12, 4))"></xsl:variable>
13168                         <xsl:variable name="controlField008-6" select="normalize-space(substring($controlField008, 7, 1))"></xsl:variable>
13169                         <xsl:if test="$controlField008-6='e' or $controlField008-6='p' or $controlField008-6='r' or $controlField008-6='t' or $controlField008-6='s'">
13170                                 <xsl:if test="$controlField008-7-10 and ($controlField008-7-10 != $dataField260c)">
13171                                         <dateIssued encoding="marc">
13172                                                 <xsl:value-of select="$controlField008-7-10"/>
13173                                         </dateIssued>
13174                                 </xsl:if>
13175                         </xsl:if>
13176                         <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'">
13177                                 <xsl:if test="$controlField008-7-10">
13178                                         <dateIssued encoding="marc" point="start">
13179                                                 <xsl:value-of select="$controlField008-7-10"/>
13180                                         </dateIssued>
13181                                 </xsl:if>
13182                         </xsl:if>
13183                         <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'">
13184                                 <xsl:if test="$controlField008-11-14">
13185                                         <dateIssued encoding="marc" point="end">
13186                                                 <xsl:value-of select="$controlField008-11-14"/>
13187                                         </dateIssued>
13188                                 </xsl:if>
13189                         </xsl:if>
13190                         <xsl:if test="$controlField008-6='q'">
13191                                 <xsl:if test="$controlField008-7-10">
13192                                         <dateIssued encoding="marc" point="start" qualifier="questionable">
13193                                                 <xsl:value-of select="$controlField008-7-10"/>
13194                                         </dateIssued>
13195                                 </xsl:if>
13196                         </xsl:if>
13197                         <xsl:if test="$controlField008-6='q'">
13198                                 <xsl:if test="$controlField008-11-14">
13199                                         <dateIssued encoding="marc" point="end" qualifier="questionable">
13200                                                 <xsl:value-of select="$controlField008-11-14"/>
13201                                         </dateIssued>
13202                                 </xsl:if>
13203                         </xsl:if>
13204                         <xsl:if test="$controlField008-6='t'">
13205                                 <xsl:if test="$controlField008-11-14">
13206                                         <copyrightDate encoding="marc">
13207                                                 <xsl:value-of select="$controlField008-11-14"/>
13208                                         </copyrightDate>
13209                                 </xsl:if>
13210                         </xsl:if>
13211                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=0 or @ind1=1]/marc:subfield[@code='a']">
13212                                 <dateCaptured encoding="iso8601">
13213                                         <xsl:value-of select="."/>
13214                                 </dateCaptured>
13215                         </xsl:for-each>
13216                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][1]">
13217                                 <dateCaptured encoding="iso8601" point="start">
13218                                         <xsl:value-of select="."/>
13219                                 </dateCaptured>
13220                         </xsl:for-each>
13221                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][2]">
13222                                 <dateCaptured encoding="iso8601" point="end">
13223                                         <xsl:value-of select="."/>
13224                                 </dateCaptured>
13225                         </xsl:for-each>
13226                         <xsl:for-each select="marc:datafield[@tag=250]/marc:subfield[@code='a']">
13227                                 <edition>
13228                                         <xsl:value-of select="."/>
13229                                 </edition>
13230                         </xsl:for-each>
13231                         <xsl:for-each select="marc:leader">
13232                                 <issuance>
13233                                         <xsl:choose>
13234                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">monographic</xsl:when>
13235                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">continuing</xsl:when>
13236                                         </xsl:choose>
13237                                 </issuance>
13238                         </xsl:for-each>
13239                         <xsl:for-each select="marc:datafield[@tag=310]|marc:datafield[@tag=321]">
13240                                 <frequency>
13241                                         <xsl:call-template name="subfieldSelect">
13242                                                 <xsl:with-param name="codes">ab</xsl:with-param>
13243                                         </xsl:call-template>
13244                                 </frequency>
13245                         </xsl:for-each>
13246                 </originInfo>
13247                 <xsl:variable name="controlField008-35-37" select="normalize-space(translate(substring($controlField008,36,3),'|#',''))"></xsl:variable>
13248                 <xsl:if test="$controlField008-35-37">
13249                         <language>
13250                                 <languageTerm authority="iso639-2b" type="code">
13251                                         <xsl:value-of select="substring($controlField008,36,3)"/>
13252                                 </languageTerm>
13253                         </language>
13254                 </xsl:if>
13255                 <xsl:for-each select="marc:datafield[@tag=041]">
13256                         <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']">
13257                                 <xsl:variable name="langCodes" select="."/>
13258                                 <xsl:choose>
13259                                         <xsl:when test="../marc:subfield[@code='2']='rfc3066'">
13260                                                 <!-- not stacked but could be repeated -->
13261                                                 <xsl:call-template name="rfcLanguages">
13262                                                         <xsl:with-param name="nodeNum">
13263                                                                 <xsl:value-of select="1"/>
13264                                                         </xsl:with-param>
13265                                                         <xsl:with-param name="usedLanguages">
13266                                                                 <xsl:text></xsl:text>
13267                                                         </xsl:with-param>
13268                                                         <xsl:with-param name="controlField008-35-37">
13269                                                                 <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13270                                                         </xsl:with-param>
13271                                                 </xsl:call-template>
13272                                         </xsl:when>
13273                                         <xsl:otherwise>
13274                                                 <!-- iso -->
13275                                                 <xsl:variable name="allLanguages">
13276                                                         <xsl:copy-of select="$langCodes"></xsl:copy-of>
13277                                                 </xsl:variable>
13278                                                 <xsl:variable name="currentLanguage">
13279                                                         <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13280                                                 </xsl:variable>
13281                                                 <xsl:call-template name="isoLanguage">
13282                                                         <xsl:with-param name="currentLanguage">
13283                                                                 <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13284                                                         </xsl:with-param>
13285                                                         <xsl:with-param name="remainingLanguages">
13286                                                                 <xsl:value-of select="substring($allLanguages,4,string-length($allLanguages)-3)"></xsl:value-of>
13287                                                         </xsl:with-param>
13288                                                         <xsl:with-param name="usedLanguages">
13289                                                                 <xsl:if test="$controlField008-35-37">
13290                                                                         <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13291                                                                 </xsl:if>
13292                                                         </xsl:with-param>
13293                                                 </xsl:call-template>
13294                                         </xsl:otherwise>
13295                                 </xsl:choose>
13296                         </xsl:for-each>
13297                 </xsl:for-each>
13298                 <xsl:variable name="physicalDescription">
13299                         <!--3.2 change tmee 007/11 -->
13300                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='a']">
13301                                 <digitalOrigin>reformatted digital</digitalOrigin>
13302                         </xsl:if>
13303                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='b']">
13304                                 <digitalOrigin>digitized microfilm</digitalOrigin>
13305                         </xsl:if>
13306                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='d']">
13307                                 <digitalOrigin>digitized other analog</digitalOrigin>
13308                         </xsl:if>
13309                         <xsl:variable name="controlField008-23" select="substring($controlField008,24,1)"></xsl:variable>
13310                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
13311                         <xsl:variable name="check008-23">
13312                                 <xsl:if test="$typeOf008='BK' or $typeOf008='MU' or $typeOf008='SE' or $typeOf008='MM'">
13313                                         <xsl:value-of select="true()"></xsl:value-of>
13314                                 </xsl:if>
13315                         </xsl:variable>
13316                         <xsl:variable name="check008-29">
13317                                 <xsl:if test="$typeOf008='MP' or $typeOf008='VM'">
13318                                         <xsl:value-of select="true()"></xsl:value-of>
13319                                 </xsl:if>
13320                         </xsl:variable>
13321                         <xsl:choose>
13322                                 <xsl:when test="($check008-23 and $controlField008-23='f') or ($check008-29 and $controlField008-29='f')">
13323                                         <form authority="marcform">braille</form>
13324                                 </xsl:when>
13325                                 <xsl:when test="($controlField008-23=' ' and ($leader6='c' or $leader6='d')) or (($typeOf008='BK' or $typeOf008='SE') and ($controlField008-23=' ' or $controlField008='r'))">
13326                                         <form authority="marcform">print</form>
13327                                 </xsl:when>
13328                                 <xsl:when test="$leader6 = 'm' or ($check008-23 and $controlField008-23='s') or ($check008-29 and $controlField008-29='s')">
13329                                         <form authority="marcform">electronic</form>
13330                                 </xsl:when>
13331                                 <xsl:when test="($check008-23 and $controlField008-23='b') or ($check008-29 and $controlField008-29='b')">
13332                                         <form authority="marcform">microfiche</form>
13333                                 </xsl:when>
13334                                 <xsl:when test="($check008-23 and $controlField008-23='a') or ($check008-29 and $controlField008-29='a')">
13335                                         <form authority="marcform">microfilm</form>
13336                                 </xsl:when>
13337                         </xsl:choose>
13338                         <!-- 1/04 fix -->
13339                         <xsl:if test="marc:datafield[@tag=130]/marc:subfield[@code='h']">
13340                                 <form authority="gmd">
13341                                         <xsl:call-template name="chopBrackets">
13342                                                 <xsl:with-param name="chopString">
13343                                                         <xsl:value-of select="marc:datafield[@tag=130]/marc:subfield[@code='h']"></xsl:value-of>
13344                                                 </xsl:with-param>
13345                                         </xsl:call-template>
13346                                 </form>
13347                         </xsl:if>
13348                         <xsl:if test="marc:datafield[@tag=240]/marc:subfield[@code='h']">
13349                                 <form authority="gmd">
13350                                         <xsl:call-template name="chopBrackets">
13351                                                 <xsl:with-param name="chopString">
13352                                                         <xsl:value-of select="marc:datafield[@tag=240]/marc:subfield[@code='h']"></xsl:value-of>
13353                                                 </xsl:with-param>
13354                                         </xsl:call-template>
13355                                 </form>
13356                         </xsl:if>
13357                         <xsl:if test="marc:datafield[@tag=242]/marc:subfield[@code='h']">
13358                                 <form authority="gmd">
13359                                         <xsl:call-template name="chopBrackets">
13360                                                 <xsl:with-param name="chopString">
13361                                                         <xsl:value-of select="marc:datafield[@tag=242]/marc:subfield[@code='h']"></xsl:value-of>
13362                                                 </xsl:with-param>
13363                                         </xsl:call-template>
13364                                 </form>
13365                         </xsl:if>
13366                         <xsl:if test="marc:datafield[@tag=245]/marc:subfield[@code='h']">
13367                                 <form authority="gmd">
13368                                         <xsl:call-template name="chopBrackets">
13369                                                 <xsl:with-param name="chopString">
13370                                                         <xsl:value-of select="marc:datafield[@tag=245]/marc:subfield[@code='h']"></xsl:value-of>
13371                                                 </xsl:with-param>
13372                                         </xsl:call-template>
13373                                 </form>
13374                         </xsl:if>
13375                         <xsl:if test="marc:datafield[@tag=246]/marc:subfield[@code='h']">
13376                                 <form authority="gmd">
13377                                         <xsl:call-template name="chopBrackets">
13378                                                 <xsl:with-param name="chopString">
13379                                                         <xsl:value-of select="marc:datafield[@tag=246]/marc:subfield[@code='h']"></xsl:value-of>
13380                                                 </xsl:with-param>
13381                                         </xsl:call-template>
13382                                 </form>
13383                         </xsl:if>
13384                         <xsl:if test="marc:datafield[@tag=730]/marc:subfield[@code='h']">
13385                                 <form authority="gmd">
13386                                         <xsl:call-template name="chopBrackets">
13387                                                 <xsl:with-param name="chopString">
13388                                                         <xsl:value-of select="marc:datafield[@tag=730]/marc:subfield[@code='h']"></xsl:value-of>
13389                                                 </xsl:with-param>
13390                                         </xsl:call-template>
13391                                 </form>
13392                         </xsl:if>
13393                         <xsl:for-each select="marc:datafield[@tag=256]/marc:subfield[@code='a']">
13394                                 <form>
13395                                         <xsl:value-of select="."></xsl:value-of>
13396                                 </form>
13397                         </xsl:for-each>
13398                         <xsl:for-each select="marc:controlfield[@tag=007][substring(text(),1,1)='c']">
13399                                 <xsl:choose>
13400                                         <xsl:when test="substring(text(),14,1)='a'">
13401                                                 <reformattingQuality>access</reformattingQuality>
13402                                         </xsl:when>
13403                                         <xsl:when test="substring(text(),14,1)='p'">
13404                                                 <reformattingQuality>preservation</reformattingQuality>
13405                                         </xsl:when>
13406                                         <xsl:when test="substring(text(),14,1)='r'">
13407                                                 <reformattingQuality>replacement</reformattingQuality>
13408                                         </xsl:when>
13409                                 </xsl:choose>
13410                         </xsl:for-each>
13411                         <!--3.2 change tmee 007/01 -->
13412                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='b']">
13413                                 <form authority="smd">chip cartridge</form>
13414                         </xsl:if>
13415                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='c']">
13416                                 <form authority="smd">computer optical disc cartridge</form>
13417                         </xsl:if>
13418                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='j']">
13419                                 <form authority="smd">magnetic disc</form>
13420                         </xsl:if>
13421                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='m']">
13422                                 <form authority="smd">magneto-optical disc</form>
13423                         </xsl:if>
13424                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='o']">
13425                                 <form authority="smd">optical disc</form>
13426                         </xsl:if>
13427                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='r']">
13428                                 <form authority="smd">remote</form>
13429                         </xsl:if>
13430                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='a']">
13431                                 <form authority="smd">tape cartridge</form>
13432                         </xsl:if>
13433                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='f']">
13434                                 <form authority="smd">tape cassette</form>
13435                         </xsl:if>
13436                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='h']">
13437                                 <form authority="smd">tape reel</form>
13438                         </xsl:if>
13439                         
13440                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='a']">
13441                                 <form authority="smd">celestial globe</form>
13442                         </xsl:if>
13443                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='e']">
13444                                 <form authority="smd">earth moon globe</form>
13445                         </xsl:if>
13446                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='b']">
13447                                 <form authority="smd">planetary or lunar globe</form>
13448                         </xsl:if>
13449                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='c']">
13450                                 <form authority="smd">terrestrial globe</form>
13451                         </xsl:if>
13452                         
13453                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='o'][substring(text(),2,1)='o']">
13454                                 <form authority="smd">kit</form>
13455                         </xsl:if>
13456                         
13457                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
13458                                 <form authority="smd">atlas</form>
13459                         </xsl:if>
13460                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='g']">
13461                                 <form authority="smd">diagram</form>
13462                         </xsl:if>
13463                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
13464                                 <form authority="smd">map</form>
13465                         </xsl:if>
13466                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13467                                 <form authority="smd">model</form>
13468                         </xsl:if>
13469                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='k']">
13470                                 <form authority="smd">profile</form>
13471                         </xsl:if>
13472                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
13473                                 <form authority="smd">remote-sensing image</form>
13474                         </xsl:if>
13475                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='s']">
13476                                 <form authority="smd">section</form>
13477                         </xsl:if>
13478                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='y']">
13479                                 <form authority="smd">view</form>
13480                         </xsl:if>
13481                         
13482                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='a']">
13483                                 <form authority="smd">aperture card</form>
13484                         </xsl:if>
13485                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='e']">
13486                                 <form authority="smd">microfiche</form>
13487                         </xsl:if>
13488                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='f']">
13489                                 <form authority="smd">microfiche cassette</form>
13490                         </xsl:if>
13491                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='b']">
13492                                 <form authority="smd">microfilm cartridge</form>
13493                         </xsl:if>
13494                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='c']">
13495                                 <form authority="smd">microfilm cassette</form>
13496                         </xsl:if>
13497                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='d']">
13498                                 <form authority="smd">microfilm reel</form>
13499                         </xsl:if>
13500                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='g']">
13501                                 <form authority="smd">microopaque</form>
13502                         </xsl:if>
13503                         
13504                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='c']">
13505                                 <form authority="smd">film cartridge</form>
13506                         </xsl:if>
13507                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='f']">
13508                                 <form authority="smd">film cassette</form>
13509                         </xsl:if>
13510                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='r']">
13511                                 <form authority="smd">film reel</form>
13512                         </xsl:if>
13513                         
13514                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='n']">
13515                                 <form authority="smd">chart</form>
13516                         </xsl:if>
13517                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='c']">
13518                                 <form authority="smd">collage</form>
13519                         </xsl:if>
13520                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='d']">
13521                                 <form authority="smd">drawing</form>
13522                         </xsl:if>
13523                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='o']">
13524                                 <form authority="smd">flash card</form>
13525                         </xsl:if>
13526                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='e']">
13527                                 <form authority="smd">painting</form>
13528                         </xsl:if>
13529                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='f']">
13530                                 <form authority="smd">photomechanical print</form>
13531                         </xsl:if>
13532                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='g']">
13533                                 <form authority="smd">photonegative</form>
13534                         </xsl:if>
13535                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='h']">
13536                                 <form authority="smd">photoprint</form>
13537                         </xsl:if>
13538                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='i']">
13539                                 <form authority="smd">picture</form>
13540                         </xsl:if>
13541                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='j']">
13542                                 <form authority="smd">print</form>
13543                         </xsl:if>
13544                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='l']">
13545                                 <form authority="smd">technical drawing</form>
13546                         </xsl:if>
13547                         
13548                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='q'][substring(text(),2,1)='q']">
13549                                 <form authority="smd">notated music</form>
13550                         </xsl:if>
13551                         
13552                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='d']">
13553                                 <form authority="smd">filmslip</form>
13554                         </xsl:if>
13555                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='c']">
13556                                 <form authority="smd">filmstrip cartridge</form>
13557                         </xsl:if>
13558                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='o']">
13559                                 <form authority="smd">filmstrip roll</form>
13560                         </xsl:if>
13561                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='f']">
13562                                 <form authority="smd">other filmstrip type</form>
13563                         </xsl:if>
13564                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='s']">
13565                                 <form authority="smd">slide</form>
13566                         </xsl:if>
13567                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='t']">
13568                                 <form authority="smd">transparency</form>
13569                         </xsl:if>
13570                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='r'][substring(text(),2,1)='r']">
13571                                 <form authority="smd">remote-sensing image</form>
13572                         </xsl:if>
13573                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='e']">
13574                                 <form authority="smd">cylinder</form>
13575                         </xsl:if>
13576                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='q']">
13577                                 <form authority="smd">roll</form>
13578                         </xsl:if>
13579                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='g']">
13580                                 <form authority="smd">sound cartridge</form>
13581                         </xsl:if>
13582                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='s']">
13583                                 <form authority="smd">sound cassette</form>
13584                         </xsl:if>
13585                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='d']">
13586                                 <form authority="smd">sound disc</form>
13587                         </xsl:if>
13588                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='t']">
13589                                 <form authority="smd">sound-tape reel</form>
13590                         </xsl:if>
13591                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='i']">
13592                                 <form authority="smd">sound-track film</form>
13593                         </xsl:if>
13594                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='w']">
13595                                 <form authority="smd">wire recording</form>
13596                         </xsl:if>
13597                         
13598                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='c']">
13599                                 <form authority="smd">braille</form>
13600                         </xsl:if>
13601                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='b']">
13602                                 <form authority="smd">combination</form>
13603                         </xsl:if>
13604                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='a']">
13605                                 <form authority="smd">moon</form>
13606                         </xsl:if>
13607                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='d']">
13608                                 <form authority="smd">tactile, with no writing system</form>
13609                         </xsl:if>
13610                         
13611                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='c']">
13612                                 <form authority="smd">braille</form>
13613                         </xsl:if>
13614                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='b']">
13615                                 <form authority="smd">large print</form>
13616                         </xsl:if>
13617                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='a']">
13618                                 <form authority="smd">regular print</form>
13619                         </xsl:if>
13620                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='d']">
13621                                 <form authority="smd">text in looseleaf binder</form>
13622                         </xsl:if>
13623                         
13624                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='c']">
13625                                 <form authority="smd">videocartridge</form>
13626                         </xsl:if>
13627                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='f']">
13628                                 <form authority="smd">videocassette</form>
13629                         </xsl:if>
13630                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='d']">
13631                                 <form authority="smd">videodisc</form>
13632                         </xsl:if>
13633                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='r']">
13634                                 <form authority="smd">videoreel</form>
13635                         </xsl:if>
13636                         
13637                         <xsl:for-each select="marc:datafield[@tag=856]/marc:subfield[@code='q'][string-length(.)>1]">
13638                                 <internetMediaType>
13639                                         <xsl:value-of select="."></xsl:value-of>
13640                                 </internetMediaType>
13641                         </xsl:for-each>
13642                         <xsl:for-each select="marc:datafield[@tag=300]">
13643                                 <extent>
13644                                         <xsl:call-template name="subfieldSelect">
13645                                                 <xsl:with-param name="codes">abce</xsl:with-param>
13646                                         </xsl:call-template>
13647                                 </extent>
13648                         </xsl:for-each>
13649                 </xsl:variable>
13650                 <xsl:if test="string-length(normalize-space($physicalDescription))">
13651                         <physicalDescription>
13652                                 <xsl:copy-of select="$physicalDescription"></xsl:copy-of>
13653                         </physicalDescription>
13654                 </xsl:if>
13655                 <xsl:for-each select="marc:datafield[@tag=520]">
13656                         <abstract>
13657                                 <xsl:call-template name="uri"></xsl:call-template>
13658                                 <xsl:call-template name="subfieldSelect">
13659                                         <xsl:with-param name="codes">ab</xsl:with-param>
13660                                 </xsl:call-template>
13661                         </abstract>
13662                 </xsl:for-each>
13663                 <xsl:for-each select="marc:datafield[@tag=505]">
13664                         <tableOfContents>
13665                                 <xsl:call-template name="uri"></xsl:call-template>
13666                                 <xsl:call-template name="subfieldSelect">
13667                                         <xsl:with-param name="codes">agrt</xsl:with-param>
13668                                 </xsl:call-template>
13669                         </tableOfContents>
13670                 </xsl:for-each>
13671                 <xsl:for-each select="marc:datafield[@tag=521]">
13672                         <targetAudience>
13673                                 <xsl:call-template name="subfieldSelect">
13674                                         <xsl:with-param name="codes">ab</xsl:with-param>
13675                                 </xsl:call-template>
13676                         </targetAudience>
13677                 </xsl:for-each>
13678                 <xsl:if test="$typeOf008='BK' or $typeOf008='CF' or $typeOf008='MU' or $typeOf008='VM'">
13679                         <xsl:variable name="controlField008-22" select="substring($controlField008,23,1)"></xsl:variable>
13680                         <xsl:choose>
13681                                 <!-- 01/04 fix -->
13682                                 <xsl:when test="$controlField008-22='d'">
13683                                         <targetAudience authority="marctarget">adolescent</targetAudience>
13684                                 </xsl:when>
13685                                 <xsl:when test="$controlField008-22='e'">
13686                                         <targetAudience authority="marctarget">adult</targetAudience>
13687                                 </xsl:when>
13688                                 <xsl:when test="$controlField008-22='g'">
13689                                         <targetAudience authority="marctarget">general</targetAudience>
13690                                 </xsl:when>
13691                                 <xsl:when test="$controlField008-22='b' or $controlField008-22='c' or $controlField008-22='j'">
13692                                         <targetAudience authority="marctarget">juvenile</targetAudience>
13693                                 </xsl:when>
13694                                 <xsl:when test="$controlField008-22='a'">
13695                                         <targetAudience authority="marctarget">preschool</targetAudience>
13696                                 </xsl:when>
13697                                 <xsl:when test="$controlField008-22='f'">
13698                                         <targetAudience authority="marctarget">specialized</targetAudience>
13699                                 </xsl:when>
13700                         </xsl:choose>
13701                 </xsl:if>
13702                 <xsl:for-each select="marc:datafield[@tag=245]/marc:subfield[@code='c']">
13703                         <note type="statement of responsibility">
13704                                 <xsl:value-of select="."></xsl:value-of>
13705                         </note>
13706                 </xsl:for-each>
13707                 <xsl:for-each select="marc:datafield[@tag=500]">
13708                         <note>
13709                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13710                                 <xsl:call-template name="uri"></xsl:call-template>
13711                         </note>
13712                 </xsl:for-each>
13713                 
13714                 <!--3.2 change tmee additional note fields-->
13715                 
13716                 <xsl:for-each select="marc:datafield[@tag=506]">
13717                         <note type="restrictions">
13718                                 <xsl:call-template name="uri"></xsl:call-template>
13719                                 <xsl:variable name="str">
13720                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13721                                                 <xsl:value-of select="."></xsl:value-of>
13722                                                 <xsl:text> </xsl:text>
13723                                         </xsl:for-each>
13724                                 </xsl:variable>
13725                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13726                         </note>
13727                 </xsl:for-each>
13728                 
13729                 <xsl:for-each select="marc:datafield[@tag=510]">
13730                         <note  type="citation/reference">
13731                                 <xsl:call-template name="uri"></xsl:call-template>
13732                                 <xsl:variable name="str">
13733                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13734                                                 <xsl:value-of select="."></xsl:value-of>
13735                                                 <xsl:text> </xsl:text>
13736                                         </xsl:for-each>
13737                                 </xsl:variable>
13738                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13739                         </note>
13740                 </xsl:for-each>
13741                 
13742                         
13743                 <xsl:for-each select="marc:datafield[@tag=511]">
13744                         <note type="performers">
13745                                 <xsl:call-template name="uri"></xsl:call-template>
13746                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13747                         </note>
13748                 </xsl:for-each>
13749                 <xsl:for-each select="marc:datafield[@tag=518]">
13750                         <note type="venue">
13751                                 <xsl:call-template name="uri"></xsl:call-template>
13752                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13753                         </note>
13754                 </xsl:for-each>
13755                 
13756                 <xsl:for-each select="marc:datafield[@tag=530]">
13757                         <note  type="additional physical form">
13758                                 <xsl:call-template name="uri"></xsl:call-template>
13759                                 <xsl:variable name="str">
13760                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13761                                                 <xsl:value-of select="."></xsl:value-of>
13762                                                 <xsl:text> </xsl:text>
13763                                         </xsl:for-each>
13764                                 </xsl:variable>
13765                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13766                         </note>
13767                 </xsl:for-each>
13768                 
13769                 <xsl:for-each select="marc:datafield[@tag=533]">
13770                         <note  type="reproduction">
13771                                 <xsl:call-template name="uri"></xsl:call-template>
13772                                 <xsl:variable name="str">
13773                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13774                                                 <xsl:value-of select="."></xsl:value-of>
13775                                                 <xsl:text> </xsl:text>
13776                                         </xsl:for-each>
13777                                 </xsl:variable>
13778                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13779                         </note>
13780                 </xsl:for-each>
13781                 
13782                 <xsl:for-each select="marc:datafield[@tag=534]">
13783                         <note  type="original version">
13784                                 <xsl:call-template name="uri"></xsl:call-template>
13785                                 <xsl:variable name="str">
13786                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13787                                                 <xsl:value-of select="."></xsl:value-of>
13788                                                 <xsl:text> </xsl:text>
13789                                         </xsl:for-each>
13790                                 </xsl:variable>
13791                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13792                         </note>
13793                 </xsl:for-each>
13794                 
13795                 <xsl:for-each select="marc:datafield[@tag=538]">
13796                         <note  type="system details">
13797                                 <xsl:call-template name="uri"></xsl:call-template>
13798                                 <xsl:variable name="str">
13799                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13800                                                 <xsl:value-of select="."></xsl:value-of>
13801                                                 <xsl:text> </xsl:text>
13802                                         </xsl:for-each>
13803                                 </xsl:variable>
13804                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13805                         </note>
13806                 </xsl:for-each>
13807                 
13808                 <xsl:for-each select="marc:datafield[@tag=583]">
13809                         <note type="action">
13810                                 <xsl:call-template name="uri"></xsl:call-template>
13811                                 <xsl:variable name="str">
13812                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13813                                                 <xsl:value-of select="."></xsl:value-of>
13814                                                 <xsl:text> </xsl:text>
13815                                         </xsl:for-each>
13816                                 </xsl:variable>
13817                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13818                         </note>
13819                 </xsl:for-each>
13820                 
13821
13822                 
13823                 
13824                 
13825                 <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]">
13826                         <note>
13827                                 <xsl:call-template name="uri"></xsl:call-template>
13828                                 <xsl:variable name="str">
13829                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13830                                                 <xsl:value-of select="."></xsl:value-of>
13831                                                 <xsl:text> </xsl:text>
13832                                         </xsl:for-each>
13833                                 </xsl:variable>
13834                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13835                         </note>
13836                 </xsl:for-each>
13837                 <xsl:for-each select="marc:datafield[@tag=034][marc:subfield[@code='d' or @code='e' or @code='f' or @code='g']]">
13838                         <subject>
13839                                 <cartographics>
13840                                         <coordinates>
13841                                                 <xsl:call-template name="subfieldSelect">
13842                                                         <xsl:with-param name="codes">defg</xsl:with-param>
13843                                                 </xsl:call-template>
13844                                         </coordinates>
13845                                 </cartographics>
13846                         </subject>
13847                 </xsl:for-each>
13848                 <xsl:for-each select="marc:datafield[@tag=043]">
13849                         <subject>
13850                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13851                                         <geographicCode>
13852                                                 <xsl:attribute name="authority">
13853                                                         <xsl:if test="@code='a'">
13854                                                                 <xsl:text>marcgac</xsl:text>
13855                                                         </xsl:if>
13856                                                         <xsl:if test="@code='b'">
13857                                                                 <xsl:value-of select="following-sibling::marc:subfield[@code=2]"></xsl:value-of>
13858                                                         </xsl:if>
13859                                                         <xsl:if test="@code='c'">
13860                                                                 <xsl:text>iso3166</xsl:text>
13861                                                         </xsl:if>
13862                                                 </xsl:attribute>
13863                                                 <xsl:value-of select="self::marc:subfield"></xsl:value-of>
13864                                         </geographicCode>
13865                                 </xsl:for-each>
13866                         </subject>
13867                 </xsl:for-each>
13868                 <!-- tmee 2006/11/27 -->
13869                 <xsl:for-each select="marc:datafield[@tag=255]">
13870                         <subject>
13871                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13872                                 <cartographics>
13873                                         <xsl:if test="@code='a'">
13874                                                 <scale>
13875                                                         <xsl:value-of select="."></xsl:value-of>
13876                                                 </scale>
13877                                         </xsl:if>
13878                                         <xsl:if test="@code='b'">
13879                                                 <projection>
13880                                                         <xsl:value-of select="."></xsl:value-of>
13881                                                 </projection>
13882                                         </xsl:if>
13883                                         <xsl:if test="@code='c'">
13884                                                 <coordinates>
13885                                                         <xsl:value-of select="."></xsl:value-of>
13886                                                 </coordinates>
13887                                         </xsl:if>
13888                                 </cartographics>
13889                                 </xsl:for-each>
13890                         </subject>
13891                 </xsl:for-each>
13892                                 
13893                 <xsl:apply-templates select="marc:datafield[653 >= @tag and @tag >= 600]"></xsl:apply-templates>
13894                 <xsl:apply-templates select="marc:datafield[@tag=656]"></xsl:apply-templates>
13895                 <xsl:for-each select="marc:datafield[@tag=752]">
13896                         <subject>
13897                                 <hierarchicalGeographic>
13898                                         <xsl:for-each select="marc:subfield[@code='a']">
13899                                                 <country>
13900                                                         <xsl:call-template name="chopPunctuation">
13901                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13902                                                         </xsl:call-template>
13903                                                 </country>
13904                                         </xsl:for-each>
13905                                         <xsl:for-each select="marc:subfield[@code='b']">
13906                                                 <state>
13907                                                         <xsl:call-template name="chopPunctuation">
13908                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13909                                                         </xsl:call-template>
13910                                                 </state>
13911                                         </xsl:for-each>
13912                                         <xsl:for-each select="marc:subfield[@code='c']">
13913                                                 <county>
13914                                                         <xsl:call-template name="chopPunctuation">
13915                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13916                                                         </xsl:call-template>
13917                                                 </county>
13918                                         </xsl:for-each>
13919                                         <xsl:for-each select="marc:subfield[@code='d']">
13920                                                 <city>
13921                                                         <xsl:call-template name="chopPunctuation">
13922                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13923                                                         </xsl:call-template>
13924                                                 </city>
13925                                         </xsl:for-each>
13926                                 </hierarchicalGeographic>
13927                         </subject>
13928                 </xsl:for-each>
13929                 <xsl:for-each select="marc:datafield[@tag=045][marc:subfield[@code='b']]">
13930                         <subject>
13931                                 <xsl:choose>
13932                                         <xsl:when test="@ind1=2">
13933                                                 <temporal encoding="iso8601" point="start">
13934                                                         <xsl:call-template name="chopPunctuation">
13935                                                                 <xsl:with-param name="chopString">
13936                                                                         <xsl:value-of select="marc:subfield[@code='b'][1]"></xsl:value-of>
13937                                                                 </xsl:with-param>
13938                                                         </xsl:call-template>
13939                                                 </temporal>
13940                                                 <temporal encoding="iso8601" point="end">
13941                                                         <xsl:call-template name="chopPunctuation">
13942                                                                 <xsl:with-param name="chopString">
13943                                                                         <xsl:value-of select="marc:subfield[@code='b'][2]"></xsl:value-of>
13944                                                                 </xsl:with-param>
13945                                                         </xsl:call-template>
13946                                                 </temporal>
13947                                         </xsl:when>
13948                                         <xsl:otherwise>
13949                                                 <xsl:for-each select="marc:subfield[@code='b']">
13950                                                         <temporal encoding="iso8601">
13951                                                                 <xsl:call-template name="chopPunctuation">
13952                                                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
13953                                                                 </xsl:call-template>
13954                                                         </temporal>
13955                                                 </xsl:for-each>
13956                                         </xsl:otherwise>
13957                                 </xsl:choose>
13958                         </subject>
13959                 </xsl:for-each>
13960                 <xsl:for-each select="marc:datafield[@tag=050]">
13961                         <xsl:for-each select="marc:subfield[@code='b']">
13962                                 <classification authority="lcc">
13963                                         <xsl:if test="../marc:subfield[@code='3']">
13964                                                 <xsl:attribute name="displayLabel">
13965                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
13966                                                 </xsl:attribute>
13967                                         </xsl:if>
13968                                         <xsl:value-of select="preceding-sibling::marc:subfield[@code='a'][1]"></xsl:value-of>
13969                                         <xsl:text> </xsl:text>
13970                                         <xsl:value-of select="text()"></xsl:value-of>
13971                                 </classification>
13972                         </xsl:for-each>
13973                         <xsl:for-each select="marc:subfield[@code='a'][not(following-sibling::marc:subfield[@code='b'])]">
13974                                 <classification authority="lcc">
13975                                         <xsl:if test="../marc:subfield[@code='3']">
13976                                                 <xsl:attribute name="displayLabel">
13977                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
13978                                                 </xsl:attribute>
13979                                         </xsl:if>
13980                                         <xsl:value-of select="text()"></xsl:value-of>
13981                                 </classification>
13982                         </xsl:for-each>
13983                 </xsl:for-each>
13984                 <xsl:for-each select="marc:datafield[@tag=082]">
13985                         <classification authority="ddc">
13986                                 <xsl:if test="marc:subfield[@code='2']">
13987                                         <xsl:attribute name="edition">
13988                                                 <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
13989                                         </xsl:attribute>
13990                                 </xsl:if>
13991                                 <xsl:call-template name="subfieldSelect">
13992                                         <xsl:with-param name="codes">ab</xsl:with-param>
13993                                 </xsl:call-template>
13994                         </classification>
13995                 </xsl:for-each>
13996                 <xsl:for-each select="marc:datafield[@tag=080]">
13997                         <classification authority="udc">
13998                                 <xsl:call-template name="subfieldSelect">
13999                                         <xsl:with-param name="codes">abx</xsl:with-param>
14000                                 </xsl:call-template>
14001                         </classification>
14002                 </xsl:for-each>
14003                 <xsl:for-each select="marc:datafield[@tag=060]">
14004                         <classification authority="nlm">
14005                                 <xsl:call-template name="subfieldSelect">
14006                                         <xsl:with-param name="codes">ab</xsl:with-param>
14007                                 </xsl:call-template>
14008                         </classification>
14009                 </xsl:for-each>
14010                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=0]">
14011                         <classification authority="sudocs">
14012                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14013                         </classification>
14014                 </xsl:for-each>
14015                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=1]">
14016                         <classification authority="candoc">
14017                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14018                         </classification>
14019                 </xsl:for-each>
14020                 <xsl:for-each select="marc:datafield[@tag=086]">
14021                         <classification>
14022                                 <xsl:attribute name="authority">
14023                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14024                                 </xsl:attribute>
14025                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14026                         </classification>
14027                 </xsl:for-each>
14028                 <xsl:for-each select="marc:datafield[@tag=084]">
14029                         <classification>
14030                                 <xsl:attribute name="authority">
14031                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14032                                 </xsl:attribute>
14033                                 <xsl:call-template name="subfieldSelect">
14034                                         <xsl:with-param name="codes">ab</xsl:with-param>
14035                                 </xsl:call-template>
14036                         </classification>
14037                 </xsl:for-each>
14038                 <xsl:for-each select="marc:datafield[@tag=440]">
14039                         <relatedItem type="series">
14040                                 <titleInfo>
14041                                         <title>
14042                                                 <xsl:call-template name="chopPunctuation">
14043                                                         <xsl:with-param name="chopString">
14044                                                                 <xsl:call-template name="subfieldSelect">
14045                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14046                                                                 </xsl:call-template>
14047                                                         </xsl:with-param>
14048                                                 </xsl:call-template>
14049                                         </title>
14050                                         <xsl:call-template name="part"></xsl:call-template>
14051                                 </titleInfo>
14052                         </relatedItem>
14053                 </xsl:for-each>
14054                 <xsl:for-each select="marc:datafield[@tag=490][@ind1=0]">
14055                         <relatedItem type="series">
14056                                 <titleInfo>
14057                                         <title>
14058                                                 <xsl:call-template name="chopPunctuation">
14059                                                         <xsl:with-param name="chopString">
14060                                                                 <xsl:call-template name="subfieldSelect">
14061                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14062                                                                 </xsl:call-template>
14063                                                         </xsl:with-param>
14064                                                 </xsl:call-template>
14065                                         </title>
14066                                         <xsl:call-template name="part"></xsl:call-template>
14067                                 </titleInfo>
14068                         </relatedItem>
14069                 </xsl:for-each>
14070                 <xsl:for-each select="marc:datafield[@tag=510]">
14071                         <relatedItem type="isReferencedBy">
14072                                 <note>
14073                                         <xsl:call-template name="subfieldSelect">
14074                                                 <xsl:with-param name="codes">abcx3</xsl:with-param>
14075                                         </xsl:call-template>
14076                                 </note>
14077                         </relatedItem>
14078                 </xsl:for-each>
14079                 <xsl:for-each select="marc:datafield[@tag=534]">
14080                         <relatedItem type="original">
14081                                 <xsl:call-template name="relatedTitle"></xsl:call-template>
14082                                 <xsl:call-template name="relatedName"></xsl:call-template>
14083                                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
14084                                         <originInfo>
14085                                                 <xsl:for-each select="marc:subfield[@code='c']">
14086                                                         <publisher>
14087                                                                 <xsl:value-of select="."></xsl:value-of>
14088                                                         </publisher>
14089                                                 </xsl:for-each>
14090                                                 <xsl:for-each select="marc:subfield[@code='b']">
14091                                                         <edition>
14092                                                                 <xsl:value-of select="."></xsl:value-of>
14093                                                         </edition>
14094                                                 </xsl:for-each>
14095                                         </originInfo>
14096                                 </xsl:if>
14097                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14098                                 <xsl:for-each select="marc:subfield[@code='z']">
14099                                         <identifier type="isbn">
14100                                                 <xsl:value-of select="."></xsl:value-of>
14101                                         </identifier>
14102                                 </xsl:for-each>
14103                                 <xsl:call-template name="relatedNote"></xsl:call-template>
14104                         </relatedItem>
14105                 </xsl:for-each>
14106                 <xsl:for-each select="marc:datafield[@tag=700][marc:subfield[@code='t']]">
14107                         <relatedItem>
14108                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14109                                 <titleInfo>
14110                                         <title>
14111                                                 <xsl:call-template name="chopPunctuation">
14112                                                         <xsl:with-param name="chopString">
14113                                                                 <xsl:call-template name="specialSubfieldSelect">
14114                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14115                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14116                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14117                                                                 </xsl:call-template>
14118                                                         </xsl:with-param>
14119                                                 </xsl:call-template>
14120                                         </title>
14121                                         <xsl:call-template name="part"></xsl:call-template>
14122                                 </titleInfo>
14123                                 <name type="personal">
14124                                         <namePart>
14125                                                 <xsl:call-template name="specialSubfieldSelect">
14126                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14127                                                         <xsl:with-param name="axis">t</xsl:with-param>
14128                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14129                                                 </xsl:call-template>
14130                                         </namePart>
14131                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14132                                         <xsl:call-template name="nameDate"></xsl:call-template>
14133                                         <xsl:call-template name="role"></xsl:call-template>
14134                                 </name>
14135                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14136                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14137                         </relatedItem>
14138                 </xsl:for-each>
14139                 <xsl:for-each select="marc:datafield[@tag=710][marc:subfield[@code='t']]">
14140                         <relatedItem>
14141                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14142                                 <titleInfo>
14143                                         <title>
14144                                                 <xsl:call-template name="chopPunctuation">
14145                                                         <xsl:with-param name="chopString">
14146                                                                 <xsl:call-template name="specialSubfieldSelect">
14147                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14148                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14149                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14150                                                                 </xsl:call-template>
14151                                                         </xsl:with-param>
14152                                                 </xsl:call-template>
14153                                         </title>
14154                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14155                                 </titleInfo>
14156                                 <name type="corporate">
14157                                         <xsl:for-each select="marc:subfield[@code='a']">
14158                                                 <namePart>
14159                                                         <xsl:value-of select="."></xsl:value-of>
14160                                                 </namePart>
14161                                         </xsl:for-each>
14162                                         <xsl:for-each select="marc:subfield[@code='b']">
14163                                                 <namePart>
14164                                                         <xsl:value-of select="."></xsl:value-of>
14165                                                 </namePart>
14166                                         </xsl:for-each>
14167                                         <xsl:variable name="tempNamePart">
14168                                                 <xsl:call-template name="specialSubfieldSelect">
14169                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14170                                                         <xsl:with-param name="axis">t</xsl:with-param>
14171                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14172                                                 </xsl:call-template>
14173                                         </xsl:variable>
14174                                         <xsl:if test="normalize-space($tempNamePart)">
14175                                                 <namePart>
14176                                                         <xsl:value-of select="$tempNamePart"></xsl:value-of>
14177                                                 </namePart>
14178                                         </xsl:if>
14179                                         <xsl:call-template name="role"></xsl:call-template>
14180                                 </name>
14181                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14182                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14183                         </relatedItem>
14184                 </xsl:for-each>
14185                 <xsl:for-each select="marc:datafield[@tag=711][marc:subfield[@code='t']]">
14186                         <relatedItem>
14187                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14188                                 <titleInfo>
14189                                         <title>
14190                                                 <xsl:call-template name="chopPunctuation">
14191                                                         <xsl:with-param name="chopString">
14192                                                                 <xsl:call-template name="specialSubfieldSelect">
14193                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14194                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14195                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14196                                                                 </xsl:call-template>
14197                                                         </xsl:with-param>
14198                                                 </xsl:call-template>
14199                                         </title>
14200                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14201                                 </titleInfo>
14202                                 <name type="conference">
14203                                         <namePart>
14204                                                 <xsl:call-template name="specialSubfieldSelect">
14205                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14206                                                         <xsl:with-param name="axis">t</xsl:with-param>
14207                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14208                                                 </xsl:call-template>
14209                                         </namePart>
14210                                 </name>
14211                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14212                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14213                         </relatedItem>
14214                 </xsl:for-each>
14215                 <xsl:for-each select="marc:datafield[@tag=730][@ind2=2]">
14216                         <relatedItem>
14217                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14218                                 <titleInfo>
14219                                         <title>
14220                                                 <xsl:call-template name="chopPunctuation">
14221                                                         <xsl:with-param name="chopString">
14222                                                                 <xsl:call-template name="subfieldSelect">
14223                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14224                                                                 </xsl:call-template>
14225                                                         </xsl:with-param>
14226                                                 </xsl:call-template>
14227                                         </title>
14228                                         <xsl:call-template name="part"></xsl:call-template>
14229                                 </titleInfo>
14230                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14231                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14232                         </relatedItem>
14233                 </xsl:for-each>
14234                 <xsl:for-each select="marc:datafield[@tag=740][@ind2=2]">
14235                         <relatedItem>
14236                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14237                                 <titleInfo>
14238                                         <title>
14239                                                 <xsl:call-template name="chopPunctuation">
14240                                                         <xsl:with-param name="chopString">
14241                                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14242                                                         </xsl:with-param>
14243                                                 </xsl:call-template>
14244                                         </title>
14245                                         <xsl:call-template name="part"></xsl:call-template>
14246                                 </titleInfo>
14247                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14248                         </relatedItem>
14249                 </xsl:for-each>
14250                 <xsl:for-each select="marc:datafield[@tag=760]|marc:datafield[@tag=762]">
14251                         <relatedItem type="series">
14252                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14253                         </relatedItem>
14254                 </xsl:for-each>
14255                 <xsl:for-each select="marc:datafield[@tag=765]|marc:datafield[@tag=767]|marc:datafield[@tag=777]|marc:datafield[@tag=787]">
14256                         <relatedItem>
14257                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14258                         </relatedItem>
14259                 </xsl:for-each>
14260                 <xsl:for-each select="marc:datafield[@tag=775]">
14261                         <relatedItem type="otherVersion">
14262                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14263                         </relatedItem>
14264                 </xsl:for-each>
14265                 <xsl:for-each select="marc:datafield[@tag=770]|marc:datafield[@tag=774]">
14266                         <relatedItem type="constituent">
14267                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14268                         </relatedItem>
14269                 </xsl:for-each>
14270                 <xsl:for-each select="marc:datafield[@tag=772]|marc:datafield[@tag=773]">
14271                         <relatedItem type="host">
14272                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14273                         </relatedItem>
14274                 </xsl:for-each>
14275                 <xsl:for-each select="marc:datafield[@tag=776]">
14276                         <relatedItem type="otherFormat">
14277                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14278                         </relatedItem>
14279                 </xsl:for-each>
14280                 <xsl:for-each select="marc:datafield[@tag=780]">
14281                         <relatedItem type="preceding">
14282                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14283                         </relatedItem>
14284                 </xsl:for-each>
14285                 <xsl:for-each select="marc:datafield[@tag=785]">
14286                         <relatedItem type="succeeding">
14287                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14288                         </relatedItem>
14289                 </xsl:for-each>
14290                 <xsl:for-each select="marc:datafield[@tag=786]">
14291                         <relatedItem type="original">
14292                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14293                         </relatedItem>
14294                 </xsl:for-each>
14295                 <xsl:for-each select="marc:datafield[@tag=800]">
14296                         <relatedItem type="series">
14297                                 <titleInfo>
14298                                         <title>
14299                                                 <xsl:call-template name="chopPunctuation">
14300                                                         <xsl:with-param name="chopString">
14301                                                                 <xsl:call-template name="specialSubfieldSelect">
14302                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14303                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14304                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14305                                                                 </xsl:call-template>
14306                                                         </xsl:with-param>
14307                                                 </xsl:call-template>
14308                                         </title>
14309                                         <xsl:call-template name="part"></xsl:call-template>
14310                                 </titleInfo>
14311                                 <name type="personal">
14312                                         <namePart>
14313                                                 <xsl:call-template name="chopPunctuation">
14314                                                         <xsl:with-param name="chopString">
14315                                                                 <xsl:call-template name="specialSubfieldSelect">
14316                                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14317                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14318                                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14319                                                                 </xsl:call-template>
14320                                                         </xsl:with-param>
14321                                                 </xsl:call-template>
14322                                         </namePart>
14323                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14324                                         <xsl:call-template name="nameDate"></xsl:call-template>
14325                                         <xsl:call-template name="role"></xsl:call-template>
14326                                 </name>
14327                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14328                         </relatedItem>
14329                 </xsl:for-each>
14330                 <xsl:for-each select="marc:datafield[@tag=810]">
14331                         <relatedItem type="series">
14332                                 <titleInfo>
14333                                         <title>
14334                                                 <xsl:call-template name="chopPunctuation">
14335                                                         <xsl:with-param name="chopString">
14336                                                                 <xsl:call-template name="specialSubfieldSelect">
14337                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14338                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14339                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14340                                                                 </xsl:call-template>
14341                                                         </xsl:with-param>
14342                                                 </xsl:call-template>
14343                                         </title>
14344                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14345                                 </titleInfo>
14346                                 <name type="corporate">
14347                                         <xsl:for-each select="marc:subfield[@code='a']">
14348                                                 <namePart>
14349                                                         <xsl:value-of select="."></xsl:value-of>
14350                                                 </namePart>
14351                                         </xsl:for-each>
14352                                         <xsl:for-each select="marc:subfield[@code='b']">
14353                                                 <namePart>
14354                                                         <xsl:value-of select="."></xsl:value-of>
14355                                                 </namePart>
14356                                         </xsl:for-each>
14357                                         <namePart>
14358                                                 <xsl:call-template name="specialSubfieldSelect">
14359                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14360                                                         <xsl:with-param name="axis">t</xsl:with-param>
14361                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14362                                                 </xsl:call-template>
14363                                         </namePart>
14364                                         <xsl:call-template name="role"></xsl:call-template>
14365                                 </name>
14366                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14367                         </relatedItem>
14368                 </xsl:for-each>
14369                 <xsl:for-each select="marc:datafield[@tag=811]">
14370                         <relatedItem type="series">
14371                                 <titleInfo>
14372                                         <title>
14373                                                 <xsl:call-template name="chopPunctuation">
14374                                                         <xsl:with-param name="chopString">
14375                                                                 <xsl:call-template name="specialSubfieldSelect">
14376                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14377                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14378                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14379                                                                 </xsl:call-template>
14380                                                         </xsl:with-param>
14381                                                 </xsl:call-template>
14382                                         </title>
14383                                         <xsl:call-template name="relatedPartNumName"/>
14384                                 </titleInfo>
14385                                 <name type="conference">
14386                                         <namePart>
14387                                                 <xsl:call-template name="specialSubfieldSelect">
14388                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14389                                                         <xsl:with-param name="axis">t</xsl:with-param>
14390                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14391                                                 </xsl:call-template>
14392                                         </namePart>
14393                                         <xsl:call-template name="role"/>
14394                                 </name>
14395                                 <xsl:call-template name="relatedForm"/>
14396                         </relatedItem>
14397                 </xsl:for-each>
14398                 <xsl:for-each select="marc:datafield[@tag='830']">
14399                         <relatedItem type="series">
14400                                 <titleInfo>
14401                                         <title>
14402                                                 <xsl:call-template name="chopPunctuation">
14403                                                         <xsl:with-param name="chopString">
14404                                                                 <xsl:call-template name="subfieldSelect">
14405                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14406                                                                 </xsl:call-template>
14407                                                         </xsl:with-param>
14408                                                 </xsl:call-template>
14409                                         </title>
14410                                         <xsl:call-template name="part"/>
14411                                 </titleInfo>
14412                                 <xsl:call-template name="relatedForm"/>
14413                         </relatedItem>
14414                 </xsl:for-each>
14415                 <xsl:for-each select="marc:datafield[@tag='856'][@ind2='2']/marc:subfield[@code='q']">
14416                         <relatedItem>
14417                                 <internetMediaType>
14418                                         <xsl:value-of select="."/>
14419                                 </internetMediaType>
14420                         </relatedItem>
14421                 </xsl:for-each>
14422                 <xsl:for-each select="marc:datafield[@tag='020']">
14423                         <xsl:call-template name="isInvalid">
14424                                 <xsl:with-param name="type">isbn</xsl:with-param>
14425                         </xsl:call-template>
14426                         <xsl:if test="marc:subfield[@code='a']">
14427                                 <identifier type="isbn">
14428                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14429                                 </identifier>
14430                         </xsl:if>
14431                 </xsl:for-each>
14432                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='0']">
14433                         <xsl:call-template name="isInvalid">
14434                                 <xsl:with-param name="type">isrc</xsl:with-param>
14435                         </xsl:call-template>
14436                         <xsl:if test="marc:subfield[@code='a']">
14437                                 <identifier type="isrc">
14438                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14439                                 </identifier>
14440                         </xsl:if>
14441                 </xsl:for-each>
14442                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='2']">
14443                         <xsl:call-template name="isInvalid">
14444                                 <xsl:with-param name="type">ismn</xsl:with-param>
14445                         </xsl:call-template>
14446                         <xsl:if test="marc:subfield[@code='a']">
14447                                 <identifier type="ismn">
14448                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14449                                 </identifier>
14450                         </xsl:if>
14451                 </xsl:for-each>
14452                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='4']">
14453                         <xsl:call-template name="isInvalid">
14454                                 <xsl:with-param name="type">sici</xsl:with-param>
14455                         </xsl:call-template>
14456                         <identifier type="sici">
14457                                 <xsl:call-template name="subfieldSelect">
14458                                         <xsl:with-param name="codes">ab</xsl:with-param>
14459                                 </xsl:call-template>
14460                         </identifier>
14461                 </xsl:for-each>
14462                 <xsl:for-each select="marc:datafield[@tag='022']">
14463                         <xsl:call-template name="isInvalid">
14464                                 <xsl:with-param name="type">issn</xsl:with-param>
14465                         </xsl:call-template>
14466                         <identifier type="issn">
14467                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14468                         </identifier>
14469                 </xsl:for-each>
14470                 <xsl:for-each select="marc:datafield[@tag='010']">
14471                         <xsl:call-template name="isInvalid">
14472                                 <xsl:with-param name="type">lccn</xsl:with-param>
14473                         </xsl:call-template>
14474                         <identifier type="lccn">
14475                                 <xsl:value-of select="normalize-space(marc:subfield[@code='a'])"/>
14476                         </identifier>
14477                 </xsl:for-each>
14478                 <xsl:for-each select="marc:datafield[@tag='028']">
14479                         <identifier>
14480                                 <xsl:attribute name="type">
14481                                         <xsl:choose>
14482                                                 <xsl:when test="@ind1='0'">issue number</xsl:when>
14483                                                 <xsl:when test="@ind1='1'">matrix number</xsl:when>
14484                                                 <xsl:when test="@ind1='2'">music plate</xsl:when>
14485                                                 <xsl:when test="@ind1='3'">music publisher</xsl:when>
14486                                                 <xsl:when test="@ind1='4'">videorecording identifier</xsl:when>
14487                                         </xsl:choose>
14488                                 </xsl:attribute>
14489                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 028 -->
14490                                 <xsl:call-template name="subfieldSelect">
14491                                         <xsl:with-param name="codes">
14492                                                 <xsl:choose>
14493                                                         <xsl:when test="@ind1='0'">ba</xsl:when>
14494                                                         <xsl:otherwise>ab</xsl:otherwise>
14495                                                 </xsl:choose>
14496                                         </xsl:with-param>
14497                                 </xsl:call-template>
14498                         </identifier>
14499                 </xsl:for-each>
14500                 <xsl:for-each select="marc:datafield[@tag='037']">
14501                         <identifier type="stock number">
14502                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 037 -->
14503                                 <xsl:call-template name="subfieldSelect">
14504                                         <xsl:with-param name="codes">ab</xsl:with-param>
14505                                 </xsl:call-template>
14506                         </identifier>
14507                 </xsl:for-each>
14508                 <xsl:for-each select="marc:datafield[@tag='856'][marc:subfield[@code='u']]">
14509                         <identifier>
14510                                 <xsl:attribute name="type">
14511                                         <xsl:choose>
14512                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:doi') or starts-with(marc:subfield[@code='u'],'doi')">doi</xsl:when>
14513                                                 <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>
14514                                                 <xsl:otherwise>uri</xsl:otherwise>
14515                                         </xsl:choose>
14516                                 </xsl:attribute>
14517                                 <xsl:choose>
14518                                         <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') ">
14519                                                 <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14520                                         </xsl:when>
14521                                         <xsl:otherwise>
14522                                                 <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14523                                         </xsl:otherwise>
14524                                 </xsl:choose>
14525                         </identifier>
14526                         <xsl:if test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl')">
14527                                 <identifier type="hdl">
14528                                         <xsl:if test="marc:subfield[@code='y' or @code='3' or @code='z']">
14529                                                 <xsl:attribute name="displayLabel">
14530                                                         <xsl:call-template name="subfieldSelect">
14531                                                                 <xsl:with-param name="codes">y3z</xsl:with-param>
14532                                                         </xsl:call-template>
14533                                                 </xsl:attribute>
14534                                         </xsl:if>
14535                                         <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14536                                 </identifier>
14537                         </xsl:if>
14538                 </xsl:for-each>
14539                 <xsl:for-each select="marc:datafield[@tag=024][@ind1=1]">
14540                         <identifier type="upc">
14541                                 <xsl:call-template name="isInvalid"/>
14542                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14543                         </identifier>
14544                 </xsl:for-each>
14545                 <!-- 1/04 fix added $y -->
14546                 <xsl:for-each select="marc:datafield[@tag=856][marc:subfield[@code='u']]">
14547                         <location>
14548                                 <url>
14549                                         <xsl:if test="marc:subfield[@code='y' or @code='3']">
14550                                                 <xsl:attribute name="displayLabel">
14551                                                         <xsl:call-template name="subfieldSelect">
14552                                                                 <xsl:with-param name="codes">y3</xsl:with-param>
14553                                                         </xsl:call-template>
14554                                                 </xsl:attribute>
14555                                         </xsl:if>
14556                                         <xsl:if test="marc:subfield[@code='z' ]">
14557                                                 <xsl:attribute name="note">
14558                                                         <xsl:call-template name="subfieldSelect">
14559                                                                 <xsl:with-param name="codes">z</xsl:with-param>
14560                                                         </xsl:call-template>
14561                                                 </xsl:attribute>
14562                                         </xsl:if>
14563                                         <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14564
14565                                 </url>
14566                         </location>
14567                 </xsl:for-each>
14568                         
14569                         <!-- 3.2 change tmee 856z  -->
14570
14571                 
14572                 <xsl:for-each select="marc:datafield[@tag=852]">
14573                         <location>
14574                                 <physicalLocation>
14575                                         <xsl:call-template name="displayLabel"></xsl:call-template>
14576                                         <xsl:call-template name="subfieldSelect">
14577                                                 <xsl:with-param name="codes">abje</xsl:with-param>
14578                                         </xsl:call-template>
14579                                 </physicalLocation>
14580                         </location>
14581                 </xsl:for-each>
14582                 <xsl:for-each select="marc:datafield[@tag=506]">
14583                         <accessCondition type="restrictionOnAccess">
14584                                 <xsl:call-template name="subfieldSelect">
14585                                         <xsl:with-param name="codes">abcd35</xsl:with-param>
14586                                 </xsl:call-template>
14587                         </accessCondition>
14588                 </xsl:for-each>
14589                 <xsl:for-each select="marc:datafield[@tag=540]">
14590                         <accessCondition type="useAndReproduction">
14591                                 <xsl:call-template name="subfieldSelect">
14592                                         <xsl:with-param name="codes">abcde35</xsl:with-param>
14593                                 </xsl:call-template>
14594                         </accessCondition>
14595                 </xsl:for-each>
14596                 <recordInfo>
14597                         <xsl:for-each select="marc:datafield[@tag=040]">
14598                                 <recordContentSource authority="marcorg">
14599                                         <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14600                                 </recordContentSource>
14601                         </xsl:for-each>
14602                         <xsl:for-each select="marc:controlfield[@tag=008]">
14603                                 <recordCreationDate encoding="marc">
14604                                         <xsl:value-of select="substring(.,1,6)"></xsl:value-of>
14605                                 </recordCreationDate>
14606                         </xsl:for-each>
14607                         <xsl:for-each select="marc:controlfield[@tag=005]">
14608                                 <recordChangeDate encoding="iso8601">
14609                                         <xsl:value-of select="."></xsl:value-of>
14610                                 </recordChangeDate>
14611                         </xsl:for-each>
14612                         <xsl:for-each select="marc:controlfield[@tag=001]">
14613                                 <recordIdentifier>
14614                                         <xsl:if test="../marc:controlfield[@tag=003]">
14615                                                 <xsl:attribute name="source">
14616                                                         <xsl:value-of select="../marc:controlfield[@tag=003]"></xsl:value-of>
14617                                                 </xsl:attribute>
14618                                         </xsl:if>
14619                                         <xsl:value-of select="."></xsl:value-of>
14620                                 </recordIdentifier>
14621                         </xsl:for-each>
14622                         <xsl:for-each select="marc:datafield[@tag=040]/marc:subfield[@code='b']">
14623                                 <languageOfCataloging>
14624                                         <languageTerm authority="iso639-2b" type="code">
14625                                                 <xsl:value-of select="."></xsl:value-of>
14626                                         </languageTerm>
14627                                 </languageOfCataloging>
14628                         </xsl:for-each>
14629                 </recordInfo>
14630         </xsl:template>
14631         <xsl:template name="displayForm">
14632                 <xsl:for-each select="marc:subfield[@code='c']">
14633                         <displayForm>
14634                                 <xsl:value-of select="."></xsl:value-of>
14635                         </displayForm>
14636                 </xsl:for-each>
14637         </xsl:template>
14638         <xsl:template name="affiliation">
14639                 <xsl:for-each select="marc:subfield[@code='u']">
14640                         <affiliation>
14641                                 <xsl:value-of select="."></xsl:value-of>
14642                         </affiliation>
14643                 </xsl:for-each>
14644         </xsl:template>
14645         <xsl:template name="uri">
14646                 <xsl:for-each select="marc:subfield[@code='u']">
14647                         <xsl:attribute name="xlink:href">
14648                                 <xsl:value-of select="."></xsl:value-of>
14649                         </xsl:attribute>
14650                 </xsl:for-each>
14651         </xsl:template>
14652         <xsl:template name="role">
14653                 <xsl:for-each select="marc:subfield[@code='e']">
14654                         <role>
14655                                 <roleTerm type="text">
14656                                         <xsl:value-of select="."></xsl:value-of>
14657                                 </roleTerm>
14658                         </role>
14659                 </xsl:for-each>
14660                 <xsl:for-each select="marc:subfield[@code='4']">
14661                         <role>
14662                                 <roleTerm authority="marcrelator" type="code">
14663                                         <xsl:value-of select="."></xsl:value-of>
14664                                 </roleTerm>
14665                         </role>
14666                 </xsl:for-each>
14667         </xsl:template>
14668         <xsl:template name="part">
14669                 <xsl:variable name="partNumber">
14670                         <xsl:call-template name="specialSubfieldSelect">
14671                                 <xsl:with-param name="axis">n</xsl:with-param>
14672                                 <xsl:with-param name="anyCodes">n</xsl:with-param>
14673                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14674                         </xsl:call-template>
14675                 </xsl:variable>
14676                 <xsl:variable name="partName">
14677                         <xsl:call-template name="specialSubfieldSelect">
14678                                 <xsl:with-param name="axis">p</xsl:with-param>
14679                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14680                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14681                         </xsl:call-template>
14682                 </xsl:variable>
14683                 <xsl:if test="string-length(normalize-space($partNumber))">
14684                         <partNumber>
14685                                 <xsl:call-template name="chopPunctuation">
14686                                         <xsl:with-param name="chopString" select="$partNumber"></xsl:with-param>
14687                                 </xsl:call-template>
14688                         </partNumber>
14689                 </xsl:if>
14690                 <xsl:if test="string-length(normalize-space($partName))">
14691                         <partName>
14692                                 <xsl:call-template name="chopPunctuation">
14693                                         <xsl:with-param name="chopString" select="$partName"></xsl:with-param>
14694                                 </xsl:call-template>
14695                         </partName>
14696                 </xsl:if>
14697         </xsl:template>
14698         <xsl:template name="relatedPart">
14699                 <xsl:if test="@tag=773">
14700                         <xsl:for-each select="marc:subfield[@code='g']">
14701                                 <part>
14702                                         <text>
14703                                                 <xsl:value-of select="."></xsl:value-of>
14704                                         </text>
14705                                 </part>
14706                         </xsl:for-each>
14707                         <xsl:for-each select="marc:subfield[@code='q']">
14708                                 <part>
14709                                         <xsl:call-template name="parsePart"></xsl:call-template>
14710                                 </part>
14711                         </xsl:for-each>
14712                 </xsl:if>
14713         </xsl:template>
14714         <xsl:template name="relatedPartNumName">
14715                 <xsl:variable name="partNumber">
14716                         <xsl:call-template name="specialSubfieldSelect">
14717                                 <xsl:with-param name="axis">g</xsl:with-param>
14718                                 <xsl:with-param name="anyCodes">g</xsl:with-param>
14719                                 <xsl:with-param name="afterCodes">pst</xsl:with-param>
14720                         </xsl:call-template>
14721                 </xsl:variable>
14722                 <xsl:variable name="partName">
14723                         <xsl:call-template name="specialSubfieldSelect">
14724                                 <xsl:with-param name="axis">p</xsl:with-param>
14725                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14726                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14727                         </xsl:call-template>
14728                 </xsl:variable>
14729                 <xsl:if test="string-length(normalize-space($partNumber))">
14730                         <partNumber>
14731                                 <xsl:value-of select="$partNumber"></xsl:value-of>
14732                         </partNumber>
14733                 </xsl:if>
14734                 <xsl:if test="string-length(normalize-space($partName))">
14735                         <partName>
14736                                 <xsl:value-of select="$partName"></xsl:value-of>
14737                         </partName>
14738                 </xsl:if>
14739         </xsl:template>
14740         <xsl:template name="relatedName">
14741                 <xsl:for-each select="marc:subfield[@code='a']">
14742                         <name>
14743                                 <namePart>
14744                                         <xsl:value-of select="."></xsl:value-of>
14745                                 </namePart>
14746                         </name>
14747                 </xsl:for-each>
14748         </xsl:template>
14749         <xsl:template name="relatedForm">
14750                 <xsl:for-each select="marc:subfield[@code='h']">
14751                         <physicalDescription>
14752                                 <form>
14753                                         <xsl:value-of select="."></xsl:value-of>
14754                                 </form>
14755                         </physicalDescription>
14756                 </xsl:for-each>
14757         </xsl:template>
14758         <xsl:template name="relatedExtent">
14759                 <xsl:for-each select="marc:subfield[@code='h']">
14760                         <physicalDescription>
14761                                 <extent>
14762                                         <xsl:value-of select="."></xsl:value-of>
14763                                 </extent>
14764                         </physicalDescription>
14765                 </xsl:for-each>
14766         </xsl:template>
14767         <xsl:template name="relatedNote">
14768                 <xsl:for-each select="marc:subfield[@code='n']">
14769                         <note>
14770                                 <xsl:value-of select="."></xsl:value-of>
14771                         </note>
14772                 </xsl:for-each>
14773         </xsl:template>
14774         <xsl:template name="relatedSubject">
14775                 <xsl:for-each select="marc:subfield[@code='j']">
14776                         <subject>
14777                                 <temporal encoding="iso8601">
14778                                         <xsl:call-template name="chopPunctuation">
14779                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14780                                         </xsl:call-template>
14781                                 </temporal>
14782                         </subject>
14783                 </xsl:for-each>
14784         </xsl:template>
14785         <xsl:template name="relatedIdentifierISSN">
14786                 <xsl:for-each select="marc:subfield[@code='x']">
14787                         <identifier type="issn">
14788                                 <xsl:value-of select="."></xsl:value-of>
14789                         </identifier>
14790                 </xsl:for-each>
14791         </xsl:template>
14792         <xsl:template name="relatedIdentifierLocal">
14793                 <xsl:for-each select="marc:subfield[@code='w']">
14794                         <identifier type="local">
14795                                 <xsl:value-of select="."></xsl:value-of>
14796                         </identifier>
14797                 </xsl:for-each>
14798         </xsl:template>
14799         <xsl:template name="relatedIdentifier">
14800                 <xsl:for-each select="marc:subfield[@code='o']">
14801                         <identifier>
14802                                 <xsl:value-of select="."></xsl:value-of>
14803                         </identifier>
14804                 </xsl:for-each>
14805         </xsl:template>
14806         <xsl:template name="relatedItem76X-78X">
14807                 <xsl:call-template name="displayLabel"></xsl:call-template>
14808                 <xsl:call-template name="relatedTitle76X-78X"></xsl:call-template>
14809                 <xsl:call-template name="relatedName"></xsl:call-template>
14810                 <xsl:call-template name="relatedOriginInfo"></xsl:call-template>
14811                 <xsl:call-template name="relatedLanguage"></xsl:call-template>
14812                 <xsl:call-template name="relatedExtent"></xsl:call-template>
14813                 <xsl:call-template name="relatedNote"></xsl:call-template>
14814                 <xsl:call-template name="relatedSubject"></xsl:call-template>
14815                 <xsl:call-template name="relatedIdentifier"></xsl:call-template>
14816                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14817                 <xsl:call-template name="relatedIdentifierLocal"></xsl:call-template>
14818                 <xsl:call-template name="relatedPart"></xsl:call-template>
14819         </xsl:template>
14820         <xsl:template name="subjectGeographicZ">
14821                 <geographic>
14822                         <xsl:call-template name="chopPunctuation">
14823                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14824                         </xsl:call-template>
14825                 </geographic>
14826         </xsl:template>
14827         <xsl:template name="subjectTemporalY">
14828                 <temporal>
14829                         <xsl:call-template name="chopPunctuation">
14830                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14831                         </xsl:call-template>
14832                 </temporal>
14833         </xsl:template>
14834         <xsl:template name="subjectTopic">
14835                 <topic>
14836                         <xsl:call-template name="chopPunctuation">
14837                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14838                         </xsl:call-template>
14839                 </topic>
14840         </xsl:template> 
14841         <!-- 3.2 change tmee 6xx $v genre -->
14842         <xsl:template name="subjectGenre">
14843                 <genre>
14844                         <xsl:call-template name="chopPunctuation">
14845                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14846                         </xsl:call-template>
14847                 </genre>
14848         </xsl:template>
14849         
14850         <xsl:template name="nameABCDN">
14851                 <xsl:for-each select="marc:subfield[@code='a']">
14852                         <namePart>
14853                                 <xsl:call-template name="chopPunctuation">
14854                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
14855                                 </xsl:call-template>
14856                         </namePart>
14857                 </xsl:for-each>
14858                 <xsl:for-each select="marc:subfield[@code='b']">
14859                         <namePart>
14860                                 <xsl:value-of select="."></xsl:value-of>
14861                         </namePart>
14862                 </xsl:for-each>
14863                 <xsl:if test="marc:subfield[@code='c'] or marc:subfield[@code='d'] or marc:subfield[@code='n']">
14864                         <namePart>
14865                                 <xsl:call-template name="subfieldSelect">
14866                                         <xsl:with-param name="codes">cdn</xsl:with-param>
14867                                 </xsl:call-template>
14868                         </namePart>
14869                 </xsl:if>
14870         </xsl:template>
14871         <xsl:template name="nameABCDQ">
14872                 <namePart>
14873                         <xsl:call-template name="chopPunctuation">
14874                                 <xsl:with-param name="chopString">
14875                                         <xsl:call-template name="subfieldSelect">
14876                                                 <xsl:with-param name="codes">aq</xsl:with-param>
14877                                         </xsl:call-template>
14878                                 </xsl:with-param>
14879                                 <xsl:with-param name="punctuation">
14880                                         <xsl:text>:,;/ </xsl:text>
14881                                 </xsl:with-param>
14882                         </xsl:call-template>
14883                 </namePart>
14884                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
14885                 <xsl:call-template name="nameDate"></xsl:call-template>
14886         </xsl:template>
14887         <xsl:template name="nameACDEQ">
14888                 <namePart>
14889                         <xsl:call-template name="subfieldSelect">
14890                                 <xsl:with-param name="codes">acdeq</xsl:with-param>
14891                         </xsl:call-template>
14892                 </namePart>
14893         </xsl:template>
14894         <xsl:template name="constituentOrRelatedType">
14895                 <xsl:if test="@ind2=2">
14896                         <xsl:attribute name="type">constituent</xsl:attribute>
14897                 </xsl:if>
14898         </xsl:template>
14899         <xsl:template name="relatedTitle">
14900                 <xsl:for-each select="marc:subfield[@code='t']">
14901                         <titleInfo>
14902                                 <title>
14903                                         <xsl:call-template name="chopPunctuation">
14904                                                 <xsl:with-param name="chopString">
14905                                                         <xsl:value-of select="."></xsl:value-of>
14906                                                 </xsl:with-param>
14907                                         </xsl:call-template>
14908                                 </title>
14909                         </titleInfo>
14910                 </xsl:for-each>
14911         </xsl:template>
14912         <xsl:template name="relatedTitle76X-78X">
14913                 <xsl:for-each select="marc:subfield[@code='t']">
14914                         <titleInfo>
14915                                 <title>
14916                                         <xsl:call-template name="chopPunctuation">
14917                                                 <xsl:with-param name="chopString">
14918                                                         <xsl:value-of select="."></xsl:value-of>
14919                                                 </xsl:with-param>
14920                                         </xsl:call-template>
14921                                 </title>
14922                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14923                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14924                                 </xsl:if>
14925                         </titleInfo>
14926                 </xsl:for-each>
14927                 <xsl:for-each select="marc:subfield[@code='p']">
14928                         <titleInfo type="abbreviated">
14929                                 <title>
14930                                         <xsl:call-template name="chopPunctuation">
14931                                                 <xsl:with-param name="chopString">
14932                                                         <xsl:value-of select="."></xsl:value-of>
14933                                                 </xsl:with-param>
14934                                         </xsl:call-template>
14935                                 </title>
14936                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14937                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14938                                 </xsl:if>
14939                         </titleInfo>
14940                 </xsl:for-each>
14941                 <xsl:for-each select="marc:subfield[@code='s']">
14942                         <titleInfo type="uniform">
14943                                 <title>
14944                                         <xsl:call-template name="chopPunctuation">
14945                                                 <xsl:with-param name="chopString">
14946                                                         <xsl:value-of select="."></xsl:value-of>
14947                                                 </xsl:with-param>
14948                                         </xsl:call-template>
14949                                 </title>
14950                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14951                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14952                                 </xsl:if>
14953                         </titleInfo>
14954                 </xsl:for-each>
14955         </xsl:template>
14956         <xsl:template name="relatedOriginInfo">
14957                 <xsl:if test="marc:subfield[@code='b' or @code='d'] or marc:subfield[@code='f']">
14958                         <originInfo>
14959                                 <xsl:if test="@tag=775">
14960                                         <xsl:for-each select="marc:subfield[@code='f']">
14961                                                 <place>
14962                                                         <placeTerm>
14963                                                                 <xsl:attribute name="type">code</xsl:attribute>
14964                                                                 <xsl:attribute name="authority">marcgac</xsl:attribute>
14965                                                                 <xsl:value-of select="."></xsl:value-of>
14966                                                         </placeTerm>
14967                                                 </place>
14968                                         </xsl:for-each>
14969                                 </xsl:if>
14970                                 <xsl:for-each select="marc:subfield[@code='d']">
14971                                         <publisher>
14972                                                 <xsl:value-of select="."></xsl:value-of>
14973                                         </publisher>
14974                                 </xsl:for-each>
14975                                 <xsl:for-each select="marc:subfield[@code='b']">
14976                                         <edition>
14977                                                 <xsl:value-of select="."></xsl:value-of>
14978                                         </edition>
14979                                 </xsl:for-each>
14980                         </originInfo>
14981                 </xsl:if>
14982         </xsl:template>
14983         <xsl:template name="relatedLanguage">
14984                 <xsl:for-each select="marc:subfield[@code='e']">
14985                         <xsl:call-template name="getLanguage">
14986                                 <xsl:with-param name="langString">
14987                                         <xsl:value-of select="."></xsl:value-of>
14988                                 </xsl:with-param>
14989                         </xsl:call-template>
14990                 </xsl:for-each>
14991         </xsl:template>
14992         <xsl:template name="nameDate">
14993                 <xsl:for-each select="marc:subfield[@code='d']">
14994                         <namePart type="date">
14995                                 <xsl:call-template name="chopPunctuation">
14996                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
14997                                 </xsl:call-template>
14998                         </namePart>
14999                 </xsl:for-each>
15000         </xsl:template>
15001         <xsl:template name="subjectAuthority">
15002                 <xsl:if test="@ind2!=4">
15003                         <xsl:if test="@ind2!=' '">
15004                                 <xsl:if test="@ind2!=8">
15005                                         <xsl:if test="@ind2!=9">
15006                                                 <xsl:attribute name="authority">
15007                                                         <xsl:choose>
15008                                                                 <xsl:when test="@ind2=0">lcsh</xsl:when>
15009                                                                 <xsl:when test="@ind2=1">lcshac</xsl:when>
15010                                                                 <xsl:when test="@ind2=2">mesh</xsl:when>
15011                                                                 <!-- 1/04 fix -->
15012                                                                 <xsl:when test="@ind2=3">nal</xsl:when>
15013                                                                 <xsl:when test="@ind2=5">csh</xsl:when>
15014                                                                 <xsl:when test="@ind2=6">rvm</xsl:when>
15015                                                                 <xsl:when test="@ind2=7">
15016                                                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
15017                                                                 </xsl:when>
15018                                                         </xsl:choose>
15019                                                 </xsl:attribute>
15020                                         </xsl:if>
15021                                 </xsl:if>
15022                         </xsl:if>
15023                 </xsl:if>
15024         </xsl:template>
15025         <xsl:template name="subjectAnyOrder">
15026                 <xsl:for-each select="marc:subfield[@code='v' or @code='x' or @code='y' or @code='z']">
15027                         <xsl:choose>
15028                                 <xsl:when test="@code='v'">
15029                                         <xsl:call-template name="subjectGenre"></xsl:call-template>
15030                                 </xsl:when>
15031                                 <xsl:when test="@code='x'">
15032                                         <xsl:call-template name="subjectTopic"></xsl:call-template>
15033                                 </xsl:when>
15034                                 <xsl:when test="@code='y'">
15035                                         <xsl:call-template name="subjectTemporalY"></xsl:call-template>
15036                                 </xsl:when>
15037                                 <xsl:when test="@code='z'">
15038                                         <xsl:call-template name="subjectGeographicZ"></xsl:call-template>
15039                                 </xsl:when>
15040                         </xsl:choose>
15041                 </xsl:for-each>
15042         </xsl:template>
15043         <xsl:template name="specialSubfieldSelect">
15044                 <xsl:param name="anyCodes"></xsl:param>
15045                 <xsl:param name="axis"></xsl:param>
15046                 <xsl:param name="beforeCodes"></xsl:param>
15047                 <xsl:param name="afterCodes"></xsl:param>
15048                 <xsl:variable name="str">
15049                         <xsl:for-each select="marc:subfield">
15050                                 <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])">
15051                                         <xsl:value-of select="text()"></xsl:value-of>
15052                                         <xsl:text> </xsl:text>
15053                                 </xsl:if>
15054                         </xsl:for-each>
15055                 </xsl:variable>
15056                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
15057         </xsl:template>
15058         
15059         <!-- 3.2 change tmee 6xx $v genre -->
15060         <xsl:template match="marc:datafield[@tag=600]">
15061                 <subject>
15062                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15063                         <name type="personal">
15064                                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
15065                                 <namePart>
15066                                         <xsl:call-template name="chopPunctuation">
15067                                                 <xsl:with-param name="chopString">
15068                                                         <xsl:call-template name="subfieldSelect">
15069                                                                 <xsl:with-param name="codes">aq</xsl:with-param>
15070                                                         </xsl:call-template>
15071                                                 </xsl:with-param>
15072                                         </xsl:call-template>
15073                                 </namePart>
15074                                 <xsl:call-template name="nameDate"></xsl:call-template>
15075                                 <xsl:call-template name="affiliation"></xsl:call-template>
15076                                 <xsl:call-template name="role"></xsl:call-template>
15077                         </name>
15078                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15079                 </subject>
15080         </xsl:template>
15081         <xsl:template match="marc:datafield[@tag=610]">
15082                 <subject>
15083                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15084                         <name type="corporate">
15085                                 <xsl:for-each select="marc:subfield[@code='a']">
15086                                         <namePart>
15087                                                 <xsl:value-of select="."></xsl:value-of>
15088                                         </namePart>
15089                                 </xsl:for-each>
15090                                 <xsl:for-each select="marc:subfield[@code='b']">
15091                                         <namePart>
15092                                                 <xsl:value-of select="."></xsl:value-of>
15093                                         </namePart>
15094                                 </xsl:for-each>
15095                                 <xsl:if test="marc:subfield[@code='c' or @code='d' or @code='n' or @code='p']">
15096                                         <namePart>
15097                                                 <xsl:call-template name="subfieldSelect">
15098                                                         <xsl:with-param name="codes">cdnp</xsl:with-param>
15099                                                 </xsl:call-template>
15100                                         </namePart>
15101                                 </xsl:if>
15102                                 <xsl:call-template name="role"></xsl:call-template>
15103                         </name>
15104                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15105                 </subject>
15106         </xsl:template>
15107         <xsl:template match="marc:datafield[@tag=611]">
15108                 <subject>
15109                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15110                         <name type="conference">
15111                                 <namePart>
15112                                         <xsl:call-template name="subfieldSelect">
15113                                                 <xsl:with-param name="codes">abcdeqnp</xsl:with-param>
15114                                         </xsl:call-template>
15115                                 </namePart>
15116                                 <xsl:for-each select="marc:subfield[@code='4']">
15117                                         <role>
15118                                                 <roleTerm authority="marcrelator" type="code">
15119                                                         <xsl:value-of select="."></xsl:value-of>
15120                                                 </roleTerm>
15121                                         </role>
15122                                 </xsl:for-each>
15123                         </name>
15124                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15125                 </subject>
15126         </xsl:template>
15127         <xsl:template match="marc:datafield[@tag=630]">
15128                 <subject>
15129                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15130                         <titleInfo>
15131                                 <title>
15132                                         <xsl:call-template name="chopPunctuation">
15133                                                 <xsl:with-param name="chopString">
15134                                                         <xsl:call-template name="subfieldSelect">
15135                                                                 <xsl:with-param name="codes">adfhklor</xsl:with-param>
15136                                                         </xsl:call-template>
15137                                                 </xsl:with-param>
15138                                         </xsl:call-template>
15139                                         <xsl:call-template name="part"></xsl:call-template>
15140                                 </title>
15141                         </titleInfo>
15142                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15143                 </subject>
15144         </xsl:template>
15145         <xsl:template match="marc:datafield[@tag=650]">
15146                 <subject>
15147                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15148                         <topic>
15149                                 <xsl:call-template name="chopPunctuation">
15150                                         <xsl:with-param name="chopString">
15151                                                 <xsl:call-template name="subfieldSelect">
15152                                                         <xsl:with-param name="codes">abcd</xsl:with-param>
15153                                                 </xsl:call-template>
15154                                         </xsl:with-param>
15155                                 </xsl:call-template>
15156                         </topic>
15157                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15158                 </subject>
15159         </xsl:template>
15160         <xsl:template match="marc:datafield[@tag=651]">
15161                 <subject>
15162                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15163                         <xsl:for-each select="marc:subfield[@code='a']">
15164                                 <geographic>
15165                                         <xsl:call-template name="chopPunctuation">
15166                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
15167                                         </xsl:call-template>
15168                                 </geographic>
15169                         </xsl:for-each>
15170                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15171                 </subject>
15172         </xsl:template>
15173         <xsl:template match="marc:datafield[@tag=653]">
15174                 <subject>
15175                         <xsl:for-each select="marc:subfield[@code='a']">
15176                                 <topic>
15177                                         <xsl:value-of select="."></xsl:value-of>
15178                                 </topic>
15179                         </xsl:for-each>
15180                 </subject>
15181         </xsl:template>
15182         <xsl:template match="marc:datafield[@tag=656]">
15183                 <subject>
15184                         <xsl:if test="marc:subfield[@code=2]">
15185                                 <xsl:attribute name="authority">
15186                                         <xsl:value-of select="marc:subfield[@code=2]"></xsl:value-of>
15187                                 </xsl:attribute>
15188                         </xsl:if>
15189                         <occupation>
15190                                 <xsl:call-template name="chopPunctuation">
15191                                         <xsl:with-param name="chopString">
15192                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
15193                                         </xsl:with-param>
15194                                 </xsl:call-template>
15195                         </occupation>
15196                 </subject>
15197         </xsl:template>
15198         <xsl:template name="termsOfAddress">
15199                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
15200                         <namePart type="termsOfAddress">
15201                                 <xsl:call-template name="chopPunctuation">
15202                                         <xsl:with-param name="chopString">
15203                                                 <xsl:call-template name="subfieldSelect">
15204                                                         <xsl:with-param name="codes">bc</xsl:with-param>
15205                                                 </xsl:call-template>
15206                                         </xsl:with-param>
15207                                 </xsl:call-template>
15208                         </namePart>
15209                 </xsl:if>
15210         </xsl:template>
15211         <xsl:template name="displayLabel">
15212                 <xsl:if test="marc:subfield[@code='i']">
15213                         <xsl:attribute name="displayLabel">
15214                                 <xsl:value-of select="marc:subfield[@code='i']"></xsl:value-of>
15215                         </xsl:attribute>
15216                 </xsl:if>
15217                 <xsl:if test="marc:subfield[@code='3']">
15218                         <xsl:attribute name="displayLabel">
15219                                 <xsl:value-of select="marc:subfield[@code='3']"></xsl:value-of>
15220                         </xsl:attribute>
15221                 </xsl:if>
15222         </xsl:template>
15223         <xsl:template name="isInvalid">
15224                 <xsl:param name="type"/>
15225                 <xsl:if test="marc:subfield[@code='z'] or marc:subfield[@code='y']">
15226                         <identifier>
15227                                 <xsl:attribute name="type">
15228                                         <xsl:value-of select="$type"/>
15229                                 </xsl:attribute>
15230                                 <xsl:attribute name="invalid">
15231                                         <xsl:text>yes</xsl:text>
15232                                 </xsl:attribute>
15233                                 <xsl:if test="marc:subfield[@code='z']">
15234                                         <xsl:value-of select="marc:subfield[@code='z']"/>
15235                                 </xsl:if>
15236                                 <xsl:if test="marc:subfield[@code='y']">
15237                                         <xsl:value-of select="marc:subfield[@code='y']"/>
15238                                 </xsl:if>
15239                         </identifier>
15240                 </xsl:if>
15241         </xsl:template>
15242         <xsl:template name="subtitle">
15243                 <xsl:if test="marc:subfield[@code='b']">
15244                         <subTitle>
15245                                 <xsl:call-template name="chopPunctuation">
15246                                         <xsl:with-param name="chopString">
15247                                                 <xsl:value-of select="marc:subfield[@code='b']"/>
15248                                                 <!--<xsl:call-template name="subfieldSelect">
15249                                                         <xsl:with-param name="codes">b</xsl:with-param>                                                                 
15250                                                 </xsl:call-template>-->
15251                                         </xsl:with-param>
15252                                 </xsl:call-template>
15253                         </subTitle>
15254                 </xsl:if>
15255         </xsl:template>
15256         <xsl:template name="script">
15257                 <xsl:param name="scriptCode"></xsl:param>
15258                 <xsl:attribute name="script">
15259                         <xsl:choose>
15260                                 <xsl:when test="$scriptCode='(3'">Arabic</xsl:when>
15261                                 <xsl:when test="$scriptCode='(B'">Latin</xsl:when>
15262                                 <xsl:when test="$scriptCode='$1'">Chinese, Japanese, Korean</xsl:when>
15263                                 <xsl:when test="$scriptCode='(N'">Cyrillic</xsl:when>
15264                                 <xsl:when test="$scriptCode='(2'">Hebrew</xsl:when>
15265                                 <xsl:when test="$scriptCode='(S'">Greek</xsl:when>
15266                         </xsl:choose>
15267                 </xsl:attribute>
15268         </xsl:template>
15269         <xsl:template name="parsePart">
15270                 <!-- assumes 773$q= 1:2:3<4
15271                      with up to 3 levels and one optional start page
15272                 -->
15273                 <xsl:variable name="level1">
15274                         <xsl:choose>
15275                                 <xsl:when test="contains(text(),':')">
15276                                         <!-- 1:2 -->
15277                                         <xsl:value-of select="substring-before(text(),':')"></xsl:value-of>
15278                                 </xsl:when>
15279                                 <xsl:when test="not(contains(text(),':'))">
15280                                         <!-- 1 or 1<3 -->
15281                                         <xsl:if test="contains(text(),'&lt;')">
15282                                                 <!-- 1<3 -->
15283                                                 <xsl:value-of select="substring-before(text(),'&lt;')"></xsl:value-of>
15284                                         </xsl:if>
15285                                         <xsl:if test="not(contains(text(),'&lt;'))">
15286                                                 <!-- 1 -->
15287                                                 <xsl:value-of select="text()"></xsl:value-of>
15288                                         </xsl:if>
15289                                 </xsl:when>
15290                         </xsl:choose>
15291                 </xsl:variable>
15292                 <xsl:variable name="sici2">
15293                         <xsl:choose>
15294                                 <xsl:when test="starts-with(substring-after(text(),$level1),':')">
15295                                         <xsl:value-of select="substring(substring-after(text(),$level1),2)"></xsl:value-of>
15296                                 </xsl:when>
15297                                 <xsl:otherwise>
15298                                         <xsl:value-of select="substring-after(text(),$level1)"></xsl:value-of>
15299                                 </xsl:otherwise>
15300                         </xsl:choose>
15301                 </xsl:variable>
15302                 <xsl:variable name="level2">
15303                         <xsl:choose>
15304                                 <xsl:when test="contains($sici2,':')">
15305                                         <!--  2:3<4  -->
15306                                         <xsl:value-of select="substring-before($sici2,':')"></xsl:value-of>
15307                                 </xsl:when>
15308                                 <xsl:when test="contains($sici2,'&lt;')">
15309                                         <!-- 1: 2<4 -->
15310                                         <xsl:value-of select="substring-before($sici2,'&lt;')"></xsl:value-of>
15311                                 </xsl:when>
15312                                 <xsl:otherwise>
15313                                         <xsl:value-of select="$sici2"></xsl:value-of>
15314                                         <!-- 1:2 -->
15315                                 </xsl:otherwise>
15316                         </xsl:choose>
15317                 </xsl:variable>
15318                 <xsl:variable name="sici3">
15319                         <xsl:choose>
15320                                 <xsl:when test="starts-with(substring-after($sici2,$level2),':')">
15321                                         <xsl:value-of select="substring(substring-after($sici2,$level2),2)"></xsl:value-of>
15322                                 </xsl:when>
15323                                 <xsl:otherwise>
15324                                         <xsl:value-of select="substring-after($sici2,$level2)"></xsl:value-of>
15325                                 </xsl:otherwise>
15326                         </xsl:choose>
15327                 </xsl:variable>
15328                 <xsl:variable name="level3">
15329                         <xsl:choose>
15330                                 <xsl:when test="contains($sici3,'&lt;')">
15331                                         <!-- 2<4 -->
15332                                         <xsl:value-of select="substring-before($sici3,'&lt;')"></xsl:value-of>
15333                                 </xsl:when>
15334                                 <xsl:otherwise>
15335                                         <xsl:value-of select="$sici3"></xsl:value-of>
15336                                         <!-- 3 -->
15337                                 </xsl:otherwise>
15338                         </xsl:choose>
15339                 </xsl:variable>
15340                 <xsl:variable name="page">
15341                         <xsl:if test="contains(text(),'&lt;')">
15342                                 <xsl:value-of select="substring-after(text(),'&lt;')"></xsl:value-of>
15343                         </xsl:if>
15344                 </xsl:variable>
15345                 <xsl:if test="$level1">
15346                         <detail level="1">
15347                                 <number>
15348                                         <xsl:value-of select="$level1"></xsl:value-of>
15349                                 </number>
15350                         </detail>
15351                 </xsl:if>
15352                 <xsl:if test="$level2">
15353                         <detail level="2">
15354                                 <number>
15355                                         <xsl:value-of select="$level2"></xsl:value-of>
15356                                 </number>
15357                         </detail>
15358                 </xsl:if>
15359                 <xsl:if test="$level3">
15360                         <detail level="3">
15361                                 <number>
15362                                         <xsl:value-of select="$level3"></xsl:value-of>
15363                                 </number>
15364                         </detail>
15365                 </xsl:if>
15366                 <xsl:if test="$page">
15367                         <extent unit="page">
15368                                 <start>
15369                                         <xsl:value-of select="$page"></xsl:value-of>
15370                                 </start>
15371                         </extent>
15372                 </xsl:if>
15373         </xsl:template>
15374         <xsl:template name="getLanguage">
15375                 <xsl:param name="langString"></xsl:param>
15376                 <xsl:param name="controlField008-35-37"></xsl:param>
15377                 <xsl:variable name="length" select="string-length($langString)"></xsl:variable>
15378                 <xsl:choose>
15379                         <xsl:when test="$length=0"></xsl:when>
15380                         <xsl:when test="$controlField008-35-37=substring($langString,1,3)">
15381                                 <xsl:call-template name="getLanguage">
15382                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15383                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15384                                 </xsl:call-template>
15385                         </xsl:when>
15386                         <xsl:otherwise>
15387                                 <language>
15388                                         <languageTerm authority="iso639-2b" type="code">
15389                                                 <xsl:value-of select="substring($langString,1,3)"></xsl:value-of>
15390                                         </languageTerm>
15391                                 </language>
15392                                 <xsl:call-template name="getLanguage">
15393                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15394                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15395                                 </xsl:call-template>
15396                         </xsl:otherwise>
15397                 </xsl:choose>
15398         </xsl:template>
15399         <xsl:template name="isoLanguage">
15400                 <xsl:param name="currentLanguage"></xsl:param>
15401                 <xsl:param name="usedLanguages"></xsl:param>
15402                 <xsl:param name="remainingLanguages"></xsl:param>
15403                 <xsl:choose>
15404                         <xsl:when test="string-length($currentLanguage)=0"></xsl:when>
15405                         <xsl:when test="not(contains($usedLanguages, $currentLanguage))">
15406                                 <language>
15407                                         <xsl:if test="@code!='a'">
15408                                                 <xsl:attribute name="objectPart">
15409                                                         <xsl:choose>
15410                                                                 <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15411                                                                 <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15412                                                                 <xsl:when test="@code='e'">libretto</xsl:when>
15413                                                                 <xsl:when test="@code='f'">table of contents</xsl:when>
15414                                                                 <xsl:when test="@code='g'">accompanying material</xsl:when>
15415                                                                 <xsl:when test="@code='h'">translation</xsl:when>
15416                                                         </xsl:choose>
15417                                                 </xsl:attribute>
15418                                         </xsl:if>
15419                                         <languageTerm authority="iso639-2b" type="code">
15420                                                 <xsl:value-of select="$currentLanguage"></xsl:value-of>
15421                                         </languageTerm>
15422                                 </language>
15423                                 <xsl:call-template name="isoLanguage">
15424                                         <xsl:with-param name="currentLanguage">
15425                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15426                                         </xsl:with-param>
15427                                         <xsl:with-param name="usedLanguages">
15428                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15429                                         </xsl:with-param>
15430                                         <xsl:with-param name="remainingLanguages">
15431                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15432                                         </xsl:with-param>
15433                                 </xsl:call-template>
15434                         </xsl:when>
15435                         <xsl:otherwise>
15436                                 <xsl:call-template name="isoLanguage">
15437                                         <xsl:with-param name="currentLanguage">
15438                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15439                                         </xsl:with-param>
15440                                         <xsl:with-param name="usedLanguages">
15441                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15442                                         </xsl:with-param>
15443                                         <xsl:with-param name="remainingLanguages">
15444                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15445                                         </xsl:with-param>
15446                                 </xsl:call-template>
15447                         </xsl:otherwise>
15448                 </xsl:choose>
15449         </xsl:template>
15450         <xsl:template name="chopBrackets">
15451                 <xsl:param name="chopString"></xsl:param>
15452                 <xsl:variable name="string">
15453                         <xsl:call-template name="chopPunctuation">
15454                                 <xsl:with-param name="chopString" select="$chopString"></xsl:with-param>
15455                         </xsl:call-template>
15456                 </xsl:variable>
15457                 <xsl:if test="substring($string, 1,1)='['">
15458                         <xsl:value-of select="substring($string,2, string-length($string)-2)"></xsl:value-of>
15459                 </xsl:if>
15460                 <xsl:if test="substring($string, 1,1)!='['">
15461                         <xsl:value-of select="$string"></xsl:value-of>
15462                 </xsl:if>
15463         </xsl:template>
15464         <xsl:template name="rfcLanguages">
15465                 <xsl:param name="nodeNum"></xsl:param>
15466                 <xsl:param name="usedLanguages"></xsl:param>
15467                 <xsl:param name="controlField008-35-37"></xsl:param>
15468                 <xsl:variable name="currentLanguage" select="."></xsl:variable>
15469                 <xsl:choose>
15470                         <xsl:when test="not($currentLanguage)"></xsl:when>
15471                         <xsl:when test="$currentLanguage!=$controlField008-35-37 and $currentLanguage!='rfc3066'">
15472                                 <xsl:if test="not(contains($usedLanguages,$currentLanguage))">
15473                                         <language>
15474                                                 <xsl:if test="@code!='a'">
15475                                                         <xsl:attribute name="objectPart">
15476                                                                 <xsl:choose>
15477                                                                         <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15478                                                                         <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15479                                                                         <xsl:when test="@code='e'">libretto</xsl:when>
15480                                                                         <xsl:when test="@code='f'">table of contents</xsl:when>
15481                                                                         <xsl:when test="@code='g'">accompanying material</xsl:when>
15482                                                                         <xsl:when test="@code='h'">translation</xsl:when>
15483                                                                 </xsl:choose>
15484                                                         </xsl:attribute>
15485                                                 </xsl:if>
15486                                                 <languageTerm authority="rfc3066" type="code">
15487                                                         <xsl:value-of select="$currentLanguage"/>
15488                                                 </languageTerm>
15489                                         </language>
15490                                 </xsl:if>
15491                         </xsl:when>
15492                         <xsl:otherwise>
15493                         </xsl:otherwise>
15494                 </xsl:choose>
15495         </xsl:template>
15496         <xsl:template name="datafield">
15497                 <xsl:param name="tag"/>
15498                 <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param>
15499                 <xsl:param name="ind2"><xsl:text> </xsl:text></xsl:param>
15500                 <xsl:param name="subfields"/>
15501                 <xsl:element name="marc:datafield">
15502                         <xsl:attribute name="tag">
15503                                 <xsl:value-of select="$tag"/>
15504                         </xsl:attribute>
15505                         <xsl:attribute name="ind1">
15506                                 <xsl:value-of select="$ind1"/>
15507                         </xsl:attribute>
15508                         <xsl:attribute name="ind2">
15509                                 <xsl:value-of select="$ind2"/>
15510                         </xsl:attribute>
15511                         <xsl:copy-of select="$subfields"/>
15512                 </xsl:element>
15513         </xsl:template>
15514
15515         <xsl:template name="subfieldSelect">
15516                 <xsl:param name="codes"/>
15517                 <xsl:param name="delimeter"><xsl:text> </xsl:text></xsl:param>
15518                 <xsl:variable name="str">
15519                         <xsl:for-each select="marc:subfield">
15520                                 <xsl:if test="contains($codes, @code)">
15521                                         <xsl:value-of select="text()"/><xsl:value-of select="$delimeter"/>
15522                                 </xsl:if>
15523                         </xsl:for-each>
15524                 </xsl:variable>
15525                 <xsl:value-of select="substring($str,1,string-length($str)-string-length($delimeter))"/>
15526         </xsl:template>
15527
15528         <xsl:template name="buildSpaces">
15529                 <xsl:param name="spaces"/>
15530                 <xsl:param name="char"><xsl:text> </xsl:text></xsl:param>
15531                 <xsl:if test="$spaces>0">
15532                         <xsl:value-of select="$char"/>
15533                         <xsl:call-template name="buildSpaces">
15534                                 <xsl:with-param name="spaces" select="$spaces - 1"/>
15535                                 <xsl:with-param name="char" select="$char"/>
15536                         </xsl:call-template>
15537                 </xsl:if>
15538         </xsl:template>
15539
15540         <xsl:template name="chopPunctuation">
15541                 <xsl:param name="chopString"/>
15542                 <xsl:param name="punctuation"><xsl:text>.:,;/ </xsl:text></xsl:param>
15543                 <xsl:variable name="length" select="string-length($chopString)"/>
15544                 <xsl:choose>
15545                         <xsl:when test="$length=0"/>
15546                         <xsl:when test="contains($punctuation, substring($chopString,$length,1))">
15547                                 <xsl:call-template name="chopPunctuation">
15548                                         <xsl:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
15549                                         <xsl:with-param name="punctuation" select="$punctuation"/>
15550                                 </xsl:call-template>
15551                         </xsl:when>
15552                         <xsl:when test="not($chopString)"/>
15553                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15554                 </xsl:choose>
15555         </xsl:template>
15556
15557         <xsl:template name="chopPunctuationFront">
15558                 <xsl:param name="chopString"/>
15559                 <xsl:variable name="length" select="string-length($chopString)"/>
15560                 <xsl:choose>
15561                         <xsl:when test="$length=0"/>
15562                         <xsl:when test="contains('.:,;/[ ', substring($chopString,1,1))">
15563                                 <xsl:call-template name="chopPunctuationFront">
15564                                         <xsl:with-param name="chopString" select="substring($chopString,2,$length - 1)"/>
15565                                 </xsl:call-template>
15566                         </xsl:when>
15567                         <xsl:when test="not($chopString)"/>
15568                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15569                 </xsl:choose>
15570         </xsl:template>
15571 </xsl:stylesheet>$$ WHERE name = 'mods32';
15572
15573 -- Currently, the only difference from naco_normalize is that search_normalize
15574 -- turns apostrophes into spaces, while naco_normalize collapses them.
15575 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
15576
15577     use strict;
15578     use Unicode::Normalize;
15579     use Encode;
15580
15581     my $str = decode_utf8(shift);
15582     my $sf = shift;
15583
15584     # Apply NACO normalization to input string; based on
15585     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
15586     #
15587     # Note that unlike a strict reading of the NACO normalization rules,
15588     # output is returned as lowercase instead of uppercase for compatibility
15589     # with previous versions of the Evergreen naco_normalize routine.
15590
15591     # Convert to upper-case first; even though final output will be lowercase, doing this will
15592     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
15593     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
15594     $str = uc $str;
15595
15596     # remove non-filing strings
15597     $str =~ s/\x{0098}.*?\x{009C}//g;
15598
15599     $str = NFKD($str);
15600
15601     # additional substitutions - 3.6.
15602     $str =~ s/\x{00C6}/AE/g;
15603     $str =~ s/\x{00DE}/TH/g;
15604     $str =~ s/\x{0152}/OE/g;
15605     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;
15606
15607     # transformations based on Unicode category codes
15608     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
15609
15610         if ($sf && $sf =~ /^a/o) {
15611                 my $commapos = index($str, ',');
15612                 if ($commapos > -1) {
15613                         if ($commapos != length($str) - 1) {
15614                 $str =~ s/,/\x07/; # preserve first comma
15615                         }
15616                 }
15617         }
15618
15619     # since we've stripped out the control characters, we can now
15620     # use a few as placeholders temporarily
15621     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
15622     $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;
15623     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
15624
15625     # decimal digits
15626     $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/;
15627
15628     # intentionally skipping step 8 of the NACO algorithm; if the string
15629     # gets normalized away, that's fine.
15630
15631     # leading and trailing spaces
15632     $str =~ s/\s+/ /g;
15633     $str =~ s/^\s+//;
15634     $str =~ s/\s+$//g;
15635
15636     return lc $str;
15637 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
15638
15639 CREATE OR REPLACE FUNCTION public.search_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
15640         SELECT public.search_normalize($1,'a');
15641 $func$ LANGUAGE SQL STRICT IMMUTABLE;
15642
15643 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT ) RETURNS TEXT AS $func$
15644         SELECT public.search_normalize($1,'');
15645 $func$ LANGUAGE 'sql' STRICT IMMUTABLE;
15646
15647 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15648         'Search Normalize',
15649         'Apply search normalization rules to the extracted text. A less extreme version of NACO normalization.',
15650         'search_normalize',
15651         0
15652 );
15653
15654 UPDATE config.metabib_field_index_norm_map
15655     SET norm = (
15656         SELECT id FROM config.index_normalizer WHERE func = 'search_normalize'
15657     )
15658     WHERE norm = (
15659         SELECT id FROM config.index_normalizer WHERE func = 'naco_normalize'
15660     )
15661 ;
15662
15663
15664 -- This could take a long time if you have a very non-English bib database
15665 -- Run it outside of a transaction to avoid lock escalation
15666 SELECT metabib.reingest_metabib_field_entries(record)
15667     FROM metabib.full_rec
15668     WHERE tag = '245'
15669     AND subfield = 'a'
15670     AND value LIKE '%''%'
15671 ;
15672
15673 COMMIT;
15674
15675 -- This is split out because it takes forever to run on large bib collections.
15676 \qecho ************************************************************************
15677 \qecho The following transaction, wrapping upgrades 0679 and 0680, may take a
15678 \qecho *really* long time, and you might be able to run it by itself in
15679 \qecho parallel with other operations using a separate session.
15680 \qecho ************************************************************************
15681
15682 BEGIN;
15683 SELECT evergreen.upgrade_deps_block_check('0679', :eg_version);
15684
15685 -- Address typo in column name
15686 ALTER TABLE config.metabib_class ADD COLUMN buoyant BOOL DEFAULT FALSE NOT NULL;
15687 UPDATE config.metabib_class SET buoyant = bouyant;
15688 ALTER TABLE config.metabib_class DROP COLUMN bouyant;
15689
15690 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
15691 DECLARE
15692     normalizer      RECORD;
15693     value           TEXT := '';
15694 BEGIN
15695
15696     value := NEW.value;
15697
15698     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15699         FOR normalizer IN
15700             SELECT  n.func AS func,
15701                     n.param_count AS param_count,
15702                     m.params AS params
15703               FROM  config.index_normalizer n
15704                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15705               WHERE field = NEW.field AND m.pos < 0
15706               ORDER BY m.pos LOOP
15707                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15708                     quote_literal( value ) ||
15709                     CASE
15710                         WHEN normalizer.param_count > 0
15711                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15712                             ELSE ''
15713                         END ||
15714                     ')' INTO value;
15715
15716         END LOOP;
15717
15718         NEW.value := value;
15719     END IF;
15720
15721     IF NEW.index_vector = ''::tsvector THEN
15722         RETURN NEW;
15723     END IF;
15724
15725     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15726         FOR normalizer IN
15727             SELECT  n.func AS func,
15728                     n.param_count AS param_count,
15729                     m.params AS params
15730               FROM  config.index_normalizer n
15731                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15732               WHERE field = NEW.field AND m.pos >= 0
15733               ORDER BY m.pos LOOP
15734                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15735                     quote_literal( value ) ||
15736                     CASE
15737                         WHEN normalizer.param_count > 0
15738                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15739                             ELSE ''
15740                         END ||
15741                     ')' INTO value;
15742
15743         END LOOP;
15744     END IF;
15745
15746     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
15747         value :=  ARRAY_TO_STRING(
15748             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15749         );
15750         value := public.search_normalize(value);
15751     END IF;
15752
15753     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
15754
15755     RETURN NEW;
15756 END;
15757 $$ LANGUAGE PLPGSQL;
15758
15759 -- Given a string such as a user might type into a search box, prepare
15760 -- two changed variants for TO_TSQUERY(). See
15761 -- http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
15762 -- The first variant is normalized to match indexed documents regardless
15763 -- of diacritics.  The second variant keeps its diacritics for proper
15764 -- highlighting via TS_HEADLINE().
15765 CREATE OR REPLACE
15766     FUNCTION metabib.autosuggest_prepare_tsquery(orig TEXT) RETURNS TEXT[] AS
15767 $$
15768 DECLARE
15769     orig_ended_in_space     BOOLEAN;
15770     result                  RECORD;
15771     plain                   TEXT;
15772     normalized              TEXT;
15773 BEGIN
15774     orig_ended_in_space := orig ~ E'\\s$';
15775
15776     orig := ARRAY_TO_STRING(
15777         evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
15778     );
15779
15780     normalized := public.search_normalize(orig); -- also trim()s
15781     plain := trim(orig);
15782
15783     IF NOT orig_ended_in_space THEN
15784         plain := plain || ':*';
15785         normalized := normalized || ':*';
15786     END IF;
15787
15788     plain := ARRAY_TO_STRING(
15789         evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
15790     );
15791     normalized := ARRAY_TO_STRING(
15792         evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
15793     );
15794
15795     RETURN ARRAY[normalized, plain];
15796 END;
15797 $$ LANGUAGE PLPGSQL;
15798
15799
15800 -- Definition of OUT parameters changes, so must drop first
15801 DROP FUNCTION IF EXISTS metabib.suggest_browse_entries (TEXT, TEXT, TEXT, INTEGER, INTEGER, INTEGER);
15802
15803 CREATE OR REPLACE
15804     FUNCTION metabib.suggest_browse_entries(
15805         raw_query_text  TEXT,   -- actually typed by humans at the UI level
15806         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
15807         headline_opts   TEXT,   -- markup options for ts_headline()
15808         visibility_org  INTEGER,-- null if you don't want opac visibility test
15809         query_limit     INTEGER,-- use in LIMIT clause of interal query
15810         normalization   INTEGER -- argument to TS_RANK_CD()
15811     ) RETURNS TABLE (
15812         value                   TEXT,   -- plain
15813         field                   INTEGER,
15814         buoyant_and_class_match BOOL,
15815         field_match             BOOL,
15816         field_weight            INTEGER,
15817         rank                    REAL,
15818         buoyant                 BOOL,
15819         match                   TEXT    -- marked up
15820     ) AS $func$
15821 DECLARE
15822     prepared_query_texts    TEXT[];
15823     query                   TSQUERY;
15824     plain_query             TSQUERY;
15825     opac_visibility_join    TEXT;
15826     search_class_join       TEXT;
15827     r_fields                RECORD;
15828 BEGIN
15829     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
15830
15831     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
15832     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
15833
15834     IF visibility_org IS NOT NULL THEN
15835         opac_visibility_join := '
15836     JOIN asset.opac_visible_copies aovc ON (
15837         aovc.record = mbedm.source AND
15838         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
15839     )';
15840     ELSE
15841         opac_visibility_join := '';
15842     END IF;
15843
15844     -- The following determines whether we only provide suggestsons matching
15845     -- the user's selected search_class, or whether we show other suggestions
15846     -- too. The reason for MIN() is that for search_classes like
15847     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
15848     -- implication is that if title as a class doesn't have restrict,
15849     -- nor does the proper field, but the uniform field does, you're going
15850     -- to get 'false' for your overall evaluation of 'should we restrict?'
15851     -- To invert that, change from MIN() to MAX().
15852
15853     SELECT
15854         INTO r_fields
15855             MIN(cmc.restrict::INT) AS restrict_class,
15856             MIN(cmf.restrict::INT) AS restrict_field
15857         FROM metabib.search_class_to_registered_components(search_class)
15858             AS _registered (field_class TEXT, field INT)
15859         JOIN
15860             config.metabib_class cmc ON (cmc.name = _registered.field_class)
15861         LEFT JOIN
15862             config.metabib_field cmf ON (cmf.id = _registered.field);
15863
15864     -- evaluate 'should we restrict?'
15865     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
15866         search_class_join := '
15867     JOIN
15868         metabib.search_class_to_registered_components($2)
15869         AS _registered (field_class TEXT, field INT) ON (
15870             (_registered.field IS NULL AND
15871                 _registered.field_class = cmf.field_class) OR
15872             (_registered.field = cmf.id)
15873         )
15874     ';
15875     ELSE
15876         search_class_join := '
15877     LEFT JOIN
15878         metabib.search_class_to_registered_components($2)
15879         AS _registered (field_class TEXT, field INT) ON (
15880             _registered.field_class = cmc.name
15881         )
15882     ';
15883     END IF;
15884
15885     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $7, $3) FROM (SELECT DISTINCT
15886         mbe.value,
15887         cmf.id,
15888         cmc.buoyant AND _registered.field_class IS NOT NULL,
15889         _registered.field = cmf.id,
15890         cmf.weight,
15891         TS_RANK_CD(mbe.index_vector, $1, $6),
15892         cmc.buoyant
15893     FROM metabib.browse_entry_def_map mbedm
15894     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
15895     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
15896     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
15897     '  || search_class_join || opac_visibility_join ||
15898     ' WHERE $1 @@ mbe.index_vector
15899     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15900     LIMIT $5) x
15901     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15902     '   -- sic, repeat the order by clause in the outer select too
15903     USING
15904         query, search_class, headline_opts,
15905         visibility_org, query_limit, normalization, plain_query
15906         ;
15907
15908     -- sort order:
15909     --  buoyant AND chosen class = match class
15910     --  chosen field = match field
15911     --  field weight
15912     --  rank
15913     --  buoyancy
15914     --  value itself
15915
15916 END;
15917 $func$ LANGUAGE PLPGSQL;
15918
15919
15920 \qecho 
15921 \qecho The following takes about a minute per 100,000 rows in
15922 \qecho metabib.browse_entry on my development system, which is only a VM with
15923 \qecho 4 GB of memory and 2 cores.
15924 \qecho 
15925 \qecho The following is a very loose estimate of how long the next UPDATE
15926 \qecho statement would take to finish on MY machine, based on YOUR number
15927 \qecho of rows in metabib.browse_entry:
15928 \qecho 
15929
15930 SELECT (COUNT(id) / 100000.0) * INTERVAL '1 minute'
15931     AS "approximate duration of following UPDATE statement"
15932     FROM metabib.browse_entry;
15933
15934 UPDATE metabib.browse_entry SET index_vector = TO_TSVECTOR(
15935     'keyword',
15936     public.search_normalize(
15937         ARRAY_TO_STRING(
15938             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15939         )
15940     )
15941 );
15942
15943
15944 SELECT evergreen.upgrade_deps_block_check('0680', :eg_version);
15945
15946 -- Not much use in having identifier-class fields be suggestions. Credit for the idea goes to Ben Shum.
15947 UPDATE config.metabib_field SET browse_field = FALSE WHERE id < 100 AND field_class = 'identifier';
15948
15949
15950 ---------------------------------------------------------------------------
15951 -- The rest of this was tested on Evergreen Indiana's dev server, which has
15952 -- a large data set  of 2.6M bibs, and was instrumental in sussing out the
15953 -- needed adjustments.  Thanks, EG-IN!
15954 ---------------------------------------------------------------------------
15955
15956 -- GIN indexes are /much/ better for prefix matching, which is important for browse and autosuggest
15957 --Commented out the creation earlier, so we don't need to drop it here.
15958 --DROP INDEX metabib.metabib_browse_entry_index_vector_idx;
15959 CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIN (index_vector);
15960
15961
15962 -- We need thes to make the autosuggest limiting joins fast
15963 CREATE INDEX browse_entry_def_map_def_idx ON metabib.browse_entry_def_map (def);
15964 CREATE INDEX browse_entry_def_map_entry_idx ON metabib.browse_entry_def_map (entry);
15965 CREATE INDEX browse_entry_def_map_source_idx ON metabib.browse_entry_def_map (source);
15966
15967 -- In practice this will always be ~1 row, and the default of 1000 causes terrible plans
15968 ALTER FUNCTION metabib.search_class_to_registered_components(text) ROWS 1;
15969
15970 -- Reworking of the generated query to act in a sane manner in the face of large datasets
15971 CREATE OR REPLACE
15972     FUNCTION metabib.suggest_browse_entries(
15973         raw_query_text  TEXT,   -- actually typed by humans at the UI level
15974         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
15975         headline_opts   TEXT,   -- markup options for ts_headline()
15976         visibility_org  INTEGER,-- null if you don't want opac visibility test
15977         query_limit     INTEGER,-- use in LIMIT clause of interal query
15978         normalization   INTEGER -- argument to TS_RANK_CD()
15979     ) RETURNS TABLE (
15980         value                   TEXT,   -- plain
15981         field                   INTEGER,
15982         buoyant_and_class_match BOOL,
15983         field_match             BOOL,
15984         field_weight            INTEGER,
15985         rank                    REAL,
15986         buoyant                 BOOL,
15987         match                   TEXT    -- marked up
15988     ) AS $func$
15989 DECLARE
15990     prepared_query_texts    TEXT[];
15991     query                   TSQUERY;
15992     plain_query             TSQUERY;
15993     opac_visibility_join    TEXT;
15994     search_class_join       TEXT;
15995     r_fields                RECORD;
15996 BEGIN
15997     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
15998
15999     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
16000     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
16001
16002     IF visibility_org IS NOT NULL THEN
16003         opac_visibility_join := '
16004     JOIN asset.opac_visible_copies aovc ON (
16005         aovc.record = x.source AND
16006         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
16007     )';
16008     ELSE
16009         opac_visibility_join := '';
16010     END IF;
16011
16012     -- The following determines whether we only provide suggestsons matching
16013     -- the user's selected search_class, or whether we show other suggestions
16014     -- too. The reason for MIN() is that for search_classes like
16015     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
16016     -- implication is that if title as a class doesn't have restrict,
16017     -- nor does the proper field, but the uniform field does, you're going
16018     -- to get 'false' for your overall evaluation of 'should we restrict?'
16019     -- To invert that, change from MIN() to MAX().
16020
16021     SELECT
16022         INTO r_fields
16023             MIN(cmc.restrict::INT) AS restrict_class,
16024             MIN(cmf.restrict::INT) AS restrict_field
16025         FROM metabib.search_class_to_registered_components(search_class)
16026             AS _registered (field_class TEXT, field INT)
16027         JOIN
16028             config.metabib_class cmc ON (cmc.name = _registered.field_class)
16029         LEFT JOIN
16030             config.metabib_field cmf ON (cmf.id = _registered.field);
16031
16032     -- evaluate 'should we restrict?'
16033     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
16034         search_class_join := '
16035     JOIN
16036         metabib.search_class_to_registered_components($2)
16037         AS _registered (field_class TEXT, field INT) ON (
16038             (_registered.field IS NULL AND
16039                 _registered.field_class = cmf.field_class) OR
16040             (_registered.field = cmf.id)
16041         )
16042     ';
16043     ELSE
16044         search_class_join := '
16045     LEFT JOIN
16046         metabib.search_class_to_registered_components($2)
16047         AS _registered (field_class TEXT, field INT) ON (
16048             _registered.field_class = cmc.name
16049         )
16050     ';
16051     END IF;
16052
16053     RETURN QUERY EXECUTE '
16054 SELECT  DISTINCT
16055         x.value,
16056         x.id,
16057         x.push,
16058         x.restrict,
16059         x.weight,
16060         x.ts_rank_cd,
16061         x.buoyant,
16062         TS_HEADLINE(value, $7, $3)
16063   FROM  (SELECT DISTINCT
16064                 mbe.value,
16065                 cmf.id,
16066                 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
16067                 _registered.field = cmf.id AS restrict,
16068                 cmf.weight,
16069                 TS_RANK_CD(mbe.index_vector, $1, $6),
16070                 cmc.buoyant,
16071                 mbedm.source
16072           FROM  metabib.browse_entry_def_map mbedm
16073
16074                 -- Start with a pre-limited set of 10k possible suggestions. More than that is not going to be useful anyway
16075                 JOIN (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) mbe ON (mbe.id = mbedm.entry)
16076
16077                 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
16078                 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
16079                 '  || search_class_join || '
16080           ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16081           LIMIT 1000) AS x -- This outer limit makes testing for opac visibility usably fast
16082         ' || opac_visibility_join || '
16083   ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16084   LIMIT $5
16085 '   -- sic, repeat the order by clause in the outer select too
16086     USING
16087         query, search_class, headline_opts,
16088         visibility_org, query_limit, normalization, plain_query
16089         ;
16090
16091     -- sort order:
16092     --  buoyant AND chosen class = match class
16093     --  chosen field = match field
16094     --  field weight
16095     --  rank
16096     --  buoyancy
16097     --  value itself
16098
16099 END;
16100 $func$ LANGUAGE PLPGSQL;
16101
16102 COMMIT;
16103
16104 -- This is split out because it was backported to 2.1, but may not exist before upgrades
16105 -- It can safely fail
16106 -- Also, lets say that. <_<
16107 \qecho
16108 \qecho *************************************************************************
16109 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16110 \qecho We are about to apply a patch that may not be needed. It can fail safely.
16111 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16112 \qecho *************************************************************************
16113 \qecho
16114
16115 -- Evergreen DB patch 0693.schema.do_not_despace_issns.sql
16116 --
16117 -- FIXME: insert description of change, if needed
16118 --
16119 BEGIN;
16120
16121
16122 -- check whether patch can be applied
16123 SELECT evergreen.upgrade_deps_block_check('0693', :eg_version);
16124
16125 -- FIXME: add/check SQL statements to perform the upgrade
16126 -- Delete the index normalizer that was meant to remove spaces from ISSNs
16127 -- but ended up breaking records with multiple ISSNs
16128 DELETE FROM config.metabib_field_index_norm_map WHERE id IN (
16129     SELECT map.id FROM config.metabib_field_index_norm_map map
16130         INNER JOIN config.metabib_field cmf ON cmf.id = map.field
16131         INNER JOIN config.index_normalizer cin ON cin.id = map.norm
16132     WHERE cin.func = 'replace'
16133         AND cmf.field_class = 'identifier'
16134         AND cmf.name = 'issn'
16135         AND map.params = $$[" ",""]$$
16136 );
16137
16138 -- Reindex records that have more than just a single ISSN
16139 -- to ensure that spaces are maintained
16140 SELECT metabib.reingest_metabib_field_entries(source)
16141   FROM metabib.identifier_field_entry mife
16142     INNER JOIN config.metabib_field cmf ON cmf.id = mife.field
16143   WHERE cmf.field_class = 'identifier'
16144     AND cmf.name = 'issn'
16145     AND char_length(value) > 9
16146 ;
16147
16148
16149 COMMIT;
16150
16151 -- outside of any transaction
16152
16153 \qecho ************************************************************************
16154 \qecho                  Failures from here down are okay!
16155 \qecho ************************************************************************
16156
16157 SELECT evergreen.upgrade_deps_block_check('0691', :eg_version);
16158
16159 CREATE INDEX poi_po_idx ON acq.po_item (purchase_order);
16160
16161 CREATE INDEX ie_inv_idx on acq.invoice_entry (invoice);
16162 CREATE INDEX ie_po_idx on acq.invoice_entry (purchase_order);
16163 CREATE INDEX ie_li_idx on acq.invoice_entry (lineitem);
16164
16165 CREATE INDEX ii_inv_idx on acq.invoice_item (invoice);
16166 CREATE INDEX ii_po_idx on acq.invoice_item (purchase_order);
16167 CREATE INDEX ii_poi_idx on acq.invoice_item (po_item);
16168
16169 \qecho All Evergreen core database functions have been converted to
16170 \qecho use PLPERLU instead of PLPERL, so we are attempting to remove
16171 \qecho the PLPERL language here; but it is entirely possible that
16172 \qecho existing sites will have custom PLPERL functions that they
16173 \qecho will want to retain, so the following DROP LANGUAGE statement
16174 \qecho may fail, and that is okay.
16175
16176 DROP LANGUAGE plperl;
16177