]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
Version Upgrade Cleanup
[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
10086 -- FIXME: add/check SQL statements to perform the upgrade
10087 -- New function def
10088 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$
10089 DECLARE
10090     fclass          RECORD;
10091     ind_data        metabib.field_entry_template%ROWTYPE;
10092     mbe_row         metabib.browse_entry%ROWTYPE;
10093     mbe_id          BIGINT;
10094 BEGIN
10095     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10096     IF NOT FOUND THEN
10097         IF NOT skip_search THEN
10098             FOR fclass IN SELECT * FROM config.metabib_class LOOP
10099                 -- RAISE NOTICE 'Emptying out %', fclass.name;
10100                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10101             END LOOP;
10102         END IF;
10103         IF NOT skip_facet THEN
10104             DELETE FROM metabib.facet_entry WHERE source = bib_id;
10105         END IF;
10106         IF NOT skip_browse THEN
10107             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
10108         END IF;
10109     END IF;
10110
10111     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10112         IF ind_data.field < 0 THEN
10113             ind_data.field = -1 * ind_data.field;
10114         END IF;
10115
10116         IF ind_data.facet_field AND NOT skip_facet THEN
10117             INSERT INTO metabib.facet_entry (field, source, value)
10118                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10119         END IF;
10120
10121         IF ind_data.browse_field AND NOT skip_browse THEN
10122             -- A caveat about this SELECT: this should take care of replacing
10123             -- old mbe rows when data changes, but not if normalization (by
10124             -- which I mean specifically the output of
10125             -- evergreen.oils_tsearch2()) changes.  It may or may not be
10126             -- expensive to add a comparison of index_vector to index_vector
10127             -- to the WHERE clause below.
10128             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
10129             IF FOUND THEN
10130                 mbe_id := mbe_row.id;
10131             ELSE
10132                 INSERT INTO metabib.browse_entry (value) VALUES
10133                     (metabib.browse_normalize(ind_data.value, ind_data.field));
10134                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
10135             END IF;
10136
10137             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
10138                 VALUES (mbe_id, ind_data.field, ind_data.source);
10139         END IF;
10140
10141         IF ind_data.search_field AND NOT skip_search THEN
10142             EXECUTE $$
10143                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10144                     VALUES ($$ ||
10145                         quote_literal(ind_data.field) || $$, $$ ||
10146                         quote_literal(ind_data.source) || $$, $$ ||
10147                         quote_literal(ind_data.value) ||
10148                     $$);$$;
10149         END IF;
10150
10151     END LOOP;
10152
10153     RETURN;
10154 END;
10155 $func$ LANGUAGE PLPGSQL;
10156
10157 -- Delete old one
10158 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
10159
10160 -- Evergreen DB patch 0688.data.circ_history_export_csv.sql
10161 --
10162 -- FIXME: insert description of change, if needed
10163 --
10164
10165 -- check whether patch can be applied
10166 SELECT evergreen.upgrade_deps_block_check('0688', :eg_version);
10167
10168 INSERT INTO action_trigger.hook (key, core_type, description, passive)
10169 VALUES (
10170     'circ.format.history.csv',
10171     'circ',
10172     oils_i18n_gettext(
10173         'circ.format.history.csv',
10174         'Produce CSV of circulation history',
10175         'ath',
10176         'description'
10177     ),
10178     FALSE
10179 );
10180
10181 INSERT INTO action_trigger.event_definition (
10182     active, owner, name, hook, reactor, validator, group_field, template) 
10183 VALUES (
10184     TRUE, 1, 'Circ History CSV', 'circ.format.history.csv', 'ProcessTemplate', 'NOOP_True', 'usr',
10185 $$
10186 Title,Author,Call Number,Barcode,Format
10187 [%-
10188 FOR circ IN target;
10189     bibxml = helpers.unapi_bre(circ.target_copy.call_number.record, {flesh => '{mra}'});
10190     title = "";
10191     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10192         title = title _ part.textContent;
10193     END;
10194     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10195     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value') %]
10196
10197     [%- helpers.csv_datum(title) -%],
10198     [%- helpers.csv_datum(author) -%],
10199     [%- helpers.csv_datum(circ.target_copy.call_number.label) -%],
10200     [%- helpers.csv_datum(circ.target_copy.barcode) -%],
10201     [%- helpers.csv_datum(item_type) %]
10202 [%- END -%]
10203 $$
10204 );
10205
10206 INSERT INTO action_trigger.environment (event_def, path)
10207     VALUES (
10208         currval('action_trigger.event_definition_id_seq'),
10209         'target_copy.call_number'
10210     );
10211
10212
10213 -- Evergreen DB patch 0689.data.record_print_format_update.sql
10214 --
10215 -- Updates print and email templates for bib record actions
10216 --
10217
10218 -- check whether patch can be applied
10219 SELECT evergreen.upgrade_deps_block_check('0689', :eg_version);
10220
10221 UPDATE action_trigger.event_definition SET template = $$
10222 <div>
10223     <style> li { padding: 8px; margin 5px; }</style>
10224     <ol>
10225     [% FOR cbreb IN target %]
10226     [% FOR item IN cbreb.items;
10227         bre_id = item.target_biblio_record_entry;
10228
10229         bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10230         FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10231             title = title _ part.textContent;
10232         END;
10233
10234         author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10235         item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10236         publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10237         pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10238         isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10239         issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10240         upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10241         %]
10242
10243         <li>
10244             Bib ID# [% bre_id %]<br/>
10245             [% IF isbn %]ISBN: [% isbn %]<br/>[% END %]
10246             [% IF issn %]ISSN: [% issn %]<br/>[% END %]
10247             [% IF upc  %]UPC:  [% upc %]<br/>[% END %]
10248             Title: [% title %]<br />
10249             Author: [% author %]<br />
10250             Publication Info: [% publisher %] [% pubdate %]<br/>
10251             Item Type: [% item_type %]
10252         </li>
10253     [% END %]
10254     [% END %]
10255     </ol>
10256 </div>
10257 $$ 
10258 WHERE hook = 'biblio.format.record_entry.print' AND id < 100; -- sample data
10259
10260
10261 UPDATE action_trigger.event_definition SET delay = '00:00:00', template = $$
10262 [%- SET user = target.0.owner -%]
10263 To: [%- params.recipient_email || user.email %]
10264 From: [%- params.sender_email || default_sender %]
10265 Subject: Bibliographic Records
10266
10267 [% FOR cbreb IN target %]
10268 [% FOR item IN cbreb.items;
10269     bre_id = item.target_biblio_record_entry;
10270
10271     bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
10272     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
10273         title = title _ part.textContent;
10274     END;
10275
10276     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
10277     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
10278     publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
10279     pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
10280     isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
10281     issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
10282     upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
10283 %]
10284
10285 [% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
10286 [% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
10287 [% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
10288 [% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
10289 Title: [% title %]
10290 Author: [% author %]
10291 Publication Info: [% publisher %] [% pubdate %]
10292 Item Type: [% item_type %]
10293
10294 [% END %]
10295 [% END %]
10296 $$ 
10297 WHERE hook = 'biblio.format.record_entry.email' AND id < 100; -- sample data
10298
10299 -- remove a swath of unused environment entries
10300
10301 DELETE FROM action_trigger.environment env 
10302     USING action_trigger.event_definition def 
10303     WHERE env.event_def = def.id AND 
10304         env.path != 'items' AND 
10305         def.hook = 'biblio.format.record_entry.print' AND 
10306         def.id < 100; -- sample data
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         env.path != 'owner' AND 
10313         def.hook = 'biblio.format.record_entry.email' AND 
10314         def.id < 100; -- sample data
10315
10316 -- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
10317 --
10318 -- Rewrite the in-database unapi functions to include per-object limits and
10319 -- offsets, such as a maximum number of copies and call numbers for given
10320 -- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
10321 -- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
10322 -- call number).
10323 --
10324 -- Add some notion of "preferred library" that will provide copy counts
10325 -- and optionally affect the sorting of returned copies.
10326 --
10327 -- Sort copies by availability, preferring the most available copies.
10328 --
10329 -- Return located URIs.
10330 --
10331 --
10332
10333 -- check whether patch can be applied
10334 SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
10335
10336 -- The simplest way to apply all of these changes is just to replace the unapi
10337 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
10338 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
10339 -- if it does, then the attempt to create the unapi schema in the following
10340 -- transaction will also fail. Not graceful, but safe!
10341 DROP SCHEMA IF EXISTS unapi CASCADE;
10342
10343 CREATE SCHEMA unapi;
10344
10345 CREATE OR REPLACE FUNCTION evergreen.org_top()
10346 RETURNS SETOF actor.org_unit AS $$
10347     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
10348 $$ LANGUAGE SQL STABLE
10349 ROWS 1;
10350
10351 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
10352 RETURNS anyarray AS $$
10353     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
10354 $$ LANGUAGE SQL STABLE;
10355
10356 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
10357 RETURNS INTEGER AS $$
10358     WITH search_libs AS (
10359         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
10360     )
10361     SELECT COALESCE(
10362         (SELECT -10000 FROM actor.org_unit
10363          WHERE $1 = $3 AND id = $3 AND $2 IN (
10364                 SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
10365              )
10366         ),
10367         (SELECT distance FROM search_libs WHERE id = $1),
10368         10000
10369     );
10370 $$ LANGUAGE SQL STABLE;
10371
10372 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
10373 RETURNS INTEGER AS $$
10374     WITH totally_available AS (
10375         SELECT id, 0 AS avail_rank
10376         FROM config.copy_status
10377         WHERE opac_visible IS TRUE
10378             AND copy_active IS TRUE
10379             AND id != 1 -- "Checked out"
10380     ), almost_available AS (
10381         SELECT id, 10 AS avail_rank
10382         FROM config.copy_status
10383         WHERE holdable IS TRUE
10384             AND opac_visible IS TRUE
10385             AND copy_active IS FALSE
10386             OR id = 1 -- "Checked out"
10387     )
10388     SELECT COALESCE(
10389         (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
10390         (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
10391         100
10392     );
10393 $$ LANGUAGE SQL STABLE;
10394
10395 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
10396     bibid BIGINT, 
10397     ouid INT,
10398     depth INT DEFAULT NULL,
10399     slimit HSTORE DEFAULT NULL,
10400     soffset HSTORE DEFAULT NULL,
10401     pref_lib INT DEFAULT NULL
10402 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
10403     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
10404         SELECT acn.id, aou.name, acn.label_sortkey,
10405             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
10406             RANK() OVER w
10407         FROM asset.call_number acn
10408             JOIN asset.copy acp ON (acn.id = acp.call_number)
10409             JOIN actor.org_unit_descendants( $2, COALESCE(
10410                 $3, (
10411                     SELECT depth
10412                     FROM actor.org_unit_type aout
10413                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
10414                     WHERE ou.id = $2
10415                 ), $6)
10416             ) AS aou ON (acp.circ_lib = aou.id)
10417         WHERE acn.record = $1
10418             AND acn.deleted IS FALSE
10419             AND acp.deleted IS FALSE
10420         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
10421         WINDOW w AS (
10422             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
10423         )
10424     ) AS ua
10425     GROUP BY ua.id, ua.name, ua.label_sortkey
10426     ORDER BY rank, ua.name, ua.label_sortkey
10427     LIMIT ($4 -> 'acn')::INT
10428     OFFSET ($5 -> 'acn')::INT;
10429 $$
10430 LANGUAGE SQL STABLE;
10431
10432 CREATE OR REPLACE FUNCTION evergreen.located_uris (
10433     bibid BIGINT, 
10434     ouid INT,
10435     pref_lib INT DEFAULT NULL
10436 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
10437     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10438       FROM asset.call_number acn
10439            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10440            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10441            INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
10442       WHERE acn.record = $1
10443           AND acn.deleted IS FALSE
10444           AND auri.active IS TRUE
10445     UNION
10446     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
10447       FROM asset.call_number acn
10448            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
10449            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
10450            INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
10451       WHERE acn.record = $1
10452           AND acn.deleted IS FALSE
10453           AND auri.active IS TRUE;
10454 $$
10455 LANGUAGE SQL STABLE;
10456
10457 CREATE TABLE unapi.bre_output_layout (
10458     name                TEXT    PRIMARY KEY,
10459     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
10460     mime_type           TEXT    NOT NULL,
10461     feed_top            TEXT    NOT NULL,
10462     holdings_element    TEXT,
10463     title_element       TEXT,
10464     description_element TEXT,
10465     creator_element     TEXT,
10466     update_ts_element   TEXT
10467 );
10468
10469 INSERT INTO unapi.bre_output_layout
10470     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
10471         VALUES
10472     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
10473     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
10474     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
10475 ;
10476
10477 -- Dummy functions, so we can create the real ones out of order
10478 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;
10479 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;
10480 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;
10481 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;
10482 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;
10483 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;
10484 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;
10485 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;
10486 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;
10487 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;
10488 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;
10489 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;
10490 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;
10491 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;
10492 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;
10493 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;
10494 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;
10495 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;
10496 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;
10497 CREATE OR REPLACE FUNCTION unapi.bre (
10498     obj_id BIGINT,
10499     format TEXT,
10500     ename TEXT,
10501     includes TEXT[],
10502     org TEXT,
10503     depth INT DEFAULT NULL,
10504     slimit HSTORE DEFAULT NULL,
10505     soffset HSTORE DEFAULT NULL,
10506     include_xmlns BOOL DEFAULT TRUE,
10507     pref_lib INT DEFAULT NULL
10508 )
10509 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10510 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;
10511 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;
10512 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;
10513
10514 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10515     bid BIGINT,
10516     ouid INT,
10517     org TEXT,
10518     depth INT DEFAULT NULL,
10519     includes TEXT[] DEFAULT NULL::TEXT[],
10520     slimit HSTORE DEFAULT NULL,
10521     soffset HSTORE DEFAULT NULL,
10522     include_xmlns BOOL DEFAULT TRUE,
10523     pref_lib INT DEFAULT NULL
10524 )
10525 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
10526
10527 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;
10528
10529 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$
10530 DECLARE
10531     key     TEXT;
10532     output  XML;
10533 BEGIN
10534     key :=
10535         'id'        || COALESCE(obj_id::TEXT,'') ||
10536         'format'    || COALESCE(format::TEXT,'') ||
10537         'ename'     || COALESCE(ename::TEXT,'') ||
10538         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
10539         'org'       || COALESCE(org::TEXT,'') ||
10540         'depth'     || COALESCE(depth::TEXT,'') ||
10541         'slimit'    || COALESCE(slimit::TEXT,'') ||
10542         'soffset'   || COALESCE(soffset::TEXT,'') ||
10543         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
10544     -- RAISE NOTICE 'memoize key: %', key;
10545
10546     key := MD5(key);
10547     -- RAISE NOTICE 'memoize hash: %', key;
10548
10549     -- XXX cache logic ... memcached? table?
10550
10551     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;
10552     RETURN output;
10553 END;
10554 $F$ LANGUAGE PLPGSQL STABLE;
10555
10556 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$
10557 DECLARE
10558     layout          unapi.bre_output_layout%ROWTYPE;
10559     transform       config.xml_transform%ROWTYPE;
10560     item_format     TEXT;
10561     tmp_xml         TEXT;
10562     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
10563     ouid            INT;
10564     element_list    TEXT[];
10565 BEGIN
10566
10567     IF org = '-' OR org IS NULL THEN
10568         SELECT shortname INTO org FROM evergreen.org_top();
10569     END IF;
10570
10571     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10572     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10573
10574     IF layout.name IS NULL THEN
10575         RETURN NULL::XML;
10576     END IF;
10577
10578     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
10579     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
10580
10581     -- Gather the bib xml
10582     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
10583
10584     IF layout.title_element IS NOT NULL THEN
10585         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
10586     END IF;
10587
10588     IF layout.description_element IS NOT NULL THEN
10589         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
10590     END IF;
10591
10592     IF layout.creator_element IS NOT NULL THEN
10593         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
10594     END IF;
10595
10596     IF layout.update_ts_element IS NOT NULL THEN
10597         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;
10598     END IF;
10599
10600     IF unapi_url IS NOT NULL THEN
10601         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;
10602     END IF;
10603
10604     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
10605
10606     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
10607     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
10608         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
10609     END LOOP;
10610
10611     RETURN tmp_xml::XML;
10612 END;
10613 $F$ LANGUAGE PLPGSQL STABLE;
10614
10615 CREATE OR REPLACE FUNCTION unapi.bre (
10616     obj_id BIGINT,
10617     format TEXT,
10618     ename TEXT,
10619     includes TEXT[],
10620     org TEXT,
10621     depth INT DEFAULT NULL,
10622     slimit HSTORE DEFAULT NULL,
10623     soffset HSTORE DEFAULT NULL,
10624     include_xmlns BOOL DEFAULT TRUE,
10625     pref_lib INT DEFAULT NULL
10626 )
10627 RETURNS XML AS $F$
10628 DECLARE
10629     me      biblio.record_entry%ROWTYPE;
10630     layout  unapi.bre_output_layout%ROWTYPE;
10631     xfrm    config.xml_transform%ROWTYPE;
10632     ouid    INT;
10633     tmp_xml TEXT;
10634     top_el  TEXT;
10635     output  XML;
10636     hxml    XML;
10637     axml    XML;
10638 BEGIN
10639
10640     IF org = '-' OR org IS NULL THEN
10641         SELECT shortname INTO org FROM evergreen.org_top();
10642     END IF;
10643
10644     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
10645
10646     IF ouid IS NULL THEN
10647         RETURN NULL::XML;
10648     END IF;
10649
10650     IF format = 'holdings_xml' THEN -- the special case
10651         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
10652         RETURN output;
10653     END IF;
10654
10655     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
10656
10657     IF layout.name IS NULL THEN
10658         RETURN NULL::XML;
10659     END IF;
10660
10661     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
10662
10663     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
10664
10665     -- grab SVF if we need them
10666     IF ('mra' = ANY (includes)) THEN 
10667         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
10668     ELSE
10669         axml := NULL::XML;
10670     END IF;
10671
10672     -- grab holdings if we need them
10673     IF ('holdings_xml' = ANY (includes)) THEN 
10674         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
10675     ELSE
10676         hxml := NULL::XML;
10677     END IF;
10678
10679
10680     -- generate our item node
10681
10682
10683     IF format = 'marcxml' THEN
10684         tmp_xml := me.marc;
10685         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
10686            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
10687         END IF; 
10688     ELSE
10689         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
10690     END IF;
10691
10692     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
10693
10694     IF axml IS NOT NULL THEN 
10695         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
10696     END IF;
10697
10698     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
10699         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
10700     END IF;
10701
10702     IF ('bre.unapi' = ANY (includes)) THEN 
10703         output := REGEXP_REPLACE(
10704             tmp_xml,
10705             '</' || top_el || '>(.*?)',
10706             XMLELEMENT(
10707                 name abbr,
10708                 XMLATTRIBUTES(
10709                     'http://www.w3.org/1999/xhtml' AS xmlns,
10710                     'unapi-id' AS class,
10711                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
10712                 )
10713             )::TEXT || '</' || top_el || E'>\\1'
10714         );
10715     ELSE
10716         output := tmp_xml;
10717     END IF;
10718
10719     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
10720     RETURN output;
10721 END;
10722 $F$ LANGUAGE PLPGSQL STABLE;
10723
10724 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
10725     bid BIGINT,
10726     ouid INT,
10727     org TEXT,
10728     depth INT DEFAULT NULL,
10729     includes TEXT[] DEFAULT NULL::TEXT[],
10730     slimit HSTORE DEFAULT NULL,
10731     soffset HSTORE DEFAULT NULL,
10732     include_xmlns BOOL DEFAULT TRUE,
10733     pref_lib INT DEFAULT NULL
10734 )
10735 RETURNS XML AS $F$
10736      SELECT  XMLELEMENT(
10737                  name holdings,
10738                  XMLATTRIBUTES(
10739                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10740                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
10741                  ),
10742                  XMLELEMENT(
10743                      name counts,
10744                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
10745                          SELECT  XMLELEMENT(
10746                                      name count,
10747                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10748                                  )::text
10749                            FROM  asset.opac_ou_record_copy_count($2,  $1)
10750                                      UNION
10751                          SELECT  XMLELEMENT(
10752                                      name count,
10753                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10754                                  )::text
10755                            FROM  asset.staff_ou_record_copy_count($2, $1)
10756                                      UNION
10757                          SELECT  XMLELEMENT(
10758                                      name count,
10759                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
10760                                  )::text
10761                            FROM  asset.opac_ou_record_copy_count($9,  $1)
10762                                      ORDER BY 1
10763                      )x)
10764                  ),
10765                  CASE 
10766                      WHEN ('bmp' = ANY ($5)) THEN
10767                         XMLELEMENT(
10768                             name monograph_parts,
10769                             (SELECT XMLAGG(bmp) FROM (
10770                                 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)
10771                                   FROM  biblio.monograph_part
10772                                   WHERE record = $1
10773                             )x)
10774                         )
10775                      ELSE NULL
10776                  END,
10777                  XMLELEMENT(
10778                      name volumes,
10779                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
10780                         -- Physical copies
10781                         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
10782                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
10783                         UNION ALL
10784                         -- Located URIs
10785                         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
10786                         FROM evergreen.located_uris($1, $2, $9) AS uris
10787                      )x)
10788                  ),
10789                  CASE WHEN ('ssub' = ANY ($5)) THEN 
10790                      XMLELEMENT(
10791                          name subscriptions,
10792                          (SELECT XMLAGG(ssub) FROM (
10793                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
10794                               FROM  serial.subscription
10795                               WHERE record_entry = $1
10796                         )x)
10797                      )
10798                  ELSE NULL END,
10799                  CASE WHEN ('acp' = ANY ($5)) THEN 
10800                      XMLELEMENT(
10801                          name foreign_copies,
10802                          (SELECT XMLAGG(acp) FROM (
10803                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
10804                               FROM  biblio.peer_bib_copy_map p
10805                                     JOIN asset.copy c ON (p.target_copy = c.id)
10806                               WHERE NOT c.deleted AND p.peer_record = $1
10807                             LIMIT ($6 -> 'acp')::INT
10808                             OFFSET ($7 -> 'acp')::INT
10809                         )x)
10810                      )
10811                  ELSE NULL END
10812              );
10813 $F$ LANGUAGE SQL STABLE;
10814
10815 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$
10816         SELECT  XMLELEMENT(
10817                     name subscription,
10818                     XMLATTRIBUTES(
10819                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10820                         'tag:open-ils.org:U2@ssub/' || id AS id,
10821                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
10822                         start_date AS start, end_date AS end, expected_date_offset
10823                     ),
10824                     CASE 
10825                         WHEN ('sdist' = ANY ($4)) THEN
10826                             XMLELEMENT( name distributions,
10827                                 (SELECT XMLAGG(sdist) FROM (
10828                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
10829                                       FROM  serial.distribution
10830                                       WHERE subscription = ssub.id
10831                                 )x)
10832                             )
10833                         ELSE NULL
10834                     END
10835                 )
10836           FROM  serial.subscription ssub
10837           WHERE id = $1
10838           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
10839 $F$ LANGUAGE SQL STABLE;
10840
10841 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$
10842         SELECT  XMLELEMENT(
10843                     name distribution,
10844                     XMLATTRIBUTES(
10845                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10846                         'tag:open-ils.org:U2@sdist/' || id AS id,
10847                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
10848                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
10849                         unit_label_prefix, label, unit_label_suffix, summary_method
10850                     ),
10851                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
10852                     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,
10853                     CASE 
10854                         WHEN ('sstr' = ANY ($4)) THEN
10855                             XMLELEMENT( name streams,
10856                                 (SELECT XMLAGG(sstr) FROM (
10857                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10858                                       FROM  serial.stream
10859                                       WHERE distribution = sdist.id
10860                                 )x)
10861                             )
10862                         ELSE NULL
10863                     END,
10864                     XMLELEMENT( name summaries,
10865                         CASE 
10866                             WHEN ('sbsum' = ANY ($4)) THEN
10867                                 (SELECT XMLAGG(sbsum) FROM (
10868                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10869                                       FROM  serial.basic_summary
10870                                       WHERE distribution = sdist.id
10871                                 )x)
10872                             ELSE NULL
10873                         END,
10874                         CASE 
10875                             WHEN ('sisum' = ANY ($4)) THEN
10876                                 (SELECT XMLAGG(sisum) FROM (
10877                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10878                                       FROM  serial.index_summary
10879                                       WHERE distribution = sdist.id
10880                                 )x)
10881                             ELSE NULL
10882                         END,
10883                         CASE 
10884                             WHEN ('sssum' = ANY ($4)) THEN
10885                                 (SELECT XMLAGG(sssum) FROM (
10886                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
10887                                       FROM  serial.supplement_summary
10888                                       WHERE distribution = sdist.id
10889                                 )x)
10890                             ELSE NULL
10891                         END
10892                     )
10893                 )
10894           FROM  serial.distribution sdist
10895           WHERE id = $1
10896           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
10897 $F$ LANGUAGE SQL STABLE;
10898
10899 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$
10900     SELECT  XMLELEMENT(
10901                 name stream,
10902                 XMLATTRIBUTES(
10903                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10904                     'tag:open-ils.org:U2@sstr/' || id AS id,
10905                     routing_label
10906                 ),
10907                 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,
10908                 CASE 
10909                     WHEN ('sitem' = ANY ($4)) THEN
10910                         XMLELEMENT( name items,
10911                             (SELECT XMLAGG(sitem) FROM (
10912                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
10913                                   FROM  serial.item
10914                                   WHERE stream = sstr.id
10915                             )x)
10916                         )
10917                     ELSE NULL
10918                 END
10919             )
10920       FROM  serial.stream sstr
10921       WHERE id = $1
10922       GROUP BY id, routing_label, distribution;
10923 $F$ LANGUAGE SQL STABLE;
10924
10925 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$
10926     SELECT  XMLELEMENT(
10927                 name issuance,
10928                 XMLATTRIBUTES(
10929                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10930                     'tag:open-ils.org:U2@siss/' || id AS id,
10931                     create_date, edit_date, label, date_published,
10932                     holding_code, holding_type, holding_link_id
10933                 ),
10934                 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,
10935                 CASE 
10936                     WHEN ('sitem' = ANY ($4)) THEN
10937                         XMLELEMENT( name items,
10938                             (SELECT XMLAGG(sitem) FROM (
10939                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
10940                                   FROM  serial.item
10941                                   WHERE issuance = sstr.id
10942                             )x)
10943                         )
10944                     ELSE NULL
10945                 END
10946             )
10947       FROM  serial.issuance sstr
10948       WHERE id = $1
10949       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
10950 $F$ LANGUAGE SQL STABLE;
10951
10952 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$
10953         SELECT  XMLELEMENT(
10954                     name serial_item,
10955                     XMLATTRIBUTES(
10956                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10957                         'tag:open-ils.org:U2@sitem/' || id AS id,
10958                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
10959                         date_expected, date_received
10960                     ),
10961                     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,
10962                     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,
10963                     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,
10964                     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
10965 --                    XMLELEMENT( name notes,
10966 --                        CASE 
10967 --                            WHEN ('acpn' = ANY ($4)) THEN
10968 --                                (SELECT XMLAGG(acpn) FROM (
10969 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
10970 --                                      FROM  asset.copy_note
10971 --                                      WHERE owning_copy = cp.id AND pub
10972 --                                )x)
10973 --                            ELSE NULL
10974 --                        END
10975 --                    )
10976                 )
10977           FROM  serial.item sitem
10978           WHERE id = $1;
10979 $F$ LANGUAGE SQL STABLE;
10980
10981
10982 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$
10983     SELECT  XMLELEMENT(
10984                 name serial_summary,
10985                 XMLATTRIBUTES(
10986                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
10987                     'tag:open-ils.org:U2@sbsum/' || id AS id,
10988                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
10989                 ),
10990                 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
10991             )
10992       FROM  serial.supplement_summary ssum
10993       WHERE id = $1
10994       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
10995 $F$ LANGUAGE SQL STABLE;
10996
10997 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$
10998     SELECT  XMLELEMENT(
10999                 name serial_summary,
11000                 XMLATTRIBUTES(
11001                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11002                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11003                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
11004                 ),
11005                 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
11006             )
11007       FROM  serial.basic_summary ssum
11008       WHERE id = $1
11009       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11010 $F$ LANGUAGE SQL STABLE;
11011
11012 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$
11013     SELECT  XMLELEMENT(
11014                 name serial_summary,
11015                 XMLATTRIBUTES(
11016                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11017                     'tag:open-ils.org:U2@sbsum/' || id AS id,
11018                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
11019                 ),
11020                 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
11021             )
11022       FROM  serial.index_summary ssum
11023       WHERE id = $1
11024       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
11025 $F$ LANGUAGE SQL STABLE;
11026
11027
11028 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$
11029 DECLARE
11030     output XML;
11031 BEGIN
11032     IF ename = 'circlib' THEN
11033         SELECT  XMLELEMENT(
11034                     name circlib,
11035                     XMLATTRIBUTES(
11036                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11037                         id AS ident
11038                     ),
11039                     name
11040                 ) INTO output
11041           FROM  actor.org_unit aou
11042           WHERE id = obj_id;
11043     ELSE
11044         EXECUTE $$SELECT  XMLELEMENT(
11045                     name $$ || ename || $$,
11046                     XMLATTRIBUTES(
11047                         'http://open-ils.org/spec/actors/v1' AS xmlns,
11048                         'tag:open-ils.org:U2@aou/' || id AS id,
11049                         shortname, name, opac_visible
11050                     )
11051                 )
11052           FROM  actor.org_unit aou
11053          WHERE id = $1 $$ INTO output USING obj_id;
11054     END IF;
11055
11056     RETURN output;
11057
11058 END;
11059 $F$ LANGUAGE PLPGSQL STABLE;
11060
11061 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$
11062     SELECT  XMLELEMENT(
11063                 name location,
11064                 XMLATTRIBUTES(
11065                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11066                     id AS ident,
11067                     holdable,
11068                     opac_visible,
11069                     label_prefix AS prefix,
11070                     label_suffix AS suffix
11071                 ),
11072                 name
11073             )
11074       FROM  asset.copy_location
11075       WHERE id = $1;
11076 $F$ LANGUAGE SQL STABLE;
11077
11078 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$
11079     SELECT  XMLELEMENT(
11080                 name status,
11081                 XMLATTRIBUTES(
11082                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11083                     id AS ident,
11084                     holdable,
11085                     opac_visible
11086                 ),
11087                 name
11088             )
11089       FROM  config.copy_status
11090       WHERE id = $1;
11091 $F$ LANGUAGE SQL STABLE;
11092
11093 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$
11094         SELECT  XMLELEMENT(
11095                     name copy_note,
11096                     XMLATTRIBUTES(
11097                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11098                         create_date AS date,
11099                         title
11100                     ),
11101                     value
11102                 )
11103           FROM  asset.copy_note
11104           WHERE id = $1;
11105 $F$ LANGUAGE SQL STABLE;
11106
11107 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$
11108         SELECT  XMLELEMENT(
11109                     name statcat,
11110                     XMLATTRIBUTES(
11111                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11112                         sc.name,
11113                         sc.opac_visible
11114                     ),
11115                     asce.value
11116                 )
11117           FROM  asset.stat_cat_entry asce
11118                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
11119           WHERE asce.id = $1;
11120 $F$ LANGUAGE SQL STABLE;
11121
11122 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$
11123         SELECT  XMLELEMENT(
11124                     name monograph_part,
11125                     XMLATTRIBUTES(
11126                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11127                         'tag:open-ils.org:U2@bmp/' || id AS id,
11128                         id AS ident,
11129                         label,
11130                         label_sortkey,
11131                         'tag:open-ils.org:U2@bre/' || record AS record
11132                     ),
11133                     CASE 
11134                         WHEN ('acp' = ANY ($4)) THEN
11135                             XMLELEMENT( name copies,
11136                                 (SELECT XMLAGG(acp) FROM (
11137                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
11138                                       FROM  asset.copy cp
11139                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
11140                                       WHERE cpm.part = $1
11141                                           AND cp.deleted IS FALSE
11142                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
11143                                       LIMIT ($7 -> 'acp')::INT
11144                                       OFFSET ($8 -> 'acp')::INT
11145
11146                                 )x)
11147                             )
11148                         ELSE NULL
11149                     END,
11150                     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
11151                 )
11152           FROM  biblio.monograph_part
11153           WHERE id = $1
11154           GROUP BY id, label, label_sortkey, record;
11155 $F$ LANGUAGE SQL STABLE;
11156
11157 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$
11158         SELECT  XMLELEMENT(
11159                     name copy,
11160                     XMLATTRIBUTES(
11161                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11162                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11163                         create_date, edit_date, copy_number, circulate, deposit,
11164                         ref, holdable, deleted, deposit_amount, price, barcode,
11165                         circ_modifier, circ_as_type, opac_visible, age_protect
11166                     ),
11167                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11168                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
11169                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11170                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
11171                     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,
11172                     CASE 
11173                         WHEN ('acpn' = ANY ($4)) THEN
11174                             XMLELEMENT( name copy_notes,
11175                                 (SELECT XMLAGG(acpn) FROM (
11176                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11177                                       FROM  asset.copy_note
11178                                       WHERE owning_copy = cp.id AND pub
11179                                 )x)
11180                             )
11181                         ELSE NULL
11182                     END,
11183                     CASE 
11184                         WHEN ('ascecm' = ANY ($4)) THEN
11185                             XMLELEMENT( name statcats,
11186                                 (SELECT XMLAGG(ascecm) FROM (
11187                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11188                                       FROM  asset.stat_cat_entry_copy_map
11189                                       WHERE owning_copy = cp.id
11190                                 )x)
11191                             )
11192                         ELSE NULL
11193                     END,
11194                     CASE
11195                         WHEN ('bre' = ANY ($4)) THEN
11196                             XMLELEMENT( name foreign_records,
11197                                 (SELECT XMLAGG(bre) FROM (
11198                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11199                                       FROM  biblio.peer_bib_copy_map
11200                                       WHERE target_copy = cp.id
11201                                 )x)
11202
11203                             )
11204                         ELSE NULL
11205                     END,
11206                     CASE 
11207                         WHEN ('bmp' = ANY ($4)) THEN
11208                             XMLELEMENT( name monograph_parts,
11209                                 (SELECT XMLAGG(bmp) FROM (
11210                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11211                                       FROM  asset.copy_part_map
11212                                       WHERE target_copy = cp.id
11213                                 )x)
11214                             )
11215                         ELSE NULL
11216                     END,
11217                     CASE 
11218                         WHEN ('circ' = ANY ($4)) THEN
11219                             XMLELEMENT( name current_circulation,
11220                                 (SELECT XMLAGG(circ) FROM (
11221                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11222                                       FROM  action.circulation
11223                                       WHERE target_copy = cp.id
11224                                             AND checkin_time IS NULL
11225                                 )x)
11226                             )
11227                         ELSE NULL
11228                     END
11229                 )
11230           FROM  asset.copy cp
11231           WHERE id = $1
11232               AND cp.deleted IS FALSE
11233           GROUP BY id, status, location, circ_lib, call_number, create_date,
11234               edit_date, copy_number, circulate, deposit, ref, holdable,
11235               deleted, deposit_amount, price, barcode, circ_modifier,
11236               circ_as_type, opac_visible, age_protect;
11237 $F$ LANGUAGE SQL STABLE;
11238
11239 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$
11240         SELECT  XMLELEMENT(
11241                     name serial_unit,
11242                     XMLATTRIBUTES(
11243                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11244                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
11245                         create_date, edit_date, copy_number, circulate, deposit,
11246                         ref, holdable, deleted, deposit_amount, price, barcode,
11247                         circ_modifier, circ_as_type, opac_visible, age_protect,
11248                         status_changed_time, floating, mint_condition,
11249                         detailed_contents, sort_key, summary_contents, cost 
11250                     ),
11251                     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),
11252                     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),
11253                     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),
11254                     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),
11255                     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,
11256                     XMLELEMENT( name copy_notes,
11257                         CASE 
11258                             WHEN ('acpn' = ANY ($4)) THEN
11259                                 (SELECT XMLAGG(acpn) FROM (
11260                                     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)
11261                                       FROM  asset.copy_note
11262                                       WHERE owning_copy = cp.id AND pub
11263                                 )x)
11264                             ELSE NULL
11265                         END
11266                     ),
11267                     XMLELEMENT( name statcats,
11268                         CASE 
11269                             WHEN ('ascecm' = ANY ($4)) THEN
11270                                 (SELECT XMLAGG(ascecm) FROM (
11271                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11272                                       FROM  asset.stat_cat_entry_copy_map
11273                                       WHERE owning_copy = cp.id
11274                                 )x)
11275                             ELSE NULL
11276                         END
11277                     ),
11278                     XMLELEMENT( name foreign_records,
11279                         CASE
11280                             WHEN ('bre' = ANY ($4)) THEN
11281                                 (SELECT XMLAGG(bre) FROM (
11282                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
11283                                       FROM  biblio.peer_bib_copy_map
11284                                       WHERE target_copy = cp.id
11285                                 )x)
11286                             ELSE NULL
11287                         END
11288                     ),
11289                     CASE 
11290                         WHEN ('bmp' = ANY ($4)) THEN
11291                             XMLELEMENT( name monograph_parts,
11292                                 (SELECT XMLAGG(bmp) FROM (
11293                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
11294                                       FROM  asset.copy_part_map
11295                                       WHERE target_copy = cp.id
11296                                 )x)
11297                             )
11298                         ELSE NULL
11299                     END,
11300                     CASE 
11301                         WHEN ('circ' = ANY ($4)) THEN
11302                             XMLELEMENT( name current_circulation,
11303                                 (SELECT XMLAGG(circ) FROM (
11304                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
11305                                       FROM  action.circulation
11306                                       WHERE target_copy = cp.id
11307                                             AND checkin_time IS NULL
11308                                 )x)
11309                             )
11310                         ELSE NULL
11311                     END
11312                 )
11313           FROM  serial.unit cp
11314           WHERE id = $1
11315               AND cp.deleted IS FALSE
11316           GROUP BY id, status, location, circ_lib, call_number, create_date,
11317               edit_date, copy_number, circulate, floating, mint_condition,
11318               deposit, ref, holdable, deleted, deposit_amount, price,
11319               barcode, circ_modifier, circ_as_type, opac_visible,
11320               status_changed_time, detailed_contents, sort_key,
11321               summary_contents, cost, age_protect;
11322 $F$ LANGUAGE SQL STABLE;
11323
11324 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$
11325         SELECT  XMLELEMENT(
11326                     name volume,
11327                     XMLATTRIBUTES(
11328                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11329                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
11330                         acn.id AS vol_id, o.shortname AS lib,
11331                         o.opac_visible AS opac_visible,
11332                         deleted, label, label_sortkey, label_class, record
11333                     ),
11334                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
11335                     CASE 
11336                         WHEN ('acp' = ANY ($4)) THEN
11337                             CASE WHEN $6 IS NOT NULL THEN
11338                                 XMLELEMENT( name copies,
11339                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11340                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11341                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11342                                           FROM  asset.copy cp
11343                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
11344                                           WHERE cp.call_number = acn.id
11345                                               AND cp.deleted IS FALSE
11346                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11347                                           LIMIT ($7 -> 'acp')::INT
11348                                           OFFSET ($8 -> 'acp')::INT
11349                                     )x)
11350                                 )
11351                             ELSE
11352                                 XMLELEMENT( name copies,
11353                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
11354                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11355                                             evergreen.rank_cp_status(cp.status) AS rank_avail
11356                                           FROM  asset.copy cp
11357                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
11358                                           WHERE cp.call_number = acn.id
11359                                               AND cp.deleted IS FALSE
11360                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
11361                                           LIMIT ($7 -> 'acp')::INT
11362                                           OFFSET ($8 -> 'acp')::INT
11363                                     )x)
11364                                 )
11365                             END
11366                         ELSE NULL
11367                     END,
11368                     XMLELEMENT(
11369                         name uris,
11370                         (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)
11371                     ),
11372                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11373                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
11374                     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
11375                 ) AS x
11376           FROM  asset.call_number acn
11377                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
11378           WHERE acn.id = $1
11379               AND acn.deleted IS FALSE
11380           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
11381 $F$ LANGUAGE SQL STABLE;
11382
11383 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$
11384         SELECT  XMLELEMENT(
11385                     name call_number_prefix,
11386                     XMLATTRIBUTES(
11387                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11388                         id AS ident,
11389                         label,
11390                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11391                         label_sortkey
11392                     )
11393                 )
11394           FROM  asset.call_number_prefix
11395           WHERE id = $1;
11396 $F$ LANGUAGE SQL STABLE;
11397
11398 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$
11399         SELECT  XMLELEMENT(
11400                     name call_number_suffix,
11401                     XMLATTRIBUTES(
11402                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11403                         id AS ident,
11404                         label,
11405                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
11406                         label_sortkey
11407                     )
11408                 )
11409           FROM  asset.call_number_suffix
11410           WHERE id = $1;
11411 $F$ LANGUAGE SQL STABLE;
11412
11413 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$
11414         SELECT  XMLELEMENT(
11415                     name uri,
11416                     XMLATTRIBUTES(
11417                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11418                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
11419                         use_restriction,
11420                         href,
11421                         label
11422                     ),
11423                     CASE 
11424                         WHEN ('acn' = ANY ($4)) THEN
11425                             XMLELEMENT( name copies,
11426                                 (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)
11427                             )
11428                         ELSE NULL
11429                     END
11430                 ) AS x
11431           FROM  asset.uri uri
11432           WHERE uri.id = $1
11433           GROUP BY uri.id, use_restriction, href, label;
11434 $F$ LANGUAGE SQL STABLE;
11435
11436 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$
11437         SELECT  XMLELEMENT(
11438                     name attributes,
11439                     XMLATTRIBUTES(
11440                         CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
11441                         'tag:open-ils.org:U2@mra/' || mra.id AS id,
11442                         'tag:open-ils.org:U2@bre/' || mra.id AS record
11443                     ),
11444                     (SELECT XMLAGG(foo.y)
11445                       FROM (SELECT XMLELEMENT(
11446                                 name field,
11447                                 XMLATTRIBUTES(
11448                                     key AS name,
11449                                     cvm.value AS "coded-value",
11450                                     rad.filter,
11451                                     rad.sorter
11452                                 ),
11453                                 x.value
11454                             )
11455                            FROM EACH(mra.attrs) AS x
11456                                 JOIN config.record_attr_definition rad ON (x.key = rad.name)
11457                                 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
11458                         )foo(y)
11459                     )
11460                 )
11461           FROM  metabib.record_attr mra
11462           WHERE mra.id = $1;
11463 $F$ LANGUAGE SQL STABLE;
11464
11465 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$
11466     SELECT XMLELEMENT(
11467         name circ,
11468         XMLATTRIBUTES(
11469             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
11470             'tag:open-ils.org:U2@circ/' || id AS id,
11471             xact_start,
11472             due_date
11473         ),
11474         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,
11475         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
11476     )
11477     FROM action.circulation
11478     WHERE id = $1;
11479 $F$ LANGUAGE SQL STABLE;
11480
11481 /*
11482
11483  -- Some test queries
11484
11485 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11486 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11487 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
11488
11489 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>');
11490
11491 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>');
11492 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>');
11493 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>');
11494 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>');
11495
11496 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
11497 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
11498 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
11499 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
11500 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
11501
11502 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
11503 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
11504
11505 */
11506
11507
11508 SELECT evergreen.upgrade_deps_block_check('0691', :eg_version);
11509
11510 CREATE INDEX poi_po_idx ON acq.po_item (purchase_order);
11511
11512 CREATE INDEX ie_inv_idx on acq.invoice_entry (invoice);
11513 CREATE INDEX ie_po_idx on acq.invoice_entry (purchase_order);
11514 CREATE INDEX ie_li_idx on acq.invoice_entry (lineitem);
11515
11516 CREATE INDEX ii_inv_idx on acq.invoice_item (invoice);
11517 CREATE INDEX ii_po_idx on acq.invoice_item (purchase_order);
11518 CREATE INDEX ii_poi_idx on acq.invoice_item (po_item);
11519
11520
11521 SELECT evergreen.upgrade_deps_block_check('0692', :eg_version);
11522
11523 INSERT INTO config.org_unit_setting_type
11524     (name, label, description, grp, datatype)
11525     VALUES (
11526         'circ.fines.charge_when_closed',
11527          oils_i18n_gettext(
11528             'circ.fines.charge_when_closed',
11529             'Charge fines on overdue circulations when closed',
11530             'coust',
11531             'label'
11532         ),
11533         oils_i18n_gettext(
11534             'circ.fines.charge_when_closed',
11535             '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.',
11536             'coust',
11537             'description'
11538         ),
11539         'circ',
11540         'bool'
11541     );
11542
11543 SELECT evergreen.upgrade_deps_block_check('0694', :eg_version);
11544
11545 INSERT into config.org_unit_setting_type
11546 ( name, grp, label, description, datatype, fm_class ) VALUES
11547
11548 ( 'ui.patron.edit.au.prefix.require', 'gui',
11549     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11550         'Require prefix field on patron registration',
11551         'coust', 'label'),
11552     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
11553         'The prefix field will be required on the patron registration screen.',
11554         'coust', 'description'),
11555     'bool', null)
11556         
11557 ,( 'ui.patron.edit.au.prefix.show', 'gui',
11558     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11559         'Show prefix field on patron registration',
11560         'coust', 'label'),
11561     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
11562         '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.',
11563         'coust', 'description'),
11564     'bool', null)
11565
11566 ,( 'ui.patron.edit.au.prefix.suggest', 'gui',
11567     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11568         'Suggest prefix field on patron registration',
11569         'coust', 'label'),
11570     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
11571         '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.',
11572         'coust', 'description'),
11573     'bool', null)
11574 ;               
11575
11576
11577 -- Evergreen DB patch 0695.schema.custom_toolbars.sql
11578 --
11579 -- FIXME: insert description of change, if needed
11580 --
11581
11582 -- check whether patch can be applied
11583 SELECT evergreen.upgrade_deps_block_check('0695', :eg_version);
11584
11585 CREATE TABLE actor.toolbar (
11586     id          BIGSERIAL   PRIMARY KEY,
11587     ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
11588     org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
11589     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
11590     label       TEXT        NOT NULL,
11591     layout      TEXT        NOT NULL,
11592     CONSTRAINT only_one_type CHECK (
11593         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
11594         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
11595         (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
11596     ),
11597     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
11598 );
11599 CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
11600 CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
11601 CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
11602
11603 -- this one unrelated to toolbars but is a gap in the upgrade scripts
11604 INSERT INTO permission.perm_list ( id, code, description )
11605     SELECT
11606         522,
11607         'IMPORT_AUTHORITY_MARC',
11608         oils_i18n_gettext(
11609             522,
11610             'Allows a user to create new authority records',
11611             'ppl',
11612             'description'
11613         )
11614     WHERE NOT EXISTS (
11615         SELECT 1
11616         FROM permission.perm_list
11617         WHERE
11618             id = 522
11619     );
11620
11621 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
11622     523,
11623     'ADMIN_TOOLBAR',
11624     oils_i18n_gettext(
11625         523,
11626         'Allows a user to create, edit, and delete custom toolbars',
11627         'ppl',
11628         'description'
11629     )
11630 );
11631
11632 -- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
11633 -- 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';
11634
11635 INSERT INTO actor.toolbar(org,label,layout) VALUES
11636     ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
11637     ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
11638
11639 -- 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 ;
11640
11641 -- Evergreen DB patch 0696.no_plperl.sql
11642 --
11643 -- FIXME: insert description of change, if needed
11644 --
11645
11646 -- check whether patch can be applied
11647 SELECT evergreen.upgrade_deps_block_check('0696', :eg_version);
11648
11649 -- Re-create these as plperlu instead of plperl
11650 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
11651     $_SHARED{"eg_audit_user"} = $_[0];
11652     $_SHARED{"eg_audit_ws"} = $_[1];
11653 $$ LANGUAGE plperlu;
11654
11655 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
11656     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
11657 $$ LANGUAGE plperlu;
11658
11659 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
11660     delete($_SHARED{"eg_audit_user"});
11661     delete($_SHARED{"eg_audit_ws"});
11662 $$ LANGUAGE plperlu;
11663
11664 -- And remove the language so that we don't use it later.
11665 DROP LANGUAGE plperl;
11666
11667 -- Evergreen DB patch 0697.data.place_currently_unfillable_hold.sql
11668 --
11669 -- FIXME: insert description of change, if needed
11670 --
11671
11672 -- check whether patch can be applied
11673 SELECT evergreen.upgrade_deps_block_check('0697', :eg_version);
11674
11675 -- FIXME: add/check SQL statements to perform the upgrade
11676 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11677  ( 524, 'PLACE_UNFILLABLE_HOLD', oils_i18n_gettext( 524,
11678     'Allows a user to place a hold that cannot currently be filled.', 'ppl', 'description' ));
11679
11680 -- Evergreen DB patch 0698.hold_default_pickup.sql
11681 --
11682 -- FIXME: insert description of change, if needed
11683 --
11684
11685 -- check whether patch can be applied
11686 SELECT evergreen.upgrade_deps_block_check('0698', :eg_version);
11687
11688 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
11689     VALUES ('opac.default_pickup_location', TRUE, 'Default Hold Pickup Location', 'Default location for holds pickup', 'integer');
11690
11691 SELECT evergreen.upgrade_deps_block_check('0699', :eg_version);
11692
11693 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, grp )
11694     VALUES (
11695         'ui.hide_copy_editor_fields',
11696         oils_i18n_gettext(
11697             'ui.hide_copy_editor_fields',
11698             'GUI: Hide these fields within the Item Attribute Editor',
11699             'coust',
11700             'label'
11701         ),
11702         oils_i18n_gettext(
11703             'ui.hide_copy_editor_fields',
11704             'This setting may be best maintained with the dedicated configuration'
11705             || ' interface within the Item Attribute Editor.  However, here it'
11706             || ' shows up as comma separated list of field identifiers to hide.',
11707             'coust',
11708             'description'
11709         ),
11710         'array',
11711         'gui'
11712     );
11713
11714
11715 SELECT evergreen.upgrade_deps_block_check('0700', :eg_version);
11716 SELECT evergreen.upgrade_deps_block_check('0706', :eg_version);
11717
11718 -- This throws away data, but only data that causes breakage anyway.
11719 UPDATE serial.issuance SET holding_code = NULL WHERE NOT is_json(holding_code);
11720
11721 -- If we don't do this, we have unprocessed triggers and we can't alter the table
11722 SET CONSTRAINTS serial.issuance_caption_and_pattern_fkey IMMEDIATE;
11723
11724 ALTER TABLE serial.issuance ADD CHECK (holding_code IS NULL OR is_json(holding_code));
11725
11726 INSERT INTO config.internal_flag (name, value, enabled) VALUES (
11727     'serial.rematerialize_on_same_holding_code', NULL, FALSE
11728 );
11729
11730 INSERT INTO config.org_unit_setting_type (
11731     name, label, grp, description, datatype
11732 ) VALUES (
11733     'serial.default_display_grouping',
11734     'Default display grouping for serials distributions presented in the OPAC.',
11735     'serial',
11736     'Default display grouping for serials distributions presented in the OPAC. This can be "enum" or "chron".',
11737     'string'
11738 );
11739
11740 ALTER TABLE serial.distribution
11741     ADD COLUMN display_grouping TEXT NOT NULL DEFAULT 'chron'
11742         CHECK (display_grouping IN ('enum', 'chron'));
11743
11744 -- why didn't we just make one summary table in the first place?
11745 CREATE VIEW serial.any_summary AS
11746     SELECT
11747         'basic' AS summary_type, id, distribution,
11748         generated_coverage, textual_holdings, show_generated
11749     FROM serial.basic_summary
11750     UNION
11751     SELECT
11752         'index' AS summary_type, id, distribution,
11753         generated_coverage, textual_holdings, show_generated
11754     FROM serial.index_summary
11755     UNION
11756     SELECT
11757         'supplement' AS summary_type, id, distribution,
11758         generated_coverage, textual_holdings, show_generated
11759     FROM serial.supplement_summary ;
11760
11761
11762 -- Given the IDs of two rows in actor.org_unit, *the second being an ancestor
11763 -- of the first*, return in array form the path from the ancestor to the
11764 -- descendant, with each point in the path being an org_unit ID.  This is
11765 -- useful for sorting org_units by their position in a depth-first (display
11766 -- order) representation of the tree.
11767 --
11768 -- This breaks with the precedent set by actor.org_unit_full_path() and others,
11769 -- and gets the parameters "backwards," but otherwise this function would
11770 -- not be very usable within json_query.
11771 CREATE OR REPLACE FUNCTION actor.org_unit_simple_path(INT, INT)
11772 RETURNS INT[] AS $$
11773     WITH RECURSIVE descendant_depth(id, path) AS (
11774         SELECT  aou.id,
11775                 ARRAY[aou.id]
11776           FROM  actor.org_unit aou
11777                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11778           WHERE aou.id = $2
11779             UNION ALL
11780         SELECT  aou.id,
11781                 dd.path || ARRAY[aou.id]
11782           FROM  actor.org_unit aou
11783                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
11784                 JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
11785     ) SELECT dd.path
11786         FROM actor.org_unit aou
11787         JOIN descendant_depth dd USING (id)
11788         WHERE aou.id = $1 ORDER BY dd.path;
11789 $$ LANGUAGE SQL STABLE;
11790
11791 CREATE TABLE serial.materialized_holding_code (
11792     id BIGSERIAL PRIMARY KEY,
11793     issuance INTEGER NOT NULL REFERENCES serial.issuance (id) ON DELETE CASCADE,
11794     subfield CHAR,
11795     value TEXT
11796 );
11797
11798 CREATE OR REPLACE FUNCTION serial.materialize_holding_code() RETURNS TRIGGER
11799 AS $func$ 
11800 use strict;
11801
11802 use MARC::Field;
11803 use JSON::XS;
11804
11805 if (not defined $_TD->{new}{holding_code}) {
11806     elog(WARNING, 'NULL in "holding_code" column of serial.issuance allowed for now, but may not be useful');
11807     return;
11808 }
11809
11810 # Do nothing if holding_code has not changed...
11811
11812 if ($_TD->{new}{holding_code} eq $_TD->{old}{holding_code}) {
11813     # ... unless the following internal flag is set.
11814
11815     my $flag_rv = spi_exec_query(q{
11816         SELECT * FROM config.internal_flag
11817         WHERE name = 'serial.rematerialize_on_same_holding_code' AND enabled
11818     }, 1);
11819     return unless $flag_rv->{processed};
11820 }
11821
11822
11823 my $holding_code = (new JSON::XS)->decode($_TD->{new}{holding_code});
11824
11825 my $field = new MARC::Field('999', @$holding_code); # tag doesnt matter
11826
11827 my $dstmt = spi_prepare(
11828     'DELETE FROM serial.materialized_holding_code WHERE issuance = $1',
11829     'INT'
11830 );
11831 spi_exec_prepared($dstmt, $_TD->{new}{id});
11832
11833 my $istmt = spi_prepare(
11834     q{
11835         INSERT INTO serial.materialized_holding_code (
11836             issuance, subfield, value
11837         ) VALUES ($1, $2, $3)
11838     }, qw{INT CHAR TEXT}
11839 );
11840
11841 foreach ($field->subfields) {
11842     spi_exec_prepared(
11843         $istmt,
11844         $_TD->{new}{id},
11845         $_->[0],
11846         $_->[1]
11847     );
11848 }
11849
11850 return;
11851
11852 $func$ LANGUAGE 'plperlu';
11853
11854 CREATE INDEX assist_holdings_display
11855     ON serial.materialized_holding_code (issuance, subfield);
11856
11857 CREATE TRIGGER materialize_holding_code
11858     AFTER INSERT OR UPDATE ON serial.issuance
11859     FOR EACH ROW EXECUTE PROCEDURE serial.materialize_holding_code() ;
11860
11861 -- starting here, we materialize all existing holding codes.
11862
11863 UPDATE config.internal_flag
11864     SET enabled = TRUE
11865     WHERE name = 'serial.rematerialize_on_same_holding_code';
11866
11867 UPDATE serial.issuance SET holding_code = holding_code;
11868
11869 UPDATE config.internal_flag
11870     SET enabled = FALSE
11871     WHERE name = 'serial.rematerialize_on_same_holding_code';
11872
11873 -- finish holding code materialization process
11874
11875 -- fix up missing holding_code fields from serial.issuance
11876 UPDATE serial.issuance siss
11877     SET holding_type = scap.type
11878     FROM serial.caption_and_pattern scap
11879     WHERE scap.id = siss.caption_and_pattern AND siss.holding_type IS NULL;
11880
11881
11882 -- Evergreen DB patch 0701.schema.patron_stat_category_enhancements.sql
11883 --
11884 -- Enables users to set patron statistical categories as required,
11885 -- whether or not users can input free text for the category value.
11886 -- Enables administrators to set an entry as the default for any
11887 -- given patron statistical category and org unit.
11888 --
11889
11890 -- check whether patch can be applied
11891 SELECT evergreen.upgrade_deps_block_check('0701', :eg_version);
11892
11893 -- New table
11894
11895 CREATE TABLE actor.stat_cat_entry_default (
11896     id              SERIAL  PRIMARY KEY,
11897     stat_cat_entry  INT     NOT NULL REFERENCES actor.stat_cat_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11898     stat_cat        INT     NOT NULL REFERENCES actor.stat_cat (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11899     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
11900     CONSTRAINT sced_once_per_owner UNIQUE (stat_cat,owner)
11901 );
11902
11903 COMMENT ON TABLE actor.stat_cat_entry_default IS $$
11904 User Statistical Category Default Entry
11905
11906 A library may choose one of the stat_cat entries to be the
11907 default entry.
11908 $$;
11909
11910 -- Add columns to existing tables
11911
11912 -- Patron stat cat required column
11913 ALTER TABLE actor.stat_cat
11914     ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
11915
11916 -- Patron stat cat allow_freetext column
11917 ALTER TABLE actor.stat_cat
11918     ADD COLUMN allow_freetext BOOL NOT NULL DEFAULT TRUE;
11919
11920 -- Add permissions
11921
11922 INSERT INTO permission.perm_list ( id, code, description ) VALUES
11923     ( 525, 'CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 525, 
11924         'User may set a default entry in a patron statistical category', 'ppl', 'description' )),
11925     ( 526, 'UPDATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 526, 
11926         'User may reset a default entry in a patron statistical category', 'ppl', 'description' )),
11927     ( 527, 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 527, 
11928         'User may unset a default entry in a patron statistical category', 'ppl', 'description' ));
11929
11930 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
11931     SELECT
11932         pgt.id, perm.id, aout.depth, TRUE
11933     FROM
11934         permission.grp_tree pgt,
11935         permission.perm_list perm,
11936         actor.org_unit_type aout
11937     WHERE
11938         pgt.name = 'Circulation Administrator' AND
11939         aout.name = 'System' AND
11940         perm.code IN ('CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT');
11941
11942
11943 SELECT evergreen.upgrade_deps_block_check('0702', :eg_version);
11944
11945 INSERT INTO config.global_flag (name, enabled, label) 
11946     VALUES (
11947         'opac.org_unit.non_inherited_visibility',
11948         FALSE,
11949         oils_i18n_gettext(
11950             'opac.org_unit.non_inherited_visibility',
11951             'Org Units Do Not Inherit Visibility',
11952             'cgf',
11953             'label'
11954         )
11955     );
11956
11957 CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
11958
11959 CREATE TABLE actor.org_unit_custom_tree (
11960     id              SERIAL  PRIMARY KEY,
11961     active          BOOLEAN DEFAULT FALSE,
11962     purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
11963 );
11964
11965 CREATE TABLE actor.org_unit_custom_tree_node (
11966     id              SERIAL  PRIMARY KEY,
11967     tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
11968         org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
11969         parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
11970     sibling_order   INTEGER NOT NULL DEFAULT 0,
11971     CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
11972 );
11973     
11974
11975 /* UNDO
11976 BEGIN;
11977 DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
11978 DROP TABLE actor.org_unit_custom_tree_node;
11979 DROP TABLE actor.org_unit_custom_tree;
11980 DROP TYPE actor.org_unit_custom_tree_purpose;
11981 COMMIT;
11982 */
11983
11984 -- Evergreen DB patch 0704.schema.query_parser_fts.sql
11985 --
11986 -- Add pref_ou query filter for preferred library searching
11987 --
11988
11989 -- check whether patch can be applied
11990 SELECT evergreen.upgrade_deps_block_check('0704', :eg_version);
11991
11992 -- Create the new 11-parameter function, featuring param_pref_ou
11993 CREATE OR REPLACE FUNCTION search.query_parser_fts (
11994
11995     param_search_ou INT,
11996     param_depth     INT,
11997     param_query     TEXT,
11998     param_statuses  INT[],
11999     param_locations INT[],
12000     param_offset    INT,
12001     param_check     INT,
12002     param_limit     INT,
12003     metarecord      BOOL,
12004     staff           BOOL,
12005     param_pref_ou   INT DEFAULT NULL
12006 ) RETURNS SETOF search.search_result AS $func$
12007 DECLARE
12008
12009     current_res         search.search_result%ROWTYPE;
12010     search_org_list     INT[];
12011     luri_org_list       INT[];
12012     tmp_int_list        INT[];
12013
12014     check_limit         INT;
12015     core_limit          INT;
12016     core_offset         INT;
12017     tmp_int             INT;
12018
12019     core_result         RECORD;
12020     core_cursor         REFCURSOR;
12021     core_rel_query      TEXT;
12022
12023     total_count         INT := 0;
12024     check_count         INT := 0;
12025     deleted_count       INT := 0;
12026     visible_count       INT := 0;
12027     excluded_count      INT := 0;
12028
12029 BEGIN
12030
12031     check_limit := COALESCE( param_check, 1000 );
12032     core_limit  := COALESCE( param_limit, 25000 );
12033     core_offset := COALESCE( param_offset, 0 );
12034
12035     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
12036
12037     IF param_search_ou > 0 THEN
12038         IF param_depth IS NOT NULL THEN
12039             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
12040         ELSE
12041             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
12042         END IF;
12043
12044         SELECT array_accum(distinct id) INTO luri_org_list FROM actor.org_unit_ancestors( param_search_ou );
12045
12046     ELSIF param_search_ou < 0 THEN
12047         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
12048
12049         FOR tmp_int IN SELECT * FROM UNNEST(search_org_list) LOOP
12050             SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors( tmp_int );
12051             luri_org_list := luri_org_list || tmp_int_list;
12052         END LOOP;
12053
12054         SELECT array_accum(DISTINCT x.id) INTO luri_org_list FROM UNNEST(luri_org_list) x(id);
12055
12056     ELSIF param_search_ou = 0 THEN
12057         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
12058     END IF;
12059
12060     IF param_pref_ou IS NOT NULL THEN
12061         SELECT array_accum(distinct id) INTO tmp_int_list FROM actor.org_unit_ancestors(param_pref_ou);
12062         luri_org_list := luri_org_list || tmp_int_list;
12063     END IF;
12064
12065     OPEN core_cursor FOR EXECUTE param_query;
12066
12067     LOOP
12068
12069         FETCH core_cursor INTO core_result;
12070         EXIT WHEN NOT FOUND;
12071         EXIT WHEN total_count >= core_limit;
12072
12073         total_count := total_count + 1;
12074
12075         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
12076
12077         check_count := check_count + 1;
12078
12079         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12080         IF NOT FOUND THEN
12081             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
12082             deleted_count := deleted_count + 1;
12083             CONTINUE;
12084         END IF;
12085
12086         PERFORM 1
12087           FROM  biblio.record_entry b
12088                 JOIN config.bib_source s ON (b.source = s.id)
12089           WHERE s.transcendant
12090                 AND b.id IN ( SELECT * FROM unnest( core_result.records ) );
12091
12092         IF FOUND THEN
12093             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
12094             visible_count := visible_count + 1;
12095
12096             current_res.id = core_result.id;
12097             current_res.rel = core_result.rel;
12098
12099             tmp_int := 1;
12100             IF metarecord THEN
12101                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12102             END IF;
12103
12104             IF tmp_int = 1 THEN
12105                 current_res.record = core_result.records[1];
12106             ELSE
12107                 current_res.record = NULL;
12108             END IF;
12109
12110             RETURN NEXT current_res;
12111
12112             CONTINUE;
12113         END IF;
12114
12115         PERFORM 1
12116           FROM  asset.call_number cn
12117                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
12118                 JOIN asset.uri uri ON (map.uri = uri.id)
12119           WHERE NOT cn.deleted
12120                 AND cn.label = '##URI##'
12121                 AND uri.active
12122                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
12123                 AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12124                 AND cn.owning_lib IN ( SELECT * FROM unnest( luri_org_list ) )
12125           LIMIT 1;
12126
12127         IF FOUND THEN
12128             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
12129             visible_count := visible_count + 1;
12130
12131             current_res.id = core_result.id;
12132             current_res.rel = core_result.rel;
12133
12134             tmp_int := 1;
12135             IF metarecord THEN
12136                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12137             END IF;
12138
12139             IF tmp_int = 1 THEN
12140                 current_res.record = core_result.records[1];
12141             ELSE
12142                 current_res.record = NULL;
12143             END IF;
12144
12145             RETURN NEXT current_res;
12146
12147             CONTINUE;
12148         END IF;
12149
12150         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
12151
12152             PERFORM 1
12153               FROM  asset.call_number cn
12154                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12155               WHERE NOT cn.deleted
12156                     AND NOT cp.deleted
12157                     AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12158                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12159                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12160               LIMIT 1;
12161
12162             IF NOT FOUND THEN
12163                 PERFORM 1
12164                   FROM  biblio.peer_bib_copy_map pr
12165                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12166                   WHERE NOT cp.deleted
12167                         AND cp.status IN ( SELECT * FROM unnest( param_statuses ) )
12168                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12169                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12170                   LIMIT 1;
12171
12172                 IF NOT FOUND THEN
12173                 -- RAISE NOTICE ' % and multi-home linked records were all status-excluded ... ', core_result.records;
12174                     excluded_count := excluded_count + 1;
12175                     CONTINUE;
12176                 END IF;
12177             END IF;
12178
12179         END IF;
12180
12181         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
12182
12183             PERFORM 1
12184               FROM  asset.call_number cn
12185                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12186               WHERE NOT cn.deleted
12187                     AND NOT cp.deleted
12188                     AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12189                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12190                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12191               LIMIT 1;
12192
12193             IF NOT FOUND THEN
12194                 PERFORM 1
12195                   FROM  biblio.peer_bib_copy_map pr
12196                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12197                   WHERE NOT cp.deleted
12198                         AND cp.location IN ( SELECT * FROM unnest( param_locations ) )
12199                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12200                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12201                   LIMIT 1;
12202
12203                 IF NOT FOUND THEN
12204                     -- RAISE NOTICE ' % and multi-home linked records were all copy_location-excluded ... ', core_result.records;
12205                     excluded_count := excluded_count + 1;
12206                     CONTINUE;
12207                 END IF;
12208             END IF;
12209
12210         END IF;
12211
12212         IF staff IS NULL OR NOT staff THEN
12213
12214             PERFORM 1
12215               FROM  asset.opac_visible_copies
12216               WHERE circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12217                     AND record IN ( SELECT * FROM unnest( core_result.records ) )
12218               LIMIT 1;
12219
12220             IF NOT FOUND THEN
12221                 PERFORM 1
12222                   FROM  biblio.peer_bib_copy_map pr
12223                         JOIN asset.opac_visible_copies cp ON (cp.copy_id = pr.target_copy)
12224                   WHERE cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12225                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12226                   LIMIT 1;
12227
12228                 IF NOT FOUND THEN
12229
12230                     -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12231                     excluded_count := excluded_count + 1;
12232                     CONTINUE;
12233                 END IF;
12234             END IF;
12235
12236         ELSE
12237
12238             PERFORM 1
12239               FROM  asset.call_number cn
12240                     JOIN asset.copy cp ON (cp.call_number = cn.id)
12241               WHERE NOT cn.deleted
12242                     AND NOT cp.deleted
12243                     AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12244                     AND cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12245               LIMIT 1;
12246
12247             IF NOT FOUND THEN
12248
12249                 PERFORM 1
12250                   FROM  biblio.peer_bib_copy_map pr
12251                         JOIN asset.copy cp ON (cp.id = pr.target_copy)
12252                   WHERE NOT cp.deleted
12253                         AND cp.circ_lib IN ( SELECT * FROM unnest( search_org_list ) )
12254                         AND pr.peer_record IN ( SELECT * FROM unnest( core_result.records ) )
12255                   LIMIT 1;
12256
12257                 IF NOT FOUND THEN
12258
12259                     PERFORM 1
12260                       FROM  asset.call_number cn
12261                             JOIN asset.copy cp ON (cp.call_number = cn.id)
12262                       WHERE cn.record IN ( SELECT * FROM unnest( core_result.records ) )
12263                             AND NOT cp.deleted
12264                       LIMIT 1;
12265
12266                     IF FOUND THEN
12267                         -- RAISE NOTICE ' % and multi-home linked records were all visibility-excluded ... ', core_result.records;
12268                         excluded_count := excluded_count + 1;
12269                         CONTINUE;
12270                     END IF;
12271                 END IF;
12272
12273             END IF;
12274
12275         END IF;
12276
12277         visible_count := visible_count + 1;
12278
12279         current_res.id = core_result.id;
12280         current_res.rel = core_result.rel;
12281
12282         tmp_int := 1;
12283         IF metarecord THEN
12284             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
12285         END IF;
12286
12287         IF tmp_int = 1 THEN
12288             current_res.record = core_result.records[1];
12289         ELSE
12290             current_res.record = NULL;
12291         END IF;
12292
12293         RETURN NEXT current_res;
12294
12295         IF visible_count % 1000 = 0 THEN
12296             -- RAISE NOTICE ' % visible so far ... ', visible_count;
12297         END IF;
12298
12299     END LOOP;
12300
12301     current_res.id = NULL;
12302     current_res.rel = NULL;
12303     current_res.record = NULL;
12304     current_res.total = total_count;
12305     current_res.checked = check_count;
12306     current_res.deleted = deleted_count;
12307     current_res.visible = visible_count;
12308     current_res.excluded = excluded_count;
12309
12310     CLOSE core_cursor;
12311
12312     RETURN NEXT current_res;
12313
12314 END;
12315 $func$ LANGUAGE PLPGSQL;
12316
12317 -- Drop the old 10-parameter function
12318 DROP FUNCTION IF EXISTS search.query_parser_fts (
12319     INT, INT, TEXT, INT[], INT[], INT, INT, INT, BOOL, BOOL
12320 );
12321
12322 -- Evergreen DB patch 0705.data.custom-org-tree-perms.sql
12323 --
12324 -- check whether patch can be applied
12325 SELECT evergreen.upgrade_deps_block_check('0705', :eg_version);
12326
12327 INSERT INTO permission.perm_list (id, code, description)
12328     VALUES (
12329         528,
12330         'ADMIN_ORG_UNIT_CUSTOM_TREE',
12331         oils_i18n_gettext(
12332             528,
12333             'User may update custom org unit trees',
12334             'ppl',
12335             'description'
12336         )
12337     );
12338
12339 -- Evergreen DB patch 0707.schema.acq-vandelay-integration.sql
12340
12341 SELECT evergreen.upgrade_deps_block_check('0707', :eg_version);
12342
12343 -- seed data --
12344
12345 INSERT INTO permission.perm_list ( id, code, description )
12346     VALUES (
12347         529,
12348         'ADMIN_IMPORT_MATCH_SET',
12349         oils_i18n_gettext(
12350             529,
12351             'Allows a user to create/retrieve/update/delete vandelay match sets',
12352             'ppl',
12353             'description'
12354         )
12355     ), (
12356         530,
12357         'VIEW_IMPORT_MATCH_SET',
12358         oils_i18n_gettext(
12359             530,
12360             'Allows a user to view vandelay match sets',
12361             'ppl',
12362             'description'
12363         )
12364     );
12365
12366 -- This upgrade script fixed a typo in a previous one. It was corrected in the proper place in this file.
12367 -- Still, record the fact it has been "applied".
12368 SELECT evergreen.upgrade_deps_block_check('0708', :eg_version);
12369
12370 -- Evergreen DB patch 0709.data.misc_missing_perms.sql
12371
12372 SELECT evergreen.upgrade_deps_block_check('0709', :eg_version);
12373
12374 INSERT INTO permission.perm_list ( id, code, description ) 
12375     VALUES ( 
12376         531, 
12377         'ADMIN_ADDRESS_ALERT',
12378         oils_i18n_gettext( 
12379             531,
12380             'Allows a user to create/retrieve/update/delete address alerts',
12381             'ppl', 
12382             'description' 
12383         )
12384     ), ( 
12385         532, 
12386         'VIEW_ADDRESS_ALERT',
12387         oils_i18n_gettext( 
12388             532,
12389             'Allows a user to view address alerts',
12390             'ppl', 
12391             'description' 
12392         )
12393     ), ( 
12394         533, 
12395         'ADMIN_COPY_LOCATION_GROUP',
12396         oils_i18n_gettext( 
12397             533,
12398             'Allows a user to create/retrieve/update/delete copy location groups',
12399             'ppl', 
12400             'description' 
12401         )
12402     ), ( 
12403         534, 
12404         'ADMIN_USER_ACTIVITY_TYPE',
12405         oils_i18n_gettext( 
12406             534,
12407             'Allows a user to create/retrieve/update/delete user activity types',
12408             'ppl', 
12409             'description' 
12410         )
12411     );
12412
12413 COMMIT;
12414
12415 \qecho ************************************************************************
12416 \qecho The following transaction, wrapping upgrade 0672, may take a while.  If
12417 \qecho it takes an unduly long time, try it outside of a transaction.
12418 \qecho ************************************************************************
12419
12420 BEGIN;
12421
12422 -- Evergreen DB patch 0672.fix-nonfiling-titles.sql
12423 --
12424 -- Titles that begin with non-filing articles using apostrophes
12425 -- (for example, "L'armée") get spaces injected between the article
12426 -- and the subsequent text, which then breaks searching for titles
12427 -- beginning with those articles.
12428 --
12429 -- This patch adds a nonfiling title element to MODS32 that can then
12430 -- be used to retrieve the title proper without affecting the spaces
12431 -- in the title. It's what we want, what we really really want, for
12432 -- title searches.
12433 --
12434
12435
12436 -- check whether patch can be applied
12437 SELECT evergreen.upgrade_deps_block_check('0672', :eg_version);
12438
12439 -- Update the XPath definition before the titleNonfiling element exists;
12440 -- but are you really going to read through the whole XSL below before
12441 -- seeing this important bit?
12442 UPDATE config.metabib_field
12443     SET xpath = $$//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]$$,
12444         format = 'mods32'
12445     WHERE field_class = 'title' AND name = 'proper';
12446
12447 UPDATE config.xml_transform SET xslt=$$<?xml version="1.0" encoding="UTF-8"?>
12448 <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">
12449         <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
12450 <!--
12451 Revision 1.14 - Fixed template isValid and fields 010, 020, 022, 024, 028, and 037 to output additional identifier elements 
12452   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
12453
12454 Revision 1.13 - Changed order of output under cartographics to reflect schema  2006/11/28 tmee
12455         
12456 Revision 1.12 - Updated to reflect MODS 3.2 Mapping  2006/10/11 tmee
12457                 
12458 Revision 1.11 - The attribute objectPart moved from <languageTerm> to <language>
12459       2006/04/08  jrad
12460
12461 Revision 1.10 MODS 3.1 revisions to language and classification elements  
12462                                 (plus ability to find marc:collection embedded in wrapper elements such as SRU zs: wrappers)
12463                                 2006/02/06  ggar
12464
12465 Revision 1.9 subfield $y was added to field 242 2004/09/02 10:57 jrad
12466
12467 Revision 1.8 Subject chopPunctuation expanded and attribute fixes 2004/08/12 jrad
12468
12469 Revision 1.7 2004/03/25 08:29 jrad
12470
12471 Revision 1.6 various validation fixes 2004/02/20 ntra
12472
12473 Revision 1.5  2003/10/02 16:18:58  ntra
12474 MODS2 to MODS3 updates, language unstacking and 
12475 de-duping, chopPunctuation expanded
12476
12477 Revision 1.3  2003/04/03 00:07:19  ntra
12478 Revision 1.3 Additional Changes not related to MODS Version 2.0 by ntra
12479
12480 Revision 1.2  2003/03/24 19:37:42  ckeith
12481 Added Log Comment
12482
12483 -->
12484         <xsl:template match="/">
12485                 <xsl:choose>
12486                         <xsl:when test="//marc:collection">
12487                                 <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">
12488                                         <xsl:for-each select="//marc:collection/marc:record">
12489                                                 <mods version="3.2">
12490                                                         <xsl:call-template name="marcRecord"/>
12491                                                 </mods>
12492                                         </xsl:for-each>
12493                                 </modsCollection>
12494                         </xsl:when>
12495                         <xsl:otherwise>
12496                                 <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">
12497                                         <xsl:for-each select="//marc:record">
12498                                                 <xsl:call-template name="marcRecord"/>
12499                                         </xsl:for-each>
12500                                 </mods>
12501                         </xsl:otherwise>
12502                 </xsl:choose>
12503         </xsl:template>
12504         <xsl:template name="marcRecord">
12505                 <xsl:variable name="leader" select="marc:leader"/>
12506                 <xsl:variable name="leader6" select="substring($leader,7,1)"/>
12507                 <xsl:variable name="leader7" select="substring($leader,8,1)"/>
12508                 <xsl:variable name="controlField008" select="marc:controlfield[@tag='008']"/>
12509                 <xsl:variable name="typeOf008">
12510                         <xsl:choose>
12511                                 <xsl:when test="$leader6='a'">
12512                                         <xsl:choose>
12513                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">BK</xsl:when>
12514                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">SE</xsl:when>
12515                                         </xsl:choose>
12516                                 </xsl:when>
12517                                 <xsl:when test="$leader6='t'">BK</xsl:when>
12518                                 <xsl:when test="$leader6='p'">MM</xsl:when>
12519                                 <xsl:when test="$leader6='m'">CF</xsl:when>
12520                                 <xsl:when test="$leader6='e' or $leader6='f'">MP</xsl:when>
12521                                 <xsl:when test="$leader6='g' or $leader6='k' or $leader6='o' or $leader6='r'">VM</xsl:when>
12522                                 <xsl:when test="$leader6='c' or $leader6='d' or $leader6='i' or $leader6='j'">MU</xsl:when>
12523                         </xsl:choose>
12524                 </xsl:variable>
12525                 <xsl:for-each select="marc:datafield[@tag='245']">
12526                         <titleInfo>
12527                                 <xsl:variable name="title">
12528                                         <xsl:choose>
12529                                                 <xsl:when test="marc:subfield[@code='b']">
12530                                                         <xsl:call-template name="specialSubfieldSelect">
12531                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12532                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12533                                                         </xsl:call-template>
12534                                                 </xsl:when>
12535                                                 <xsl:otherwise>
12536                                                         <xsl:call-template name="subfieldSelect">
12537                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12538                                                         </xsl:call-template>
12539                                                 </xsl:otherwise>
12540                                         </xsl:choose>
12541                                 </xsl:variable>
12542                                 <xsl:variable name="titleChop">
12543                                         <xsl:call-template name="chopPunctuation">
12544                                                 <xsl:with-param name="chopString">
12545                                                         <xsl:value-of select="$title"/>
12546                                                 </xsl:with-param>
12547                                         </xsl:call-template>
12548                                 </xsl:variable>
12549                                 <xsl:choose>
12550                                         <xsl:when test="@ind2>0">
12551                                                 <nonSort>
12552                                                         <xsl:value-of select="substring($titleChop,1,@ind2)"/>
12553                                                 </nonSort>
12554                                                 <title>
12555                                                         <xsl:value-of select="substring($titleChop,@ind2+1)"/>
12556                                                 </title>
12557                                         </xsl:when>
12558                                         <xsl:otherwise>
12559                                                 <title>
12560                                                         <xsl:value-of select="$titleChop"/>
12561                                                 </title>
12562                                         </xsl:otherwise>
12563                                 </xsl:choose>
12564                                 <xsl:if test="marc:subfield[@code='b']">
12565                                         <subTitle>
12566                                                 <xsl:call-template name="chopPunctuation">
12567                                                         <xsl:with-param name="chopString">
12568                                                                 <xsl:call-template name="specialSubfieldSelect">
12569                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12570                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12571                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12572                                                                 </xsl:call-template>
12573                                                         </xsl:with-param>
12574                                                 </xsl:call-template>
12575                                         </subTitle>
12576                                 </xsl:if>
12577                                 <xsl:call-template name="part"></xsl:call-template>
12578                         </titleInfo>
12579                         <!-- A form of title that ignores non-filing characters; useful
12580                                  for not converting "L'Oreal" into "L' Oreal" at index time -->
12581                         <titleNonfiling>
12582                                 <xsl:variable name="title">
12583                                         <xsl:choose>
12584                                                 <xsl:when test="marc:subfield[@code='b']">
12585                                                         <xsl:call-template name="specialSubfieldSelect">
12586                                                                 <xsl:with-param name="axis">b</xsl:with-param>
12587                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
12588                                                         </xsl:call-template>
12589                                                 </xsl:when>
12590                                                 <xsl:otherwise>
12591                                                         <xsl:call-template name="subfieldSelect">
12592                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
12593                                                         </xsl:call-template>
12594                                                 </xsl:otherwise>
12595                                         </xsl:choose>
12596                                 </xsl:variable>
12597                                 <title>
12598                                         <xsl:value-of select="$title"/>
12599                                 </title>
12600                                 <xsl:if test="marc:subfield[@code='b']">
12601                                         <subTitle>
12602                                                 <xsl:call-template name="chopPunctuation">
12603                                                         <xsl:with-param name="chopString">
12604                                                                 <xsl:call-template name="specialSubfieldSelect">
12605                                                                         <xsl:with-param name="axis">b</xsl:with-param>
12606                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
12607                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
12608                                                                 </xsl:call-template>
12609                                                         </xsl:with-param>
12610                                                 </xsl:call-template>
12611                                         </subTitle>
12612                                 </xsl:if>
12613                                 <xsl:call-template name="part"></xsl:call-template>
12614                         </titleNonfiling>
12615                 </xsl:for-each>
12616                 <xsl:for-each select="marc:datafield[@tag='210']">
12617                         <titleInfo type="abbreviated">
12618                                 <title>
12619                                         <xsl:call-template name="chopPunctuation">
12620                                                 <xsl:with-param name="chopString">
12621                                                         <xsl:call-template name="subfieldSelect">
12622                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12623                                                         </xsl:call-template>
12624                                                 </xsl:with-param>
12625                                         </xsl:call-template>
12626                                 </title>
12627                                 <xsl:call-template name="subtitle"/>
12628                         </titleInfo>
12629                 </xsl:for-each>
12630                 <xsl:for-each select="marc:datafield[@tag='242']">
12631                         <titleInfo type="translated">
12632                                 <!--09/01/04 Added subfield $y-->
12633                                 <xsl:for-each select="marc:subfield[@code='y']">
12634                                         <xsl:attribute name="lang">
12635                                                 <xsl:value-of select="text()"/>
12636                                         </xsl:attribute>
12637                                 </xsl:for-each>
12638                                 <title>
12639                                         <xsl:call-template name="chopPunctuation">
12640                                                 <xsl:with-param name="chopString">
12641                                                         <xsl:call-template name="subfieldSelect">
12642                                                                 <!-- 1/04 removed $h, b -->
12643                                                                 <xsl:with-param name="codes">a</xsl:with-param>
12644                                                         </xsl:call-template>
12645                                                 </xsl:with-param>
12646                                         </xsl:call-template>
12647                                 </title>
12648                                 <!-- 1/04 fix -->
12649                                 <xsl:call-template name="subtitle"/>
12650                                 <xsl:call-template name="part"/>
12651                         </titleInfo>
12652                 </xsl:for-each>
12653                 <xsl:for-each select="marc:datafield[@tag='246']">
12654                         <titleInfo type="alternative">
12655                                 <xsl:for-each select="marc:subfield[@code='i']">
12656                                         <xsl:attribute name="displayLabel">
12657                                                 <xsl:value-of select="text()"/>
12658                                         </xsl:attribute>
12659                                 </xsl:for-each>
12660                                 <title>
12661                                         <xsl:call-template name="chopPunctuation">
12662                                                 <xsl:with-param name="chopString">
12663                                                         <xsl:call-template name="subfieldSelect">
12664                                                                 <!-- 1/04 removed $h, $b -->
12665                                                                 <xsl:with-param name="codes">af</xsl:with-param>
12666                                                         </xsl:call-template>
12667                                                 </xsl:with-param>
12668                                         </xsl:call-template>
12669                                 </title>
12670                                 <xsl:call-template name="subtitle"/>
12671                                 <xsl:call-template name="part"/>
12672                         </titleInfo>
12673                 </xsl:for-each>
12674                 <xsl:for-each select="marc:datafield[@tag='130']|marc:datafield[@tag='240']|marc:datafield[@tag='730'][@ind2!='2']">
12675                         <titleInfo type="uniform">
12676                                 <title>
12677                                         <xsl:variable name="str">
12678                                                 <xsl:for-each select="marc:subfield">
12679                                                         <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'])))">
12680                                                                 <xsl:value-of select="text()"/>
12681                                                                 <xsl:text> </xsl:text>
12682                                                         </xsl:if>
12683                                                 </xsl:for-each>
12684                                         </xsl:variable>
12685                                         <xsl:call-template name="chopPunctuation">
12686                                                 <xsl:with-param name="chopString">
12687                                                         <xsl:value-of select="substring($str,1,string-length($str)-1)"/>
12688                                                 </xsl:with-param>
12689                                         </xsl:call-template>
12690                                 </title>
12691                                 <xsl:call-template name="part"/>
12692                         </titleInfo>
12693                 </xsl:for-each>
12694                 <xsl:for-each select="marc:datafield[@tag='740'][@ind2!='2']">
12695                         <titleInfo type="alternative">
12696                                 <title>
12697                                         <xsl:call-template name="chopPunctuation">
12698                                                 <xsl:with-param name="chopString">
12699                                                         <xsl:call-template name="subfieldSelect">
12700                                                                 <xsl:with-param name="codes">ah</xsl:with-param>
12701                                                         </xsl:call-template>
12702                                                 </xsl:with-param>
12703                                         </xsl:call-template>
12704                                 </title>
12705                                 <xsl:call-template name="part"/>
12706                         </titleInfo>
12707                 </xsl:for-each>
12708                 <xsl:for-each select="marc:datafield[@tag='100']">
12709                         <name type="personal">
12710                                 <xsl:call-template name="nameABCDQ"/>
12711                                 <xsl:call-template name="affiliation"/>
12712                                 <role>
12713                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12714                                 </role>
12715                                 <xsl:call-template name="role"/>
12716                         </name>
12717                 </xsl:for-each>
12718                 <xsl:for-each select="marc:datafield[@tag='110']">
12719                         <name type="corporate">
12720                                 <xsl:call-template name="nameABCDN"/>
12721                                 <role>
12722                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12723                                 </role>
12724                                 <xsl:call-template name="role"/>
12725                         </name>
12726                 </xsl:for-each>
12727                 <xsl:for-each select="marc:datafield[@tag='111']">
12728                         <name type="conference">
12729                                 <xsl:call-template name="nameACDEQ"/>
12730                                 <role>
12731                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
12732                                 </role>
12733                                 <xsl:call-template name="role"/>
12734                         </name>
12735                 </xsl:for-each>
12736                 <xsl:for-each select="marc:datafield[@tag='700'][not(marc:subfield[@code='t'])]">
12737                         <name type="personal">
12738                                 <xsl:call-template name="nameABCDQ"/>
12739                                 <xsl:call-template name="affiliation"/>
12740                                 <xsl:call-template name="role"/>
12741                         </name>
12742                 </xsl:for-each>
12743                 <xsl:for-each select="marc:datafield[@tag='710'][not(marc:subfield[@code='t'])]">
12744                         <name type="corporate">
12745                                 <xsl:call-template name="nameABCDN"/>
12746                                 <xsl:call-template name="role"/>
12747                         </name>
12748                 </xsl:for-each>
12749                 <xsl:for-each select="marc:datafield[@tag='711'][not(marc:subfield[@code='t'])]">
12750                         <name type="conference">
12751                                 <xsl:call-template name="nameACDEQ"/>
12752                                 <xsl:call-template name="role"/>
12753                         </name>
12754                 </xsl:for-each>
12755                 <xsl:for-each select="marc:datafield[@tag='720'][not(marc:subfield[@code='t'])]">
12756                         <name>
12757                                 <xsl:if test="@ind1=1">
12758                                         <xsl:attribute name="type">
12759                                                 <xsl:text>personal</xsl:text>
12760                                         </xsl:attribute>
12761                                 </xsl:if>
12762                                 <namePart>
12763                                         <xsl:value-of select="marc:subfield[@code='a']"/>
12764                                 </namePart>
12765                                 <xsl:call-template name="role"/>
12766                         </name>
12767                 </xsl:for-each>
12768                 <typeOfResource>
12769                         <xsl:if test="$leader7='c'">
12770                                 <xsl:attribute name="collection">yes</xsl:attribute>
12771                         </xsl:if>
12772                         <xsl:if test="$leader6='d' or $leader6='f' or $leader6='p' or $leader6='t'">
12773                                 <xsl:attribute name="manuscript">yes</xsl:attribute>
12774                         </xsl:if>
12775                         <xsl:choose>
12776                                 <xsl:when test="$leader6='a' or $leader6='t'">text</xsl:when>
12777                                 <xsl:when test="$leader6='e' or $leader6='f'">cartographic</xsl:when>
12778                                 <xsl:when test="$leader6='c' or $leader6='d'">notated music</xsl:when>
12779                                 <xsl:when test="$leader6='i'">sound recording-nonmusical</xsl:when>
12780                                 <xsl:when test="$leader6='j'">sound recording-musical</xsl:when>
12781                                 <xsl:when test="$leader6='k'">still image</xsl:when>
12782                                 <xsl:when test="$leader6='g'">moving image</xsl:when>
12783                                 <xsl:when test="$leader6='r'">three dimensional object</xsl:when>
12784                                 <xsl:when test="$leader6='m'">software, multimedia</xsl:when>
12785                                 <xsl:when test="$leader6='p'">mixed material</xsl:when>
12786                         </xsl:choose>
12787                 </typeOfResource>
12788                 <xsl:if test="substring($controlField008,26,1)='d'">
12789                         <genre authority="marc">globe</genre>
12790                 </xsl:if>
12791                 <xsl:if test="marc:controlfield[@tag='007'][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
12792                         <genre authority="marc">remote sensing image</genre>
12793                 </xsl:if>
12794                 <xsl:if test="$typeOf008='MP'">
12795                         <xsl:variable name="controlField008-25" select="substring($controlField008,26,1)"></xsl:variable>
12796                         <xsl:choose>
12797                                 <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']">
12798                                         <genre authority="marc">map</genre>
12799                                 </xsl:when>
12800                                 <xsl:when test="$controlField008-25='e' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
12801                                         <genre authority="marc">atlas</genre>
12802                                 </xsl:when>
12803                         </xsl:choose>
12804                 </xsl:if>
12805                 <xsl:if test="$typeOf008='SE'">
12806                         <xsl:variable name="controlField008-21" select="substring($controlField008,22,1)"></xsl:variable>
12807                         <xsl:choose>
12808                                 <xsl:when test="$controlField008-21='d'">
12809                                         <genre authority="marc">database</genre>
12810                                 </xsl:when>
12811                                 <xsl:when test="$controlField008-21='l'">
12812                                         <genre authority="marc">loose-leaf</genre>
12813                                 </xsl:when>
12814                                 <xsl:when test="$controlField008-21='m'">
12815                                         <genre authority="marc">series</genre>
12816                                 </xsl:when>
12817                                 <xsl:when test="$controlField008-21='n'">
12818                                         <genre authority="marc">newspaper</genre>
12819                                 </xsl:when>
12820                                 <xsl:when test="$controlField008-21='p'">
12821                                         <genre authority="marc">periodical</genre>
12822                                 </xsl:when>
12823                                 <xsl:when test="$controlField008-21='w'">
12824                                         <genre authority="marc">web site</genre>
12825                                 </xsl:when>
12826                         </xsl:choose>
12827                 </xsl:if>
12828                 <xsl:if test="$typeOf008='BK' or $typeOf008='SE'">
12829                         <xsl:variable name="controlField008-24" select="substring($controlField008,25,4)"></xsl:variable>
12830                         <xsl:choose>
12831                                 <xsl:when test="contains($controlField008-24,'a')">
12832                                         <genre authority="marc">abstract or summary</genre>
12833                                 </xsl:when>
12834                                 <xsl:when test="contains($controlField008-24,'b')">
12835                                         <genre authority="marc">bibliography</genre>
12836                                 </xsl:when>
12837                                 <xsl:when test="contains($controlField008-24,'c')">
12838                                         <genre authority="marc">catalog</genre>
12839                                 </xsl:when>
12840                                 <xsl:when test="contains($controlField008-24,'d')">
12841                                         <genre authority="marc">dictionary</genre>
12842                                 </xsl:when>
12843                                 <xsl:when test="contains($controlField008-24,'e')">
12844                                         <genre authority="marc">encyclopedia</genre>
12845                                 </xsl:when>
12846                                 <xsl:when test="contains($controlField008-24,'f')">
12847                                         <genre authority="marc">handbook</genre>
12848                                 </xsl:when>
12849                                 <xsl:when test="contains($controlField008-24,'g')">
12850                                         <genre authority="marc">legal article</genre>
12851                                 </xsl:when>
12852                                 <xsl:when test="contains($controlField008-24,'i')">
12853                                         <genre authority="marc">index</genre>
12854                                 </xsl:when>
12855                                 <xsl:when test="contains($controlField008-24,'k')">
12856                                         <genre authority="marc">discography</genre>
12857                                 </xsl:when>
12858                                 <xsl:when test="contains($controlField008-24,'l')">
12859                                         <genre authority="marc">legislation</genre>
12860                                 </xsl:when>
12861                                 <xsl:when test="contains($controlField008-24,'m')">
12862                                         <genre authority="marc">theses</genre>
12863                                 </xsl:when>
12864                                 <xsl:when test="contains($controlField008-24,'n')">
12865                                         <genre authority="marc">survey of literature</genre>
12866                                 </xsl:when>
12867                                 <xsl:when test="contains($controlField008-24,'o')">
12868                                         <genre authority="marc">review</genre>
12869                                 </xsl:when>
12870                                 <xsl:when test="contains($controlField008-24,'p')">
12871                                         <genre authority="marc">programmed text</genre>
12872                                 </xsl:when>
12873                                 <xsl:when test="contains($controlField008-24,'q')">
12874                                         <genre authority="marc">filmography</genre>
12875                                 </xsl:when>
12876                                 <xsl:when test="contains($controlField008-24,'r')">
12877                                         <genre authority="marc">directory</genre>
12878                                 </xsl:when>
12879                                 <xsl:when test="contains($controlField008-24,'s')">
12880                                         <genre authority="marc">statistics</genre>
12881                                 </xsl:when>
12882                                 <xsl:when test="contains($controlField008-24,'t')">
12883                                         <genre authority="marc">technical report</genre>
12884                                 </xsl:when>
12885                                 <xsl:when test="contains($controlField008-24,'v')">
12886                                         <genre authority="marc">legal case and case notes</genre>
12887                                 </xsl:when>
12888                                 <xsl:when test="contains($controlField008-24,'w')">
12889                                         <genre authority="marc">law report or digest</genre>
12890                                 </xsl:when>
12891                                 <xsl:when test="contains($controlField008-24,'z')">
12892                                         <genre authority="marc">treaty</genre>
12893                                 </xsl:when>
12894                         </xsl:choose>
12895                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
12896                         <xsl:choose>
12897                                 <xsl:when test="$controlField008-29='1'">
12898                                         <genre authority="marc">conference publication</genre>
12899                                 </xsl:when>
12900                         </xsl:choose>
12901                 </xsl:if>
12902                 <xsl:if test="$typeOf008='CF'">
12903                         <xsl:variable name="controlField008-26" select="substring($controlField008,27,1)"></xsl:variable>
12904                         <xsl:choose>
12905                                 <xsl:when test="$controlField008-26='a'">
12906                                         <genre authority="marc">numeric data</genre>
12907                                 </xsl:when>
12908                                 <xsl:when test="$controlField008-26='e'">
12909                                         <genre authority="marc">database</genre>
12910                                 </xsl:when>
12911                                 <xsl:when test="$controlField008-26='f'">
12912                                         <genre authority="marc">font</genre>
12913                                 </xsl:when>
12914                                 <xsl:when test="$controlField008-26='g'">
12915                                         <genre authority="marc">game</genre>
12916                                 </xsl:when>
12917                         </xsl:choose>
12918                 </xsl:if>
12919                 <xsl:if test="$typeOf008='BK'">
12920                         <xsl:if test="substring($controlField008,25,1)='j'">
12921                                 <genre authority="marc">patent</genre>
12922                         </xsl:if>
12923                         <xsl:if test="substring($controlField008,31,1)='1'">
12924                                 <genre authority="marc">festschrift</genre>
12925                         </xsl:if>
12926                         <xsl:variable name="controlField008-34" select="substring($controlField008,35,1)"></xsl:variable>
12927                         <xsl:if test="$controlField008-34='a' or $controlField008-34='b' or $controlField008-34='c' or $controlField008-34='d'">
12928                                 <genre authority="marc">biography</genre>
12929                         </xsl:if>
12930                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
12931                         <xsl:choose>
12932                                 <xsl:when test="$controlField008-33='e'">
12933                                         <genre authority="marc">essay</genre>
12934                                 </xsl:when>
12935                                 <xsl:when test="$controlField008-33='d'">
12936                                         <genre authority="marc">drama</genre>
12937                                 </xsl:when>
12938                                 <xsl:when test="$controlField008-33='c'">
12939                                         <genre authority="marc">comic strip</genre>
12940                                 </xsl:when>
12941                                 <xsl:when test="$controlField008-33='l'">
12942                                         <genre authority="marc">fiction</genre>
12943                                 </xsl:when>
12944                                 <xsl:when test="$controlField008-33='h'">
12945                                         <genre authority="marc">humor, satire</genre>
12946                                 </xsl:when>
12947                                 <xsl:when test="$controlField008-33='i'">
12948                                         <genre authority="marc">letter</genre>
12949                                 </xsl:when>
12950                                 <xsl:when test="$controlField008-33='f'">
12951                                         <genre authority="marc">novel</genre>
12952                                 </xsl:when>
12953                                 <xsl:when test="$controlField008-33='j'">
12954                                         <genre authority="marc">short story</genre>
12955                                 </xsl:when>
12956                                 <xsl:when test="$controlField008-33='s'">
12957                                         <genre authority="marc">speech</genre>
12958                                 </xsl:when>
12959                         </xsl:choose>
12960                 </xsl:if>
12961                 <xsl:if test="$typeOf008='MU'">
12962                         <xsl:variable name="controlField008-30-31" select="substring($controlField008,31,2)"></xsl:variable>
12963                         <xsl:if test="contains($controlField008-30-31,'b')">
12964                                 <genre authority="marc">biography</genre>
12965                         </xsl:if>
12966                         <xsl:if test="contains($controlField008-30-31,'c')">
12967                                 <genre authority="marc">conference publication</genre>
12968                         </xsl:if>
12969                         <xsl:if test="contains($controlField008-30-31,'d')">
12970                                 <genre authority="marc">drama</genre>
12971                         </xsl:if>
12972                         <xsl:if test="contains($controlField008-30-31,'e')">
12973                                 <genre authority="marc">essay</genre>
12974                         </xsl:if>
12975                         <xsl:if test="contains($controlField008-30-31,'f')">
12976                                 <genre authority="marc">fiction</genre>
12977                         </xsl:if>
12978                         <xsl:if test="contains($controlField008-30-31,'o')">
12979                                 <genre authority="marc">folktale</genre>
12980                         </xsl:if>
12981                         <xsl:if test="contains($controlField008-30-31,'h')">
12982                                 <genre authority="marc">history</genre>
12983                         </xsl:if>
12984                         <xsl:if test="contains($controlField008-30-31,'k')">
12985                                 <genre authority="marc">humor, satire</genre>
12986                         </xsl:if>
12987                         <xsl:if test="contains($controlField008-30-31,'m')">
12988                                 <genre authority="marc">memoir</genre>
12989                         </xsl:if>
12990                         <xsl:if test="contains($controlField008-30-31,'p')">
12991                                 <genre authority="marc">poetry</genre>
12992                         </xsl:if>
12993                         <xsl:if test="contains($controlField008-30-31,'r')">
12994                                 <genre authority="marc">rehearsal</genre>
12995                         </xsl:if>
12996                         <xsl:if test="contains($controlField008-30-31,'g')">
12997                                 <genre authority="marc">reporting</genre>
12998                         </xsl:if>
12999                         <xsl:if test="contains($controlField008-30-31,'s')">
13000                                 <genre authority="marc">sound</genre>
13001                         </xsl:if>
13002                         <xsl:if test="contains($controlField008-30-31,'l')">
13003                                 <genre authority="marc">speech</genre>
13004                         </xsl:if>
13005                 </xsl:if>
13006                 <xsl:if test="$typeOf008='VM'">
13007                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
13008                         <xsl:choose>
13009                                 <xsl:when test="$controlField008-33='a'">
13010                                         <genre authority="marc">art original</genre>
13011                                 </xsl:when>
13012                                 <xsl:when test="$controlField008-33='b'">
13013                                         <genre authority="marc">kit</genre>
13014                                 </xsl:when>
13015                                 <xsl:when test="$controlField008-33='c'">
13016                                         <genre authority="marc">art reproduction</genre>
13017                                 </xsl:when>
13018                                 <xsl:when test="$controlField008-33='d'">
13019                                         <genre authority="marc">diorama</genre>
13020                                 </xsl:when>
13021                                 <xsl:when test="$controlField008-33='f'">
13022                                         <genre authority="marc">filmstrip</genre>
13023                                 </xsl:when>
13024                                 <xsl:when test="$controlField008-33='g'">
13025                                         <genre authority="marc">legal article</genre>
13026                                 </xsl:when>
13027                                 <xsl:when test="$controlField008-33='i'">
13028                                         <genre authority="marc">picture</genre>
13029                                 </xsl:when>
13030                                 <xsl:when test="$controlField008-33='k'">
13031                                         <genre authority="marc">graphic</genre>
13032                                 </xsl:when>
13033                                 <xsl:when test="$controlField008-33='l'">
13034                                         <genre authority="marc">technical drawing</genre>
13035                                 </xsl:when>
13036                                 <xsl:when test="$controlField008-33='m'">
13037                                         <genre authority="marc">motion picture</genre>
13038                                 </xsl:when>
13039                                 <xsl:when test="$controlField008-33='n'">
13040                                         <genre authority="marc">chart</genre>
13041                                 </xsl:when>
13042                                 <xsl:when test="$controlField008-33='o'">
13043                                         <genre authority="marc">flash card</genre>
13044                                 </xsl:when>
13045                                 <xsl:when test="$controlField008-33='p'">
13046                                         <genre authority="marc">microscope slide</genre>
13047                                 </xsl:when>
13048                                 <xsl:when test="$controlField008-33='q' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13049                                         <genre authority="marc">model</genre>
13050                                 </xsl:when>
13051                                 <xsl:when test="$controlField008-33='r'">
13052                                         <genre authority="marc">realia</genre>
13053                                 </xsl:when>
13054                                 <xsl:when test="$controlField008-33='s'">
13055                                         <genre authority="marc">slide</genre>
13056                                 </xsl:when>
13057                                 <xsl:when test="$controlField008-33='t'">
13058                                         <genre authority="marc">transparency</genre>
13059                                 </xsl:when>
13060                                 <xsl:when test="$controlField008-33='v'">
13061                                         <genre authority="marc">videorecording</genre>
13062                                 </xsl:when>
13063                                 <xsl:when test="$controlField008-33='w'">
13064                                         <genre authority="marc">toy</genre>
13065                                 </xsl:when>
13066                         </xsl:choose>
13067                 </xsl:if>
13068                 <xsl:for-each select="marc:datafield[@tag=655]">
13069                         <genre authority="marc">
13070                                 <xsl:attribute name="authority">
13071                                         <xsl:value-of select="marc:subfield[@code='2']"/>
13072                                 </xsl:attribute>
13073                                 <xsl:call-template name="subfieldSelect">
13074                                         <xsl:with-param name="codes">abvxyz</xsl:with-param>
13075                                         <xsl:with-param name="delimeter">-</xsl:with-param>
13076                                 </xsl:call-template>
13077                         </genre>
13078                 </xsl:for-each>
13079                 <originInfo>
13080                         <xsl:variable name="MARCpublicationCode" select="normalize-space(substring($controlField008,16,3))"></xsl:variable>
13081                         <xsl:if test="translate($MARCpublicationCode,'|','')">
13082                                 <place>
13083                                         <placeTerm>
13084                                                 <xsl:attribute name="type">code</xsl:attribute>
13085                                                 <xsl:attribute name="authority">marccountry</xsl:attribute>
13086                                                 <xsl:value-of select="$MARCpublicationCode"/>
13087                                         </placeTerm>
13088                                 </place>
13089                         </xsl:if>
13090                         <xsl:for-each select="marc:datafield[@tag=044]/marc:subfield[@code='c']">
13091                                 <place>
13092                                         <placeTerm>
13093                                                 <xsl:attribute name="type">code</xsl:attribute>
13094                                                 <xsl:attribute name="authority">iso3166</xsl:attribute>
13095                                                 <xsl:value-of select="."/>
13096                                         </placeTerm>
13097                                 </place>
13098                         </xsl:for-each>
13099                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='a']">
13100                                 <place>
13101                                         <placeTerm>
13102                                                 <xsl:attribute name="type">text</xsl:attribute>
13103                                                 <xsl:call-template name="chopPunctuationFront">
13104                                                         <xsl:with-param name="chopString">
13105                                                                 <xsl:call-template name="chopPunctuation">
13106                                                                         <xsl:with-param name="chopString" select="."/>
13107                                                                 </xsl:call-template>
13108                                                         </xsl:with-param>
13109                                                 </xsl:call-template>
13110                                         </placeTerm>
13111                                 </place>
13112                         </xsl:for-each>
13113                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='m']">
13114                                 <dateValid point="start">
13115                                         <xsl:value-of select="."/>
13116                                 </dateValid>
13117                         </xsl:for-each>
13118                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='n']">
13119                                 <dateValid point="end">
13120                                         <xsl:value-of select="."/>
13121                                 </dateValid>
13122                         </xsl:for-each>
13123                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='j']">
13124                                 <dateModified>
13125                                         <xsl:value-of select="."/>
13126                                 </dateModified>
13127                         </xsl:for-each>
13128                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='b' or @code='c' or @code='g']">
13129                                 <xsl:choose>
13130                                         <xsl:when test="@code='b'">
13131                                                 <publisher>
13132                                                         <xsl:call-template name="chopPunctuation">
13133                                                                 <xsl:with-param name="chopString" select="."/>
13134                                                                 <xsl:with-param name="punctuation">
13135                                                                         <xsl:text>:,;/ </xsl:text>
13136                                                                 </xsl:with-param>
13137                                                         </xsl:call-template>
13138                                                 </publisher>
13139                                         </xsl:when>
13140                                         <xsl:when test="@code='c'">
13141                                                 <dateIssued>
13142                                                         <xsl:call-template name="chopPunctuation">
13143                                                                 <xsl:with-param name="chopString" select="."/>
13144                                                         </xsl:call-template>
13145                                                 </dateIssued>
13146                                         </xsl:when>
13147                                         <xsl:when test="@code='g'">
13148                                                 <dateCreated>
13149                                                         <xsl:value-of select="."/>
13150                                                 </dateCreated>
13151                                         </xsl:when>
13152                                 </xsl:choose>
13153                         </xsl:for-each>
13154                         <xsl:variable name="dataField260c">
13155                                 <xsl:call-template name="chopPunctuation">
13156                                         <xsl:with-param name="chopString" select="marc:datafield[@tag=260]/marc:subfield[@code='c']"></xsl:with-param>
13157                                 </xsl:call-template>
13158                         </xsl:variable>
13159                         <xsl:variable name="controlField008-7-10" select="normalize-space(substring($controlField008, 8, 4))"></xsl:variable>
13160                         <xsl:variable name="controlField008-11-14" select="normalize-space(substring($controlField008, 12, 4))"></xsl:variable>
13161                         <xsl:variable name="controlField008-6" select="normalize-space(substring($controlField008, 7, 1))"></xsl:variable>
13162                         <xsl:if test="$controlField008-6='e' or $controlField008-6='p' or $controlField008-6='r' or $controlField008-6='t' or $controlField008-6='s'">
13163                                 <xsl:if test="$controlField008-7-10 and ($controlField008-7-10 != $dataField260c)">
13164                                         <dateIssued encoding="marc">
13165                                                 <xsl:value-of select="$controlField008-7-10"/>
13166                                         </dateIssued>
13167                                 </xsl:if>
13168                         </xsl:if>
13169                         <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'">
13170                                 <xsl:if test="$controlField008-7-10">
13171                                         <dateIssued encoding="marc" point="start">
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-11-14">
13178                                         <dateIssued encoding="marc" point="end">
13179                                                 <xsl:value-of select="$controlField008-11-14"/>
13180                                         </dateIssued>
13181                                 </xsl:if>
13182                         </xsl:if>
13183                         <xsl:if test="$controlField008-6='q'">
13184                                 <xsl:if test="$controlField008-7-10">
13185                                         <dateIssued encoding="marc" point="start" qualifier="questionable">
13186                                                 <xsl:value-of select="$controlField008-7-10"/>
13187                                         </dateIssued>
13188                                 </xsl:if>
13189                         </xsl:if>
13190                         <xsl:if test="$controlField008-6='q'">
13191                                 <xsl:if test="$controlField008-11-14">
13192                                         <dateIssued encoding="marc" point="end" qualifier="questionable">
13193                                                 <xsl:value-of select="$controlField008-11-14"/>
13194                                         </dateIssued>
13195                                 </xsl:if>
13196                         </xsl:if>
13197                         <xsl:if test="$controlField008-6='t'">
13198                                 <xsl:if test="$controlField008-11-14">
13199                                         <copyrightDate encoding="marc">
13200                                                 <xsl:value-of select="$controlField008-11-14"/>
13201                                         </copyrightDate>
13202                                 </xsl:if>
13203                         </xsl:if>
13204                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=0 or @ind1=1]/marc:subfield[@code='a']">
13205                                 <dateCaptured encoding="iso8601">
13206                                         <xsl:value-of select="."/>
13207                                 </dateCaptured>
13208                         </xsl:for-each>
13209                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][1]">
13210                                 <dateCaptured encoding="iso8601" point="start">
13211                                         <xsl:value-of select="."/>
13212                                 </dateCaptured>
13213                         </xsl:for-each>
13214                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][2]">
13215                                 <dateCaptured encoding="iso8601" point="end">
13216                                         <xsl:value-of select="."/>
13217                                 </dateCaptured>
13218                         </xsl:for-each>
13219                         <xsl:for-each select="marc:datafield[@tag=250]/marc:subfield[@code='a']">
13220                                 <edition>
13221                                         <xsl:value-of select="."/>
13222                                 </edition>
13223                         </xsl:for-each>
13224                         <xsl:for-each select="marc:leader">
13225                                 <issuance>
13226                                         <xsl:choose>
13227                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">monographic</xsl:when>
13228                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">continuing</xsl:when>
13229                                         </xsl:choose>
13230                                 </issuance>
13231                         </xsl:for-each>
13232                         <xsl:for-each select="marc:datafield[@tag=310]|marc:datafield[@tag=321]">
13233                                 <frequency>
13234                                         <xsl:call-template name="subfieldSelect">
13235                                                 <xsl:with-param name="codes">ab</xsl:with-param>
13236                                         </xsl:call-template>
13237                                 </frequency>
13238                         </xsl:for-each>
13239                 </originInfo>
13240                 <xsl:variable name="controlField008-35-37" select="normalize-space(translate(substring($controlField008,36,3),'|#',''))"></xsl:variable>
13241                 <xsl:if test="$controlField008-35-37">
13242                         <language>
13243                                 <languageTerm authority="iso639-2b" type="code">
13244                                         <xsl:value-of select="substring($controlField008,36,3)"/>
13245                                 </languageTerm>
13246                         </language>
13247                 </xsl:if>
13248                 <xsl:for-each select="marc:datafield[@tag=041]">
13249                         <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']">
13250                                 <xsl:variable name="langCodes" select="."/>
13251                                 <xsl:choose>
13252                                         <xsl:when test="../marc:subfield[@code='2']='rfc3066'">
13253                                                 <!-- not stacked but could be repeated -->
13254                                                 <xsl:call-template name="rfcLanguages">
13255                                                         <xsl:with-param name="nodeNum">
13256                                                                 <xsl:value-of select="1"/>
13257                                                         </xsl:with-param>
13258                                                         <xsl:with-param name="usedLanguages">
13259                                                                 <xsl:text></xsl:text>
13260                                                         </xsl:with-param>
13261                                                         <xsl:with-param name="controlField008-35-37">
13262                                                                 <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13263                                                         </xsl:with-param>
13264                                                 </xsl:call-template>
13265                                         </xsl:when>
13266                                         <xsl:otherwise>
13267                                                 <!-- iso -->
13268                                                 <xsl:variable name="allLanguages">
13269                                                         <xsl:copy-of select="$langCodes"></xsl:copy-of>
13270                                                 </xsl:variable>
13271                                                 <xsl:variable name="currentLanguage">
13272                                                         <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13273                                                 </xsl:variable>
13274                                                 <xsl:call-template name="isoLanguage">
13275                                                         <xsl:with-param name="currentLanguage">
13276                                                                 <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
13277                                                         </xsl:with-param>
13278                                                         <xsl:with-param name="remainingLanguages">
13279                                                                 <xsl:value-of select="substring($allLanguages,4,string-length($allLanguages)-3)"></xsl:value-of>
13280                                                         </xsl:with-param>
13281                                                         <xsl:with-param name="usedLanguages">
13282                                                                 <xsl:if test="$controlField008-35-37">
13283                                                                         <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
13284                                                                 </xsl:if>
13285                                                         </xsl:with-param>
13286                                                 </xsl:call-template>
13287                                         </xsl:otherwise>
13288                                 </xsl:choose>
13289                         </xsl:for-each>
13290                 </xsl:for-each>
13291                 <xsl:variable name="physicalDescription">
13292                         <!--3.2 change tmee 007/11 -->
13293                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='a']">
13294                                 <digitalOrigin>reformatted digital</digitalOrigin>
13295                         </xsl:if>
13296                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='b']">
13297                                 <digitalOrigin>digitized microfilm</digitalOrigin>
13298                         </xsl:if>
13299                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='d']">
13300                                 <digitalOrigin>digitized other analog</digitalOrigin>
13301                         </xsl:if>
13302                         <xsl:variable name="controlField008-23" select="substring($controlField008,24,1)"></xsl:variable>
13303                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
13304                         <xsl:variable name="check008-23">
13305                                 <xsl:if test="$typeOf008='BK' or $typeOf008='MU' or $typeOf008='SE' or $typeOf008='MM'">
13306                                         <xsl:value-of select="true()"></xsl:value-of>
13307                                 </xsl:if>
13308                         </xsl:variable>
13309                         <xsl:variable name="check008-29">
13310                                 <xsl:if test="$typeOf008='MP' or $typeOf008='VM'">
13311                                         <xsl:value-of select="true()"></xsl:value-of>
13312                                 </xsl:if>
13313                         </xsl:variable>
13314                         <xsl:choose>
13315                                 <xsl:when test="($check008-23 and $controlField008-23='f') or ($check008-29 and $controlField008-29='f')">
13316                                         <form authority="marcform">braille</form>
13317                                 </xsl:when>
13318                                 <xsl:when test="($controlField008-23=' ' and ($leader6='c' or $leader6='d')) or (($typeOf008='BK' or $typeOf008='SE') and ($controlField008-23=' ' or $controlField008='r'))">
13319                                         <form authority="marcform">print</form>
13320                                 </xsl:when>
13321                                 <xsl:when test="$leader6 = 'm' or ($check008-23 and $controlField008-23='s') or ($check008-29 and $controlField008-29='s')">
13322                                         <form authority="marcform">electronic</form>
13323                                 </xsl:when>
13324                                 <xsl:when test="($check008-23 and $controlField008-23='b') or ($check008-29 and $controlField008-29='b')">
13325                                         <form authority="marcform">microfiche</form>
13326                                 </xsl:when>
13327                                 <xsl:when test="($check008-23 and $controlField008-23='a') or ($check008-29 and $controlField008-29='a')">
13328                                         <form authority="marcform">microfilm</form>
13329                                 </xsl:when>
13330                         </xsl:choose>
13331                         <!-- 1/04 fix -->
13332                         <xsl:if test="marc:datafield[@tag=130]/marc:subfield[@code='h']">
13333                                 <form authority="gmd">
13334                                         <xsl:call-template name="chopBrackets">
13335                                                 <xsl:with-param name="chopString">
13336                                                         <xsl:value-of select="marc:datafield[@tag=130]/marc:subfield[@code='h']"></xsl:value-of>
13337                                                 </xsl:with-param>
13338                                         </xsl:call-template>
13339                                 </form>
13340                         </xsl:if>
13341                         <xsl:if test="marc:datafield[@tag=240]/marc:subfield[@code='h']">
13342                                 <form authority="gmd">
13343                                         <xsl:call-template name="chopBrackets">
13344                                                 <xsl:with-param name="chopString">
13345                                                         <xsl:value-of select="marc:datafield[@tag=240]/marc:subfield[@code='h']"></xsl:value-of>
13346                                                 </xsl:with-param>
13347                                         </xsl:call-template>
13348                                 </form>
13349                         </xsl:if>
13350                         <xsl:if test="marc:datafield[@tag=242]/marc:subfield[@code='h']">
13351                                 <form authority="gmd">
13352                                         <xsl:call-template name="chopBrackets">
13353                                                 <xsl:with-param name="chopString">
13354                                                         <xsl:value-of select="marc:datafield[@tag=242]/marc:subfield[@code='h']"></xsl:value-of>
13355                                                 </xsl:with-param>
13356                                         </xsl:call-template>
13357                                 </form>
13358                         </xsl:if>
13359                         <xsl:if test="marc:datafield[@tag=245]/marc:subfield[@code='h']">
13360                                 <form authority="gmd">
13361                                         <xsl:call-template name="chopBrackets">
13362                                                 <xsl:with-param name="chopString">
13363                                                         <xsl:value-of select="marc:datafield[@tag=245]/marc:subfield[@code='h']"></xsl:value-of>
13364                                                 </xsl:with-param>
13365                                         </xsl:call-template>
13366                                 </form>
13367                         </xsl:if>
13368                         <xsl:if test="marc:datafield[@tag=246]/marc:subfield[@code='h']">
13369                                 <form authority="gmd">
13370                                         <xsl:call-template name="chopBrackets">
13371                                                 <xsl:with-param name="chopString">
13372                                                         <xsl:value-of select="marc:datafield[@tag=246]/marc:subfield[@code='h']"></xsl:value-of>
13373                                                 </xsl:with-param>
13374                                         </xsl:call-template>
13375                                 </form>
13376                         </xsl:if>
13377                         <xsl:if test="marc:datafield[@tag=730]/marc:subfield[@code='h']">
13378                                 <form authority="gmd">
13379                                         <xsl:call-template name="chopBrackets">
13380                                                 <xsl:with-param name="chopString">
13381                                                         <xsl:value-of select="marc:datafield[@tag=730]/marc:subfield[@code='h']"></xsl:value-of>
13382                                                 </xsl:with-param>
13383                                         </xsl:call-template>
13384                                 </form>
13385                         </xsl:if>
13386                         <xsl:for-each select="marc:datafield[@tag=256]/marc:subfield[@code='a']">
13387                                 <form>
13388                                         <xsl:value-of select="."></xsl:value-of>
13389                                 </form>
13390                         </xsl:for-each>
13391                         <xsl:for-each select="marc:controlfield[@tag=007][substring(text(),1,1)='c']">
13392                                 <xsl:choose>
13393                                         <xsl:when test="substring(text(),14,1)='a'">
13394                                                 <reformattingQuality>access</reformattingQuality>
13395                                         </xsl:when>
13396                                         <xsl:when test="substring(text(),14,1)='p'">
13397                                                 <reformattingQuality>preservation</reformattingQuality>
13398                                         </xsl:when>
13399                                         <xsl:when test="substring(text(),14,1)='r'">
13400                                                 <reformattingQuality>replacement</reformattingQuality>
13401                                         </xsl:when>
13402                                 </xsl:choose>
13403                         </xsl:for-each>
13404                         <!--3.2 change tmee 007/01 -->
13405                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='b']">
13406                                 <form authority="smd">chip cartridge</form>
13407                         </xsl:if>
13408                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='c']">
13409                                 <form authority="smd">computer optical disc cartridge</form>
13410                         </xsl:if>
13411                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='j']">
13412                                 <form authority="smd">magnetic disc</form>
13413                         </xsl:if>
13414                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='m']">
13415                                 <form authority="smd">magneto-optical disc</form>
13416                         </xsl:if>
13417                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='o']">
13418                                 <form authority="smd">optical disc</form>
13419                         </xsl:if>
13420                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='r']">
13421                                 <form authority="smd">remote</form>
13422                         </xsl:if>
13423                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='a']">
13424                                 <form authority="smd">tape cartridge</form>
13425                         </xsl:if>
13426                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='f']">
13427                                 <form authority="smd">tape cassette</form>
13428                         </xsl:if>
13429                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='h']">
13430                                 <form authority="smd">tape reel</form>
13431                         </xsl:if>
13432                         
13433                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='a']">
13434                                 <form authority="smd">celestial globe</form>
13435                         </xsl:if>
13436                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='e']">
13437                                 <form authority="smd">earth moon globe</form>
13438                         </xsl:if>
13439                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='b']">
13440                                 <form authority="smd">planetary or lunar globe</form>
13441                         </xsl:if>
13442                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='c']">
13443                                 <form authority="smd">terrestrial globe</form>
13444                         </xsl:if>
13445                         
13446                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='o'][substring(text(),2,1)='o']">
13447                                 <form authority="smd">kit</form>
13448                         </xsl:if>
13449                         
13450                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
13451                                 <form authority="smd">atlas</form>
13452                         </xsl:if>
13453                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='g']">
13454                                 <form authority="smd">diagram</form>
13455                         </xsl:if>
13456                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
13457                                 <form authority="smd">map</form>
13458                         </xsl:if>
13459                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
13460                                 <form authority="smd">model</form>
13461                         </xsl:if>
13462                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='k']">
13463                                 <form authority="smd">profile</form>
13464                         </xsl:if>
13465                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
13466                                 <form authority="smd">remote-sensing image</form>
13467                         </xsl:if>
13468                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='s']">
13469                                 <form authority="smd">section</form>
13470                         </xsl:if>
13471                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='y']">
13472                                 <form authority="smd">view</form>
13473                         </xsl:if>
13474                         
13475                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='a']">
13476                                 <form authority="smd">aperture card</form>
13477                         </xsl:if>
13478                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='e']">
13479                                 <form authority="smd">microfiche</form>
13480                         </xsl:if>
13481                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='f']">
13482                                 <form authority="smd">microfiche cassette</form>
13483                         </xsl:if>
13484                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='b']">
13485                                 <form authority="smd">microfilm cartridge</form>
13486                         </xsl:if>
13487                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='c']">
13488                                 <form authority="smd">microfilm cassette</form>
13489                         </xsl:if>
13490                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='d']">
13491                                 <form authority="smd">microfilm reel</form>
13492                         </xsl:if>
13493                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='g']">
13494                                 <form authority="smd">microopaque</form>
13495                         </xsl:if>
13496                         
13497                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='c']">
13498                                 <form authority="smd">film cartridge</form>
13499                         </xsl:if>
13500                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='f']">
13501                                 <form authority="smd">film cassette</form>
13502                         </xsl:if>
13503                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='r']">
13504                                 <form authority="smd">film reel</form>
13505                         </xsl:if>
13506                         
13507                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='n']">
13508                                 <form authority="smd">chart</form>
13509                         </xsl:if>
13510                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='c']">
13511                                 <form authority="smd">collage</form>
13512                         </xsl:if>
13513                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='d']">
13514                                 <form authority="smd">drawing</form>
13515                         </xsl:if>
13516                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='o']">
13517                                 <form authority="smd">flash card</form>
13518                         </xsl:if>
13519                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='e']">
13520                                 <form authority="smd">painting</form>
13521                         </xsl:if>
13522                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='f']">
13523                                 <form authority="smd">photomechanical print</form>
13524                         </xsl:if>
13525                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='g']">
13526                                 <form authority="smd">photonegative</form>
13527                         </xsl:if>
13528                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='h']">
13529                                 <form authority="smd">photoprint</form>
13530                         </xsl:if>
13531                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='i']">
13532                                 <form authority="smd">picture</form>
13533                         </xsl:if>
13534                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='j']">
13535                                 <form authority="smd">print</form>
13536                         </xsl:if>
13537                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='l']">
13538                                 <form authority="smd">technical drawing</form>
13539                         </xsl:if>
13540                         
13541                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='q'][substring(text(),2,1)='q']">
13542                                 <form authority="smd">notated music</form>
13543                         </xsl:if>
13544                         
13545                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='d']">
13546                                 <form authority="smd">filmslip</form>
13547                         </xsl:if>
13548                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='c']">
13549                                 <form authority="smd">filmstrip cartridge</form>
13550                         </xsl:if>
13551                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='o']">
13552                                 <form authority="smd">filmstrip roll</form>
13553                         </xsl:if>
13554                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='f']">
13555                                 <form authority="smd">other filmstrip type</form>
13556                         </xsl:if>
13557                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='s']">
13558                                 <form authority="smd">slide</form>
13559                         </xsl:if>
13560                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='t']">
13561                                 <form authority="smd">transparency</form>
13562                         </xsl:if>
13563                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='r'][substring(text(),2,1)='r']">
13564                                 <form authority="smd">remote-sensing image</form>
13565                         </xsl:if>
13566                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='e']">
13567                                 <form authority="smd">cylinder</form>
13568                         </xsl:if>
13569                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='q']">
13570                                 <form authority="smd">roll</form>
13571                         </xsl:if>
13572                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='g']">
13573                                 <form authority="smd">sound cartridge</form>
13574                         </xsl:if>
13575                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='s']">
13576                                 <form authority="smd">sound cassette</form>
13577                         </xsl:if>
13578                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='d']">
13579                                 <form authority="smd">sound disc</form>
13580                         </xsl:if>
13581                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='t']">
13582                                 <form authority="smd">sound-tape reel</form>
13583                         </xsl:if>
13584                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='i']">
13585                                 <form authority="smd">sound-track film</form>
13586                         </xsl:if>
13587                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='w']">
13588                                 <form authority="smd">wire recording</form>
13589                         </xsl:if>
13590                         
13591                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='c']">
13592                                 <form authority="smd">braille</form>
13593                         </xsl:if>
13594                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='b']">
13595                                 <form authority="smd">combination</form>
13596                         </xsl:if>
13597                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='a']">
13598                                 <form authority="smd">moon</form>
13599                         </xsl:if>
13600                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='d']">
13601                                 <form authority="smd">tactile, with no writing system</form>
13602                         </xsl:if>
13603                         
13604                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='c']">
13605                                 <form authority="smd">braille</form>
13606                         </xsl:if>
13607                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='b']">
13608                                 <form authority="smd">large print</form>
13609                         </xsl:if>
13610                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='a']">
13611                                 <form authority="smd">regular print</form>
13612                         </xsl:if>
13613                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='d']">
13614                                 <form authority="smd">text in looseleaf binder</form>
13615                         </xsl:if>
13616                         
13617                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='c']">
13618                                 <form authority="smd">videocartridge</form>
13619                         </xsl:if>
13620                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='f']">
13621                                 <form authority="smd">videocassette</form>
13622                         </xsl:if>
13623                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='d']">
13624                                 <form authority="smd">videodisc</form>
13625                         </xsl:if>
13626                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='r']">
13627                                 <form authority="smd">videoreel</form>
13628                         </xsl:if>
13629                         
13630                         <xsl:for-each select="marc:datafield[@tag=856]/marc:subfield[@code='q'][string-length(.)>1]">
13631                                 <internetMediaType>
13632                                         <xsl:value-of select="."></xsl:value-of>
13633                                 </internetMediaType>
13634                         </xsl:for-each>
13635                         <xsl:for-each select="marc:datafield[@tag=300]">
13636                                 <extent>
13637                                         <xsl:call-template name="subfieldSelect">
13638                                                 <xsl:with-param name="codes">abce</xsl:with-param>
13639                                         </xsl:call-template>
13640                                 </extent>
13641                         </xsl:for-each>
13642                 </xsl:variable>
13643                 <xsl:if test="string-length(normalize-space($physicalDescription))">
13644                         <physicalDescription>
13645                                 <xsl:copy-of select="$physicalDescription"></xsl:copy-of>
13646                         </physicalDescription>
13647                 </xsl:if>
13648                 <xsl:for-each select="marc:datafield[@tag=520]">
13649                         <abstract>
13650                                 <xsl:call-template name="uri"></xsl:call-template>
13651                                 <xsl:call-template name="subfieldSelect">
13652                                         <xsl:with-param name="codes">ab</xsl:with-param>
13653                                 </xsl:call-template>
13654                         </abstract>
13655                 </xsl:for-each>
13656                 <xsl:for-each select="marc:datafield[@tag=505]">
13657                         <tableOfContents>
13658                                 <xsl:call-template name="uri"></xsl:call-template>
13659                                 <xsl:call-template name="subfieldSelect">
13660                                         <xsl:with-param name="codes">agrt</xsl:with-param>
13661                                 </xsl:call-template>
13662                         </tableOfContents>
13663                 </xsl:for-each>
13664                 <xsl:for-each select="marc:datafield[@tag=521]">
13665                         <targetAudience>
13666                                 <xsl:call-template name="subfieldSelect">
13667                                         <xsl:with-param name="codes">ab</xsl:with-param>
13668                                 </xsl:call-template>
13669                         </targetAudience>
13670                 </xsl:for-each>
13671                 <xsl:if test="$typeOf008='BK' or $typeOf008='CF' or $typeOf008='MU' or $typeOf008='VM'">
13672                         <xsl:variable name="controlField008-22" select="substring($controlField008,23,1)"></xsl:variable>
13673                         <xsl:choose>
13674                                 <!-- 01/04 fix -->
13675                                 <xsl:when test="$controlField008-22='d'">
13676                                         <targetAudience authority="marctarget">adolescent</targetAudience>
13677                                 </xsl:when>
13678                                 <xsl:when test="$controlField008-22='e'">
13679                                         <targetAudience authority="marctarget">adult</targetAudience>
13680                                 </xsl:when>
13681                                 <xsl:when test="$controlField008-22='g'">
13682                                         <targetAudience authority="marctarget">general</targetAudience>
13683                                 </xsl:when>
13684                                 <xsl:when test="$controlField008-22='b' or $controlField008-22='c' or $controlField008-22='j'">
13685                                         <targetAudience authority="marctarget">juvenile</targetAudience>
13686                                 </xsl:when>
13687                                 <xsl:when test="$controlField008-22='a'">
13688                                         <targetAudience authority="marctarget">preschool</targetAudience>
13689                                 </xsl:when>
13690                                 <xsl:when test="$controlField008-22='f'">
13691                                         <targetAudience authority="marctarget">specialized</targetAudience>
13692                                 </xsl:when>
13693                         </xsl:choose>
13694                 </xsl:if>
13695                 <xsl:for-each select="marc:datafield[@tag=245]/marc:subfield[@code='c']">
13696                         <note type="statement of responsibility">
13697                                 <xsl:value-of select="."></xsl:value-of>
13698                         </note>
13699                 </xsl:for-each>
13700                 <xsl:for-each select="marc:datafield[@tag=500]">
13701                         <note>
13702                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13703                                 <xsl:call-template name="uri"></xsl:call-template>
13704                         </note>
13705                 </xsl:for-each>
13706                 
13707                 <!--3.2 change tmee additional note fields-->
13708                 
13709                 <xsl:for-each select="marc:datafield[@tag=506]">
13710                         <note type="restrictions">
13711                                 <xsl:call-template name="uri"></xsl:call-template>
13712                                 <xsl:variable name="str">
13713                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13714                                                 <xsl:value-of select="."></xsl:value-of>
13715                                                 <xsl:text> </xsl:text>
13716                                         </xsl:for-each>
13717                                 </xsl:variable>
13718                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13719                         </note>
13720                 </xsl:for-each>
13721                 
13722                 <xsl:for-each select="marc:datafield[@tag=510]">
13723                         <note  type="citation/reference">
13724                                 <xsl:call-template name="uri"></xsl:call-template>
13725                                 <xsl:variable name="str">
13726                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13727                                                 <xsl:value-of select="."></xsl:value-of>
13728                                                 <xsl:text> </xsl:text>
13729                                         </xsl:for-each>
13730                                 </xsl:variable>
13731                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13732                         </note>
13733                 </xsl:for-each>
13734                 
13735                         
13736                 <xsl:for-each select="marc:datafield[@tag=511]">
13737                         <note type="performers">
13738                                 <xsl:call-template name="uri"></xsl:call-template>
13739                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13740                         </note>
13741                 </xsl:for-each>
13742                 <xsl:for-each select="marc:datafield[@tag=518]">
13743                         <note type="venue">
13744                                 <xsl:call-template name="uri"></xsl:call-template>
13745                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
13746                         </note>
13747                 </xsl:for-each>
13748                 
13749                 <xsl:for-each select="marc:datafield[@tag=530]">
13750                         <note  type="additional physical form">
13751                                 <xsl:call-template name="uri"></xsl:call-template>
13752                                 <xsl:variable name="str">
13753                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13754                                                 <xsl:value-of select="."></xsl:value-of>
13755                                                 <xsl:text> </xsl:text>
13756                                         </xsl:for-each>
13757                                 </xsl:variable>
13758                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13759                         </note>
13760                 </xsl:for-each>
13761                 
13762                 <xsl:for-each select="marc:datafield[@tag=533]">
13763                         <note  type="reproduction">
13764                                 <xsl:call-template name="uri"></xsl:call-template>
13765                                 <xsl:variable name="str">
13766                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13767                                                 <xsl:value-of select="."></xsl:value-of>
13768                                                 <xsl:text> </xsl:text>
13769                                         </xsl:for-each>
13770                                 </xsl:variable>
13771                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13772                         </note>
13773                 </xsl:for-each>
13774                 
13775                 <xsl:for-each select="marc:datafield[@tag=534]">
13776                         <note  type="original version">
13777                                 <xsl:call-template name="uri"></xsl:call-template>
13778                                 <xsl:variable name="str">
13779                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13780                                                 <xsl:value-of select="."></xsl:value-of>
13781                                                 <xsl:text> </xsl:text>
13782                                         </xsl:for-each>
13783                                 </xsl:variable>
13784                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13785                         </note>
13786                 </xsl:for-each>
13787                 
13788                 <xsl:for-each select="marc:datafield[@tag=538]">
13789                         <note  type="system details">
13790                                 <xsl:call-template name="uri"></xsl:call-template>
13791                                 <xsl:variable name="str">
13792                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13793                                                 <xsl:value-of select="."></xsl:value-of>
13794                                                 <xsl:text> </xsl:text>
13795                                         </xsl:for-each>
13796                                 </xsl:variable>
13797                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13798                         </note>
13799                 </xsl:for-each>
13800                 
13801                 <xsl:for-each select="marc:datafield[@tag=583]">
13802                         <note type="action">
13803                                 <xsl:call-template name="uri"></xsl:call-template>
13804                                 <xsl:variable name="str">
13805                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13806                                                 <xsl:value-of select="."></xsl:value-of>
13807                                                 <xsl:text> </xsl:text>
13808                                         </xsl:for-each>
13809                                 </xsl:variable>
13810                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13811                         </note>
13812                 </xsl:for-each>
13813                 
13814
13815                 
13816                 
13817                 
13818                 <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]">
13819                         <note>
13820                                 <xsl:call-template name="uri"></xsl:call-template>
13821                                 <xsl:variable name="str">
13822                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
13823                                                 <xsl:value-of select="."></xsl:value-of>
13824                                                 <xsl:text> </xsl:text>
13825                                         </xsl:for-each>
13826                                 </xsl:variable>
13827                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
13828                         </note>
13829                 </xsl:for-each>
13830                 <xsl:for-each select="marc:datafield[@tag=034][marc:subfield[@code='d' or @code='e' or @code='f' or @code='g']]">
13831                         <subject>
13832                                 <cartographics>
13833                                         <coordinates>
13834                                                 <xsl:call-template name="subfieldSelect">
13835                                                         <xsl:with-param name="codes">defg</xsl:with-param>
13836                                                 </xsl:call-template>
13837                                         </coordinates>
13838                                 </cartographics>
13839                         </subject>
13840                 </xsl:for-each>
13841                 <xsl:for-each select="marc:datafield[@tag=043]">
13842                         <subject>
13843                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13844                                         <geographicCode>
13845                                                 <xsl:attribute name="authority">
13846                                                         <xsl:if test="@code='a'">
13847                                                                 <xsl:text>marcgac</xsl:text>
13848                                                         </xsl:if>
13849                                                         <xsl:if test="@code='b'">
13850                                                                 <xsl:value-of select="following-sibling::marc:subfield[@code=2]"></xsl:value-of>
13851                                                         </xsl:if>
13852                                                         <xsl:if test="@code='c'">
13853                                                                 <xsl:text>iso3166</xsl:text>
13854                                                         </xsl:if>
13855                                                 </xsl:attribute>
13856                                                 <xsl:value-of select="self::marc:subfield"></xsl:value-of>
13857                                         </geographicCode>
13858                                 </xsl:for-each>
13859                         </subject>
13860                 </xsl:for-each>
13861                 <!-- tmee 2006/11/27 -->
13862                 <xsl:for-each select="marc:datafield[@tag=255]">
13863                         <subject>
13864                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
13865                                 <cartographics>
13866                                         <xsl:if test="@code='a'">
13867                                                 <scale>
13868                                                         <xsl:value-of select="."></xsl:value-of>
13869                                                 </scale>
13870                                         </xsl:if>
13871                                         <xsl:if test="@code='b'">
13872                                                 <projection>
13873                                                         <xsl:value-of select="."></xsl:value-of>
13874                                                 </projection>
13875                                         </xsl:if>
13876                                         <xsl:if test="@code='c'">
13877                                                 <coordinates>
13878                                                         <xsl:value-of select="."></xsl:value-of>
13879                                                 </coordinates>
13880                                         </xsl:if>
13881                                 </cartographics>
13882                                 </xsl:for-each>
13883                         </subject>
13884                 </xsl:for-each>
13885                                 
13886                 <xsl:apply-templates select="marc:datafield[653 >= @tag and @tag >= 600]"></xsl:apply-templates>
13887                 <xsl:apply-templates select="marc:datafield[@tag=656]"></xsl:apply-templates>
13888                 <xsl:for-each select="marc:datafield[@tag=752]">
13889                         <subject>
13890                                 <hierarchicalGeographic>
13891                                         <xsl:for-each select="marc:subfield[@code='a']">
13892                                                 <country>
13893                                                         <xsl:call-template name="chopPunctuation">
13894                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13895                                                         </xsl:call-template>
13896                                                 </country>
13897                                         </xsl:for-each>
13898                                         <xsl:for-each select="marc:subfield[@code='b']">
13899                                                 <state>
13900                                                         <xsl:call-template name="chopPunctuation">
13901                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13902                                                         </xsl:call-template>
13903                                                 </state>
13904                                         </xsl:for-each>
13905                                         <xsl:for-each select="marc:subfield[@code='c']">
13906                                                 <county>
13907                                                         <xsl:call-template name="chopPunctuation">
13908                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13909                                                         </xsl:call-template>
13910                                                 </county>
13911                                         </xsl:for-each>
13912                                         <xsl:for-each select="marc:subfield[@code='d']">
13913                                                 <city>
13914                                                         <xsl:call-template name="chopPunctuation">
13915                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
13916                                                         </xsl:call-template>
13917                                                 </city>
13918                                         </xsl:for-each>
13919                                 </hierarchicalGeographic>
13920                         </subject>
13921                 </xsl:for-each>
13922                 <xsl:for-each select="marc:datafield[@tag=045][marc:subfield[@code='b']]">
13923                         <subject>
13924                                 <xsl:choose>
13925                                         <xsl:when test="@ind1=2">
13926                                                 <temporal encoding="iso8601" point="start">
13927                                                         <xsl:call-template name="chopPunctuation">
13928                                                                 <xsl:with-param name="chopString">
13929                                                                         <xsl:value-of select="marc:subfield[@code='b'][1]"></xsl:value-of>
13930                                                                 </xsl:with-param>
13931                                                         </xsl:call-template>
13932                                                 </temporal>
13933                                                 <temporal encoding="iso8601" point="end">
13934                                                         <xsl:call-template name="chopPunctuation">
13935                                                                 <xsl:with-param name="chopString">
13936                                                                         <xsl:value-of select="marc:subfield[@code='b'][2]"></xsl:value-of>
13937                                                                 </xsl:with-param>
13938                                                         </xsl:call-template>
13939                                                 </temporal>
13940                                         </xsl:when>
13941                                         <xsl:otherwise>
13942                                                 <xsl:for-each select="marc:subfield[@code='b']">
13943                                                         <temporal encoding="iso8601">
13944                                                                 <xsl:call-template name="chopPunctuation">
13945                                                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
13946                                                                 </xsl:call-template>
13947                                                         </temporal>
13948                                                 </xsl:for-each>
13949                                         </xsl:otherwise>
13950                                 </xsl:choose>
13951                         </subject>
13952                 </xsl:for-each>
13953                 <xsl:for-each select="marc:datafield[@tag=050]">
13954                         <xsl:for-each select="marc:subfield[@code='b']">
13955                                 <classification authority="lcc">
13956                                         <xsl:if test="../marc:subfield[@code='3']">
13957                                                 <xsl:attribute name="displayLabel">
13958                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
13959                                                 </xsl:attribute>
13960                                         </xsl:if>
13961                                         <xsl:value-of select="preceding-sibling::marc:subfield[@code='a'][1]"></xsl:value-of>
13962                                         <xsl:text> </xsl:text>
13963                                         <xsl:value-of select="text()"></xsl:value-of>
13964                                 </classification>
13965                         </xsl:for-each>
13966                         <xsl:for-each select="marc:subfield[@code='a'][not(following-sibling::marc:subfield[@code='b'])]">
13967                                 <classification authority="lcc">
13968                                         <xsl:if test="../marc:subfield[@code='3']">
13969                                                 <xsl:attribute name="displayLabel">
13970                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
13971                                                 </xsl:attribute>
13972                                         </xsl:if>
13973                                         <xsl:value-of select="text()"></xsl:value-of>
13974                                 </classification>
13975                         </xsl:for-each>
13976                 </xsl:for-each>
13977                 <xsl:for-each select="marc:datafield[@tag=082]">
13978                         <classification authority="ddc">
13979                                 <xsl:if test="marc:subfield[@code='2']">
13980                                         <xsl:attribute name="edition">
13981                                                 <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
13982                                         </xsl:attribute>
13983                                 </xsl:if>
13984                                 <xsl:call-template name="subfieldSelect">
13985                                         <xsl:with-param name="codes">ab</xsl:with-param>
13986                                 </xsl:call-template>
13987                         </classification>
13988                 </xsl:for-each>
13989                 <xsl:for-each select="marc:datafield[@tag=080]">
13990                         <classification authority="udc">
13991                                 <xsl:call-template name="subfieldSelect">
13992                                         <xsl:with-param name="codes">abx</xsl:with-param>
13993                                 </xsl:call-template>
13994                         </classification>
13995                 </xsl:for-each>
13996                 <xsl:for-each select="marc:datafield[@tag=060]">
13997                         <classification authority="nlm">
13998                                 <xsl:call-template name="subfieldSelect">
13999                                         <xsl:with-param name="codes">ab</xsl:with-param>
14000                                 </xsl:call-template>
14001                         </classification>
14002                 </xsl:for-each>
14003                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=0]">
14004                         <classification authority="sudocs">
14005                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14006                         </classification>
14007                 </xsl:for-each>
14008                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=1]">
14009                         <classification authority="candoc">
14010                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14011                         </classification>
14012                 </xsl:for-each>
14013                 <xsl:for-each select="marc:datafield[@tag=086]">
14014                         <classification>
14015                                 <xsl:attribute name="authority">
14016                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14017                                 </xsl:attribute>
14018                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14019                         </classification>
14020                 </xsl:for-each>
14021                 <xsl:for-each select="marc:datafield[@tag=084]">
14022                         <classification>
14023                                 <xsl:attribute name="authority">
14024                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
14025                                 </xsl:attribute>
14026                                 <xsl:call-template name="subfieldSelect">
14027                                         <xsl:with-param name="codes">ab</xsl:with-param>
14028                                 </xsl:call-template>
14029                         </classification>
14030                 </xsl:for-each>
14031                 <xsl:for-each select="marc:datafield[@tag=440]">
14032                         <relatedItem type="series">
14033                                 <titleInfo>
14034                                         <title>
14035                                                 <xsl:call-template name="chopPunctuation">
14036                                                         <xsl:with-param name="chopString">
14037                                                                 <xsl:call-template name="subfieldSelect">
14038                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14039                                                                 </xsl:call-template>
14040                                                         </xsl:with-param>
14041                                                 </xsl:call-template>
14042                                         </title>
14043                                         <xsl:call-template name="part"></xsl:call-template>
14044                                 </titleInfo>
14045                         </relatedItem>
14046                 </xsl:for-each>
14047                 <xsl:for-each select="marc:datafield[@tag=490][@ind1=0]">
14048                         <relatedItem type="series">
14049                                 <titleInfo>
14050                                         <title>
14051                                                 <xsl:call-template name="chopPunctuation">
14052                                                         <xsl:with-param name="chopString">
14053                                                                 <xsl:call-template name="subfieldSelect">
14054                                                                         <xsl:with-param name="codes">av</xsl:with-param>
14055                                                                 </xsl:call-template>
14056                                                         </xsl:with-param>
14057                                                 </xsl:call-template>
14058                                         </title>
14059                                         <xsl:call-template name="part"></xsl:call-template>
14060                                 </titleInfo>
14061                         </relatedItem>
14062                 </xsl:for-each>
14063                 <xsl:for-each select="marc:datafield[@tag=510]">
14064                         <relatedItem type="isReferencedBy">
14065                                 <note>
14066                                         <xsl:call-template name="subfieldSelect">
14067                                                 <xsl:with-param name="codes">abcx3</xsl:with-param>
14068                                         </xsl:call-template>
14069                                 </note>
14070                         </relatedItem>
14071                 </xsl:for-each>
14072                 <xsl:for-each select="marc:datafield[@tag=534]">
14073                         <relatedItem type="original">
14074                                 <xsl:call-template name="relatedTitle"></xsl:call-template>
14075                                 <xsl:call-template name="relatedName"></xsl:call-template>
14076                                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
14077                                         <originInfo>
14078                                                 <xsl:for-each select="marc:subfield[@code='c']">
14079                                                         <publisher>
14080                                                                 <xsl:value-of select="."></xsl:value-of>
14081                                                         </publisher>
14082                                                 </xsl:for-each>
14083                                                 <xsl:for-each select="marc:subfield[@code='b']">
14084                                                         <edition>
14085                                                                 <xsl:value-of select="."></xsl:value-of>
14086                                                         </edition>
14087                                                 </xsl:for-each>
14088                                         </originInfo>
14089                                 </xsl:if>
14090                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14091                                 <xsl:for-each select="marc:subfield[@code='z']">
14092                                         <identifier type="isbn">
14093                                                 <xsl:value-of select="."></xsl:value-of>
14094                                         </identifier>
14095                                 </xsl:for-each>
14096                                 <xsl:call-template name="relatedNote"></xsl:call-template>
14097                         </relatedItem>
14098                 </xsl:for-each>
14099                 <xsl:for-each select="marc:datafield[@tag=700][marc:subfield[@code='t']]">
14100                         <relatedItem>
14101                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14102                                 <titleInfo>
14103                                         <title>
14104                                                 <xsl:call-template name="chopPunctuation">
14105                                                         <xsl:with-param name="chopString">
14106                                                                 <xsl:call-template name="specialSubfieldSelect">
14107                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14108                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14109                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14110                                                                 </xsl:call-template>
14111                                                         </xsl:with-param>
14112                                                 </xsl:call-template>
14113                                         </title>
14114                                         <xsl:call-template name="part"></xsl:call-template>
14115                                 </titleInfo>
14116                                 <name type="personal">
14117                                         <namePart>
14118                                                 <xsl:call-template name="specialSubfieldSelect">
14119                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14120                                                         <xsl:with-param name="axis">t</xsl:with-param>
14121                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14122                                                 </xsl:call-template>
14123                                         </namePart>
14124                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14125                                         <xsl:call-template name="nameDate"></xsl:call-template>
14126                                         <xsl:call-template name="role"></xsl:call-template>
14127                                 </name>
14128                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14129                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14130                         </relatedItem>
14131                 </xsl:for-each>
14132                 <xsl:for-each select="marc:datafield[@tag=710][marc:subfield[@code='t']]">
14133                         <relatedItem>
14134                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14135                                 <titleInfo>
14136                                         <title>
14137                                                 <xsl:call-template name="chopPunctuation">
14138                                                         <xsl:with-param name="chopString">
14139                                                                 <xsl:call-template name="specialSubfieldSelect">
14140                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14141                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14142                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14143                                                                 </xsl:call-template>
14144                                                         </xsl:with-param>
14145                                                 </xsl:call-template>
14146                                         </title>
14147                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14148                                 </titleInfo>
14149                                 <name type="corporate">
14150                                         <xsl:for-each select="marc:subfield[@code='a']">
14151                                                 <namePart>
14152                                                         <xsl:value-of select="."></xsl:value-of>
14153                                                 </namePart>
14154                                         </xsl:for-each>
14155                                         <xsl:for-each select="marc:subfield[@code='b']">
14156                                                 <namePart>
14157                                                         <xsl:value-of select="."></xsl:value-of>
14158                                                 </namePart>
14159                                         </xsl:for-each>
14160                                         <xsl:variable name="tempNamePart">
14161                                                 <xsl:call-template name="specialSubfieldSelect">
14162                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14163                                                         <xsl:with-param name="axis">t</xsl:with-param>
14164                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14165                                                 </xsl:call-template>
14166                                         </xsl:variable>
14167                                         <xsl:if test="normalize-space($tempNamePart)">
14168                                                 <namePart>
14169                                                         <xsl:value-of select="$tempNamePart"></xsl:value-of>
14170                                                 </namePart>
14171                                         </xsl:if>
14172                                         <xsl:call-template name="role"></xsl:call-template>
14173                                 </name>
14174                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14175                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14176                         </relatedItem>
14177                 </xsl:for-each>
14178                 <xsl:for-each select="marc:datafield[@tag=711][marc:subfield[@code='t']]">
14179                         <relatedItem>
14180                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14181                                 <titleInfo>
14182                                         <title>
14183                                                 <xsl:call-template name="chopPunctuation">
14184                                                         <xsl:with-param name="chopString">
14185                                                                 <xsl:call-template name="specialSubfieldSelect">
14186                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14187                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14188                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14189                                                                 </xsl:call-template>
14190                                                         </xsl:with-param>
14191                                                 </xsl:call-template>
14192                                         </title>
14193                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14194                                 </titleInfo>
14195                                 <name type="conference">
14196                                         <namePart>
14197                                                 <xsl:call-template name="specialSubfieldSelect">
14198                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14199                                                         <xsl:with-param name="axis">t</xsl:with-param>
14200                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14201                                                 </xsl:call-template>
14202                                         </namePart>
14203                                 </name>
14204                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14205                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14206                         </relatedItem>
14207                 </xsl:for-each>
14208                 <xsl:for-each select="marc:datafield[@tag=730][@ind2=2]">
14209                         <relatedItem>
14210                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14211                                 <titleInfo>
14212                                         <title>
14213                                                 <xsl:call-template name="chopPunctuation">
14214                                                         <xsl:with-param name="chopString">
14215                                                                 <xsl:call-template name="subfieldSelect">
14216                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14217                                                                 </xsl:call-template>
14218                                                         </xsl:with-param>
14219                                                 </xsl:call-template>
14220                                         </title>
14221                                         <xsl:call-template name="part"></xsl:call-template>
14222                                 </titleInfo>
14223                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14224                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14225                         </relatedItem>
14226                 </xsl:for-each>
14227                 <xsl:for-each select="marc:datafield[@tag=740][@ind2=2]">
14228                         <relatedItem>
14229                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
14230                                 <titleInfo>
14231                                         <title>
14232                                                 <xsl:call-template name="chopPunctuation">
14233                                                         <xsl:with-param name="chopString">
14234                                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14235                                                         </xsl:with-param>
14236                                                 </xsl:call-template>
14237                                         </title>
14238                                         <xsl:call-template name="part"></xsl:call-template>
14239                                 </titleInfo>
14240                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14241                         </relatedItem>
14242                 </xsl:for-each>
14243                 <xsl:for-each select="marc:datafield[@tag=760]|marc:datafield[@tag=762]">
14244                         <relatedItem type="series">
14245                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14246                         </relatedItem>
14247                 </xsl:for-each>
14248                 <xsl:for-each select="marc:datafield[@tag=765]|marc:datafield[@tag=767]|marc:datafield[@tag=777]|marc:datafield[@tag=787]">
14249                         <relatedItem>
14250                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14251                         </relatedItem>
14252                 </xsl:for-each>
14253                 <xsl:for-each select="marc:datafield[@tag=775]">
14254                         <relatedItem type="otherVersion">
14255                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14256                         </relatedItem>
14257                 </xsl:for-each>
14258                 <xsl:for-each select="marc:datafield[@tag=770]|marc:datafield[@tag=774]">
14259                         <relatedItem type="constituent">
14260                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14261                         </relatedItem>
14262                 </xsl:for-each>
14263                 <xsl:for-each select="marc:datafield[@tag=772]|marc:datafield[@tag=773]">
14264                         <relatedItem type="host">
14265                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14266                         </relatedItem>
14267                 </xsl:for-each>
14268                 <xsl:for-each select="marc:datafield[@tag=776]">
14269                         <relatedItem type="otherFormat">
14270                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14271                         </relatedItem>
14272                 </xsl:for-each>
14273                 <xsl:for-each select="marc:datafield[@tag=780]">
14274                         <relatedItem type="preceding">
14275                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14276                         </relatedItem>
14277                 </xsl:for-each>
14278                 <xsl:for-each select="marc:datafield[@tag=785]">
14279                         <relatedItem type="succeeding">
14280                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14281                         </relatedItem>
14282                 </xsl:for-each>
14283                 <xsl:for-each select="marc:datafield[@tag=786]">
14284                         <relatedItem type="original">
14285                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
14286                         </relatedItem>
14287                 </xsl:for-each>
14288                 <xsl:for-each select="marc:datafield[@tag=800]">
14289                         <relatedItem type="series">
14290                                 <titleInfo>
14291                                         <title>
14292                                                 <xsl:call-template name="chopPunctuation">
14293                                                         <xsl:with-param name="chopString">
14294                                                                 <xsl:call-template name="specialSubfieldSelect">
14295                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14296                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14297                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14298                                                                 </xsl:call-template>
14299                                                         </xsl:with-param>
14300                                                 </xsl:call-template>
14301                                         </title>
14302                                         <xsl:call-template name="part"></xsl:call-template>
14303                                 </titleInfo>
14304                                 <name type="personal">
14305                                         <namePart>
14306                                                 <xsl:call-template name="chopPunctuation">
14307                                                         <xsl:with-param name="chopString">
14308                                                                 <xsl:call-template name="specialSubfieldSelect">
14309                                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
14310                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14311                                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
14312                                                                 </xsl:call-template>
14313                                                         </xsl:with-param>
14314                                                 </xsl:call-template>
14315                                         </namePart>
14316                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
14317                                         <xsl:call-template name="nameDate"></xsl:call-template>
14318                                         <xsl:call-template name="role"></xsl:call-template>
14319                                 </name>
14320                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14321                         </relatedItem>
14322                 </xsl:for-each>
14323                 <xsl:for-each select="marc:datafield[@tag=810]">
14324                         <relatedItem type="series">
14325                                 <titleInfo>
14326                                         <title>
14327                                                 <xsl:call-template name="chopPunctuation">
14328                                                         <xsl:with-param name="chopString">
14329                                                                 <xsl:call-template name="specialSubfieldSelect">
14330                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
14331                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14332                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
14333                                                                 </xsl:call-template>
14334                                                         </xsl:with-param>
14335                                                 </xsl:call-template>
14336                                         </title>
14337                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14338                                 </titleInfo>
14339                                 <name type="corporate">
14340                                         <xsl:for-each select="marc:subfield[@code='a']">
14341                                                 <namePart>
14342                                                         <xsl:value-of select="."></xsl:value-of>
14343                                                 </namePart>
14344                                         </xsl:for-each>
14345                                         <xsl:for-each select="marc:subfield[@code='b']">
14346                                                 <namePart>
14347                                                         <xsl:value-of select="."></xsl:value-of>
14348                                                 </namePart>
14349                                         </xsl:for-each>
14350                                         <namePart>
14351                                                 <xsl:call-template name="specialSubfieldSelect">
14352                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
14353                                                         <xsl:with-param name="axis">t</xsl:with-param>
14354                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
14355                                                 </xsl:call-template>
14356                                         </namePart>
14357                                         <xsl:call-template name="role"></xsl:call-template>
14358                                 </name>
14359                                 <xsl:call-template name="relatedForm"></xsl:call-template>
14360                         </relatedItem>
14361                 </xsl:for-each>
14362                 <xsl:for-each select="marc:datafield[@tag=811]">
14363                         <relatedItem type="series">
14364                                 <titleInfo>
14365                                         <title>
14366                                                 <xsl:call-template name="chopPunctuation">
14367                                                         <xsl:with-param name="chopString">
14368                                                                 <xsl:call-template name="specialSubfieldSelect">
14369                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
14370                                                                         <xsl:with-param name="axis">t</xsl:with-param>
14371                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
14372                                                                 </xsl:call-template>
14373                                                         </xsl:with-param>
14374                                                 </xsl:call-template>
14375                                         </title>
14376                                         <xsl:call-template name="relatedPartNumName"/>
14377                                 </titleInfo>
14378                                 <name type="conference">
14379                                         <namePart>
14380                                                 <xsl:call-template name="specialSubfieldSelect">
14381                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
14382                                                         <xsl:with-param name="axis">t</xsl:with-param>
14383                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
14384                                                 </xsl:call-template>
14385                                         </namePart>
14386                                         <xsl:call-template name="role"/>
14387                                 </name>
14388                                 <xsl:call-template name="relatedForm"/>
14389                         </relatedItem>
14390                 </xsl:for-each>
14391                 <xsl:for-each select="marc:datafield[@tag='830']">
14392                         <relatedItem type="series">
14393                                 <titleInfo>
14394                                         <title>
14395                                                 <xsl:call-template name="chopPunctuation">
14396                                                         <xsl:with-param name="chopString">
14397                                                                 <xsl:call-template name="subfieldSelect">
14398                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
14399                                                                 </xsl:call-template>
14400                                                         </xsl:with-param>
14401                                                 </xsl:call-template>
14402                                         </title>
14403                                         <xsl:call-template name="part"/>
14404                                 </titleInfo>
14405                                 <xsl:call-template name="relatedForm"/>
14406                         </relatedItem>
14407                 </xsl:for-each>
14408                 <xsl:for-each select="marc:datafield[@tag='856'][@ind2='2']/marc:subfield[@code='q']">
14409                         <relatedItem>
14410                                 <internetMediaType>
14411                                         <xsl:value-of select="."/>
14412                                 </internetMediaType>
14413                         </relatedItem>
14414                 </xsl:for-each>
14415                 <xsl:for-each select="marc:datafield[@tag='020']">
14416                         <xsl:call-template name="isInvalid">
14417                                 <xsl:with-param name="type">isbn</xsl:with-param>
14418                         </xsl:call-template>
14419                         <xsl:if test="marc:subfield[@code='a']">
14420                                 <identifier type="isbn">
14421                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14422                                 </identifier>
14423                         </xsl:if>
14424                 </xsl:for-each>
14425                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='0']">
14426                         <xsl:call-template name="isInvalid">
14427                                 <xsl:with-param name="type">isrc</xsl:with-param>
14428                         </xsl:call-template>
14429                         <xsl:if test="marc:subfield[@code='a']">
14430                                 <identifier type="isrc">
14431                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14432                                 </identifier>
14433                         </xsl:if>
14434                 </xsl:for-each>
14435                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='2']">
14436                         <xsl:call-template name="isInvalid">
14437                                 <xsl:with-param name="type">ismn</xsl:with-param>
14438                         </xsl:call-template>
14439                         <xsl:if test="marc:subfield[@code='a']">
14440                                 <identifier type="ismn">
14441                                         <xsl:value-of select="marc:subfield[@code='a']"/>
14442                                 </identifier>
14443                         </xsl:if>
14444                 </xsl:for-each>
14445                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='4']">
14446                         <xsl:call-template name="isInvalid">
14447                                 <xsl:with-param name="type">sici</xsl:with-param>
14448                         </xsl:call-template>
14449                         <identifier type="sici">
14450                                 <xsl:call-template name="subfieldSelect">
14451                                         <xsl:with-param name="codes">ab</xsl:with-param>
14452                                 </xsl:call-template>
14453                         </identifier>
14454                 </xsl:for-each>
14455                 <xsl:for-each select="marc:datafield[@tag='022']">
14456                         <xsl:call-template name="isInvalid">
14457                                 <xsl:with-param name="type">issn</xsl:with-param>
14458                         </xsl:call-template>
14459                         <identifier type="issn">
14460                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14461                         </identifier>
14462                 </xsl:for-each>
14463                 <xsl:for-each select="marc:datafield[@tag='010']">
14464                         <xsl:call-template name="isInvalid">
14465                                 <xsl:with-param name="type">lccn</xsl:with-param>
14466                         </xsl:call-template>
14467                         <identifier type="lccn">
14468                                 <xsl:value-of select="normalize-space(marc:subfield[@code='a'])"/>
14469                         </identifier>
14470                 </xsl:for-each>
14471                 <xsl:for-each select="marc:datafield[@tag='028']">
14472                         <identifier>
14473                                 <xsl:attribute name="type">
14474                                         <xsl:choose>
14475                                                 <xsl:when test="@ind1='0'">issue number</xsl:when>
14476                                                 <xsl:when test="@ind1='1'">matrix number</xsl:when>
14477                                                 <xsl:when test="@ind1='2'">music plate</xsl:when>
14478                                                 <xsl:when test="@ind1='3'">music publisher</xsl:when>
14479                                                 <xsl:when test="@ind1='4'">videorecording identifier</xsl:when>
14480                                         </xsl:choose>
14481                                 </xsl:attribute>
14482                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 028 -->
14483                                 <xsl:call-template name="subfieldSelect">
14484                                         <xsl:with-param name="codes">
14485                                                 <xsl:choose>
14486                                                         <xsl:when test="@ind1='0'">ba</xsl:when>
14487                                                         <xsl:otherwise>ab</xsl:otherwise>
14488                                                 </xsl:choose>
14489                                         </xsl:with-param>
14490                                 </xsl:call-template>
14491                         </identifier>
14492                 </xsl:for-each>
14493                 <xsl:for-each select="marc:datafield[@tag='037']">
14494                         <identifier type="stock number">
14495                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 037 -->
14496                                 <xsl:call-template name="subfieldSelect">
14497                                         <xsl:with-param name="codes">ab</xsl:with-param>
14498                                 </xsl:call-template>
14499                         </identifier>
14500                 </xsl:for-each>
14501                 <xsl:for-each select="marc:datafield[@tag='856'][marc:subfield[@code='u']]">
14502                         <identifier>
14503                                 <xsl:attribute name="type">
14504                                         <xsl:choose>
14505                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:doi') or starts-with(marc:subfield[@code='u'],'doi')">doi</xsl:when>
14506                                                 <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>
14507                                                 <xsl:otherwise>uri</xsl:otherwise>
14508                                         </xsl:choose>
14509                                 </xsl:attribute>
14510                                 <xsl:choose>
14511                                         <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') ">
14512                                                 <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14513                                         </xsl:when>
14514                                         <xsl:otherwise>
14515                                                 <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14516                                         </xsl:otherwise>
14517                                 </xsl:choose>
14518                         </identifier>
14519                         <xsl:if test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl')">
14520                                 <identifier type="hdl">
14521                                         <xsl:if test="marc:subfield[@code='y' or @code='3' or @code='z']">
14522                                                 <xsl:attribute name="displayLabel">
14523                                                         <xsl:call-template name="subfieldSelect">
14524                                                                 <xsl:with-param name="codes">y3z</xsl:with-param>
14525                                                         </xsl:call-template>
14526                                                 </xsl:attribute>
14527                                         </xsl:if>
14528                                         <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
14529                                 </identifier>
14530                         </xsl:if>
14531                 </xsl:for-each>
14532                 <xsl:for-each select="marc:datafield[@tag=024][@ind1=1]">
14533                         <identifier type="upc">
14534                                 <xsl:call-template name="isInvalid"/>
14535                                 <xsl:value-of select="marc:subfield[@code='a']"/>
14536                         </identifier>
14537                 </xsl:for-each>
14538                 <!-- 1/04 fix added $y -->
14539                 <xsl:for-each select="marc:datafield[@tag=856][marc:subfield[@code='u']]">
14540                         <location>
14541                                 <url>
14542                                         <xsl:if test="marc:subfield[@code='y' or @code='3']">
14543                                                 <xsl:attribute name="displayLabel">
14544                                                         <xsl:call-template name="subfieldSelect">
14545                                                                 <xsl:with-param name="codes">y3</xsl:with-param>
14546                                                         </xsl:call-template>
14547                                                 </xsl:attribute>
14548                                         </xsl:if>
14549                                         <xsl:if test="marc:subfield[@code='z' ]">
14550                                                 <xsl:attribute name="note">
14551                                                         <xsl:call-template name="subfieldSelect">
14552                                                                 <xsl:with-param name="codes">z</xsl:with-param>
14553                                                         </xsl:call-template>
14554                                                 </xsl:attribute>
14555                                         </xsl:if>
14556                                         <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
14557
14558                                 </url>
14559                         </location>
14560                 </xsl:for-each>
14561                         
14562                         <!-- 3.2 change tmee 856z  -->
14563
14564                 
14565                 <xsl:for-each select="marc:datafield[@tag=852]">
14566                         <location>
14567                                 <physicalLocation>
14568                                         <xsl:call-template name="displayLabel"></xsl:call-template>
14569                                         <xsl:call-template name="subfieldSelect">
14570                                                 <xsl:with-param name="codes">abje</xsl:with-param>
14571                                         </xsl:call-template>
14572                                 </physicalLocation>
14573                         </location>
14574                 </xsl:for-each>
14575                 <xsl:for-each select="marc:datafield[@tag=506]">
14576                         <accessCondition type="restrictionOnAccess">
14577                                 <xsl:call-template name="subfieldSelect">
14578                                         <xsl:with-param name="codes">abcd35</xsl:with-param>
14579                                 </xsl:call-template>
14580                         </accessCondition>
14581                 </xsl:for-each>
14582                 <xsl:for-each select="marc:datafield[@tag=540]">
14583                         <accessCondition type="useAndReproduction">
14584                                 <xsl:call-template name="subfieldSelect">
14585                                         <xsl:with-param name="codes">abcde35</xsl:with-param>
14586                                 </xsl:call-template>
14587                         </accessCondition>
14588                 </xsl:for-each>
14589                 <recordInfo>
14590                         <xsl:for-each select="marc:datafield[@tag=040]">
14591                                 <recordContentSource authority="marcorg">
14592                                         <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
14593                                 </recordContentSource>
14594                         </xsl:for-each>
14595                         <xsl:for-each select="marc:controlfield[@tag=008]">
14596                                 <recordCreationDate encoding="marc">
14597                                         <xsl:value-of select="substring(.,1,6)"></xsl:value-of>
14598                                 </recordCreationDate>
14599                         </xsl:for-each>
14600                         <xsl:for-each select="marc:controlfield[@tag=005]">
14601                                 <recordChangeDate encoding="iso8601">
14602                                         <xsl:value-of select="."></xsl:value-of>
14603                                 </recordChangeDate>
14604                         </xsl:for-each>
14605                         <xsl:for-each select="marc:controlfield[@tag=001]">
14606                                 <recordIdentifier>
14607                                         <xsl:if test="../marc:controlfield[@tag=003]">
14608                                                 <xsl:attribute name="source">
14609                                                         <xsl:value-of select="../marc:controlfield[@tag=003]"></xsl:value-of>
14610                                                 </xsl:attribute>
14611                                         </xsl:if>
14612                                         <xsl:value-of select="."></xsl:value-of>
14613                                 </recordIdentifier>
14614                         </xsl:for-each>
14615                         <xsl:for-each select="marc:datafield[@tag=040]/marc:subfield[@code='b']">
14616                                 <languageOfCataloging>
14617                                         <languageTerm authority="iso639-2b" type="code">
14618                                                 <xsl:value-of select="."></xsl:value-of>
14619                                         </languageTerm>
14620                                 </languageOfCataloging>
14621                         </xsl:for-each>
14622                 </recordInfo>
14623         </xsl:template>
14624         <xsl:template name="displayForm">
14625                 <xsl:for-each select="marc:subfield[@code='c']">
14626                         <displayForm>
14627                                 <xsl:value-of select="."></xsl:value-of>
14628                         </displayForm>
14629                 </xsl:for-each>
14630         </xsl:template>
14631         <xsl:template name="affiliation">
14632                 <xsl:for-each select="marc:subfield[@code='u']">
14633                         <affiliation>
14634                                 <xsl:value-of select="."></xsl:value-of>
14635                         </affiliation>
14636                 </xsl:for-each>
14637         </xsl:template>
14638         <xsl:template name="uri">
14639                 <xsl:for-each select="marc:subfield[@code='u']">
14640                         <xsl:attribute name="xlink:href">
14641                                 <xsl:value-of select="."></xsl:value-of>
14642                         </xsl:attribute>
14643                 </xsl:for-each>
14644         </xsl:template>
14645         <xsl:template name="role">
14646                 <xsl:for-each select="marc:subfield[@code='e']">
14647                         <role>
14648                                 <roleTerm type="text">
14649                                         <xsl:value-of select="."></xsl:value-of>
14650                                 </roleTerm>
14651                         </role>
14652                 </xsl:for-each>
14653                 <xsl:for-each select="marc:subfield[@code='4']">
14654                         <role>
14655                                 <roleTerm authority="marcrelator" type="code">
14656                                         <xsl:value-of select="."></xsl:value-of>
14657                                 </roleTerm>
14658                         </role>
14659                 </xsl:for-each>
14660         </xsl:template>
14661         <xsl:template name="part">
14662                 <xsl:variable name="partNumber">
14663                         <xsl:call-template name="specialSubfieldSelect">
14664                                 <xsl:with-param name="axis">n</xsl:with-param>
14665                                 <xsl:with-param name="anyCodes">n</xsl:with-param>
14666                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14667                         </xsl:call-template>
14668                 </xsl:variable>
14669                 <xsl:variable name="partName">
14670                         <xsl:call-template name="specialSubfieldSelect">
14671                                 <xsl:with-param name="axis">p</xsl:with-param>
14672                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14673                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14674                         </xsl:call-template>
14675                 </xsl:variable>
14676                 <xsl:if test="string-length(normalize-space($partNumber))">
14677                         <partNumber>
14678                                 <xsl:call-template name="chopPunctuation">
14679                                         <xsl:with-param name="chopString" select="$partNumber"></xsl:with-param>
14680                                 </xsl:call-template>
14681                         </partNumber>
14682                 </xsl:if>
14683                 <xsl:if test="string-length(normalize-space($partName))">
14684                         <partName>
14685                                 <xsl:call-template name="chopPunctuation">
14686                                         <xsl:with-param name="chopString" select="$partName"></xsl:with-param>
14687                                 </xsl:call-template>
14688                         </partName>
14689                 </xsl:if>
14690         </xsl:template>
14691         <xsl:template name="relatedPart">
14692                 <xsl:if test="@tag=773">
14693                         <xsl:for-each select="marc:subfield[@code='g']">
14694                                 <part>
14695                                         <text>
14696                                                 <xsl:value-of select="."></xsl:value-of>
14697                                         </text>
14698                                 </part>
14699                         </xsl:for-each>
14700                         <xsl:for-each select="marc:subfield[@code='q']">
14701                                 <part>
14702                                         <xsl:call-template name="parsePart"></xsl:call-template>
14703                                 </part>
14704                         </xsl:for-each>
14705                 </xsl:if>
14706         </xsl:template>
14707         <xsl:template name="relatedPartNumName">
14708                 <xsl:variable name="partNumber">
14709                         <xsl:call-template name="specialSubfieldSelect">
14710                                 <xsl:with-param name="axis">g</xsl:with-param>
14711                                 <xsl:with-param name="anyCodes">g</xsl:with-param>
14712                                 <xsl:with-param name="afterCodes">pst</xsl:with-param>
14713                         </xsl:call-template>
14714                 </xsl:variable>
14715                 <xsl:variable name="partName">
14716                         <xsl:call-template name="specialSubfieldSelect">
14717                                 <xsl:with-param name="axis">p</xsl:with-param>
14718                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
14719                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
14720                         </xsl:call-template>
14721                 </xsl:variable>
14722                 <xsl:if test="string-length(normalize-space($partNumber))">
14723                         <partNumber>
14724                                 <xsl:value-of select="$partNumber"></xsl:value-of>
14725                         </partNumber>
14726                 </xsl:if>
14727                 <xsl:if test="string-length(normalize-space($partName))">
14728                         <partName>
14729                                 <xsl:value-of select="$partName"></xsl:value-of>
14730                         </partName>
14731                 </xsl:if>
14732         </xsl:template>
14733         <xsl:template name="relatedName">
14734                 <xsl:for-each select="marc:subfield[@code='a']">
14735                         <name>
14736                                 <namePart>
14737                                         <xsl:value-of select="."></xsl:value-of>
14738                                 </namePart>
14739                         </name>
14740                 </xsl:for-each>
14741         </xsl:template>
14742         <xsl:template name="relatedForm">
14743                 <xsl:for-each select="marc:subfield[@code='h']">
14744                         <physicalDescription>
14745                                 <form>
14746                                         <xsl:value-of select="."></xsl:value-of>
14747                                 </form>
14748                         </physicalDescription>
14749                 </xsl:for-each>
14750         </xsl:template>
14751         <xsl:template name="relatedExtent">
14752                 <xsl:for-each select="marc:subfield[@code='h']">
14753                         <physicalDescription>
14754                                 <extent>
14755                                         <xsl:value-of select="."></xsl:value-of>
14756                                 </extent>
14757                         </physicalDescription>
14758                 </xsl:for-each>
14759         </xsl:template>
14760         <xsl:template name="relatedNote">
14761                 <xsl:for-each select="marc:subfield[@code='n']">
14762                         <note>
14763                                 <xsl:value-of select="."></xsl:value-of>
14764                         </note>
14765                 </xsl:for-each>
14766         </xsl:template>
14767         <xsl:template name="relatedSubject">
14768                 <xsl:for-each select="marc:subfield[@code='j']">
14769                         <subject>
14770                                 <temporal encoding="iso8601">
14771                                         <xsl:call-template name="chopPunctuation">
14772                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14773                                         </xsl:call-template>
14774                                 </temporal>
14775                         </subject>
14776                 </xsl:for-each>
14777         </xsl:template>
14778         <xsl:template name="relatedIdentifierISSN">
14779                 <xsl:for-each select="marc:subfield[@code='x']">
14780                         <identifier type="issn">
14781                                 <xsl:value-of select="."></xsl:value-of>
14782                         </identifier>
14783                 </xsl:for-each>
14784         </xsl:template>
14785         <xsl:template name="relatedIdentifierLocal">
14786                 <xsl:for-each select="marc:subfield[@code='w']">
14787                         <identifier type="local">
14788                                 <xsl:value-of select="."></xsl:value-of>
14789                         </identifier>
14790                 </xsl:for-each>
14791         </xsl:template>
14792         <xsl:template name="relatedIdentifier">
14793                 <xsl:for-each select="marc:subfield[@code='o']">
14794                         <identifier>
14795                                 <xsl:value-of select="."></xsl:value-of>
14796                         </identifier>
14797                 </xsl:for-each>
14798         </xsl:template>
14799         <xsl:template name="relatedItem76X-78X">
14800                 <xsl:call-template name="displayLabel"></xsl:call-template>
14801                 <xsl:call-template name="relatedTitle76X-78X"></xsl:call-template>
14802                 <xsl:call-template name="relatedName"></xsl:call-template>
14803                 <xsl:call-template name="relatedOriginInfo"></xsl:call-template>
14804                 <xsl:call-template name="relatedLanguage"></xsl:call-template>
14805                 <xsl:call-template name="relatedExtent"></xsl:call-template>
14806                 <xsl:call-template name="relatedNote"></xsl:call-template>
14807                 <xsl:call-template name="relatedSubject"></xsl:call-template>
14808                 <xsl:call-template name="relatedIdentifier"></xsl:call-template>
14809                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
14810                 <xsl:call-template name="relatedIdentifierLocal"></xsl:call-template>
14811                 <xsl:call-template name="relatedPart"></xsl:call-template>
14812         </xsl:template>
14813         <xsl:template name="subjectGeographicZ">
14814                 <geographic>
14815                         <xsl:call-template name="chopPunctuation">
14816                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14817                         </xsl:call-template>
14818                 </geographic>
14819         </xsl:template>
14820         <xsl:template name="subjectTemporalY">
14821                 <temporal>
14822                         <xsl:call-template name="chopPunctuation">
14823                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14824                         </xsl:call-template>
14825                 </temporal>
14826         </xsl:template>
14827         <xsl:template name="subjectTopic">
14828                 <topic>
14829                         <xsl:call-template name="chopPunctuation">
14830                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14831                         </xsl:call-template>
14832                 </topic>
14833         </xsl:template> 
14834         <!-- 3.2 change tmee 6xx $v genre -->
14835         <xsl:template name="subjectGenre">
14836                 <genre>
14837                         <xsl:call-template name="chopPunctuation">
14838                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
14839                         </xsl:call-template>
14840                 </genre>
14841         </xsl:template>
14842         
14843         <xsl:template name="nameABCDN">
14844                 <xsl:for-each select="marc:subfield[@code='a']">
14845                         <namePart>
14846                                 <xsl:call-template name="chopPunctuation">
14847                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
14848                                 </xsl:call-template>
14849                         </namePart>
14850                 </xsl:for-each>
14851                 <xsl:for-each select="marc:subfield[@code='b']">
14852                         <namePart>
14853                                 <xsl:value-of select="."></xsl:value-of>
14854                         </namePart>
14855                 </xsl:for-each>
14856                 <xsl:if test="marc:subfield[@code='c'] or marc:subfield[@code='d'] or marc:subfield[@code='n']">
14857                         <namePart>
14858                                 <xsl:call-template name="subfieldSelect">
14859                                         <xsl:with-param name="codes">cdn</xsl:with-param>
14860                                 </xsl:call-template>
14861                         </namePart>
14862                 </xsl:if>
14863         </xsl:template>
14864         <xsl:template name="nameABCDQ">
14865                 <namePart>
14866                         <xsl:call-template name="chopPunctuation">
14867                                 <xsl:with-param name="chopString">
14868                                         <xsl:call-template name="subfieldSelect">
14869                                                 <xsl:with-param name="codes">aq</xsl:with-param>
14870                                         </xsl:call-template>
14871                                 </xsl:with-param>
14872                                 <xsl:with-param name="punctuation">
14873                                         <xsl:text>:,;/ </xsl:text>
14874                                 </xsl:with-param>
14875                         </xsl:call-template>
14876                 </namePart>
14877                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
14878                 <xsl:call-template name="nameDate"></xsl:call-template>
14879         </xsl:template>
14880         <xsl:template name="nameACDEQ">
14881                 <namePart>
14882                         <xsl:call-template name="subfieldSelect">
14883                                 <xsl:with-param name="codes">acdeq</xsl:with-param>
14884                         </xsl:call-template>
14885                 </namePart>
14886         </xsl:template>
14887         <xsl:template name="constituentOrRelatedType">
14888                 <xsl:if test="@ind2=2">
14889                         <xsl:attribute name="type">constituent</xsl:attribute>
14890                 </xsl:if>
14891         </xsl:template>
14892         <xsl:template name="relatedTitle">
14893                 <xsl:for-each select="marc:subfield[@code='t']">
14894                         <titleInfo>
14895                                 <title>
14896                                         <xsl:call-template name="chopPunctuation">
14897                                                 <xsl:with-param name="chopString">
14898                                                         <xsl:value-of select="."></xsl:value-of>
14899                                                 </xsl:with-param>
14900                                         </xsl:call-template>
14901                                 </title>
14902                         </titleInfo>
14903                 </xsl:for-each>
14904         </xsl:template>
14905         <xsl:template name="relatedTitle76X-78X">
14906                 <xsl:for-each select="marc:subfield[@code='t']">
14907                         <titleInfo>
14908                                 <title>
14909                                         <xsl:call-template name="chopPunctuation">
14910                                                 <xsl:with-param name="chopString">
14911                                                         <xsl:value-of select="."></xsl:value-of>
14912                                                 </xsl:with-param>
14913                                         </xsl:call-template>
14914                                 </title>
14915                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14916                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14917                                 </xsl:if>
14918                         </titleInfo>
14919                 </xsl:for-each>
14920                 <xsl:for-each select="marc:subfield[@code='p']">
14921                         <titleInfo type="abbreviated">
14922                                 <title>
14923                                         <xsl:call-template name="chopPunctuation">
14924                                                 <xsl:with-param name="chopString">
14925                                                         <xsl:value-of select="."></xsl:value-of>
14926                                                 </xsl:with-param>
14927                                         </xsl:call-template>
14928                                 </title>
14929                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14930                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14931                                 </xsl:if>
14932                         </titleInfo>
14933                 </xsl:for-each>
14934                 <xsl:for-each select="marc:subfield[@code='s']">
14935                         <titleInfo type="uniform">
14936                                 <title>
14937                                         <xsl:call-template name="chopPunctuation">
14938                                                 <xsl:with-param name="chopString">
14939                                                         <xsl:value-of select="."></xsl:value-of>
14940                                                 </xsl:with-param>
14941                                         </xsl:call-template>
14942                                 </title>
14943                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
14944                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
14945                                 </xsl:if>
14946                         </titleInfo>
14947                 </xsl:for-each>
14948         </xsl:template>
14949         <xsl:template name="relatedOriginInfo">
14950                 <xsl:if test="marc:subfield[@code='b' or @code='d'] or marc:subfield[@code='f']">
14951                         <originInfo>
14952                                 <xsl:if test="@tag=775">
14953                                         <xsl:for-each select="marc:subfield[@code='f']">
14954                                                 <place>
14955                                                         <placeTerm>
14956                                                                 <xsl:attribute name="type">code</xsl:attribute>
14957                                                                 <xsl:attribute name="authority">marcgac</xsl:attribute>
14958                                                                 <xsl:value-of select="."></xsl:value-of>
14959                                                         </placeTerm>
14960                                                 </place>
14961                                         </xsl:for-each>
14962                                 </xsl:if>
14963                                 <xsl:for-each select="marc:subfield[@code='d']">
14964                                         <publisher>
14965                                                 <xsl:value-of select="."></xsl:value-of>
14966                                         </publisher>
14967                                 </xsl:for-each>
14968                                 <xsl:for-each select="marc:subfield[@code='b']">
14969                                         <edition>
14970                                                 <xsl:value-of select="."></xsl:value-of>
14971                                         </edition>
14972                                 </xsl:for-each>
14973                         </originInfo>
14974                 </xsl:if>
14975         </xsl:template>
14976         <xsl:template name="relatedLanguage">
14977                 <xsl:for-each select="marc:subfield[@code='e']">
14978                         <xsl:call-template name="getLanguage">
14979                                 <xsl:with-param name="langString">
14980                                         <xsl:value-of select="."></xsl:value-of>
14981                                 </xsl:with-param>
14982                         </xsl:call-template>
14983                 </xsl:for-each>
14984         </xsl:template>
14985         <xsl:template name="nameDate">
14986                 <xsl:for-each select="marc:subfield[@code='d']">
14987                         <namePart type="date">
14988                                 <xsl:call-template name="chopPunctuation">
14989                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
14990                                 </xsl:call-template>
14991                         </namePart>
14992                 </xsl:for-each>
14993         </xsl:template>
14994         <xsl:template name="subjectAuthority">
14995                 <xsl:if test="@ind2!=4">
14996                         <xsl:if test="@ind2!=' '">
14997                                 <xsl:if test="@ind2!=8">
14998                                         <xsl:if test="@ind2!=9">
14999                                                 <xsl:attribute name="authority">
15000                                                         <xsl:choose>
15001                                                                 <xsl:when test="@ind2=0">lcsh</xsl:when>
15002                                                                 <xsl:when test="@ind2=1">lcshac</xsl:when>
15003                                                                 <xsl:when test="@ind2=2">mesh</xsl:when>
15004                                                                 <!-- 1/04 fix -->
15005                                                                 <xsl:when test="@ind2=3">nal</xsl:when>
15006                                                                 <xsl:when test="@ind2=5">csh</xsl:when>
15007                                                                 <xsl:when test="@ind2=6">rvm</xsl:when>
15008                                                                 <xsl:when test="@ind2=7">
15009                                                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
15010                                                                 </xsl:when>
15011                                                         </xsl:choose>
15012                                                 </xsl:attribute>
15013                                         </xsl:if>
15014                                 </xsl:if>
15015                         </xsl:if>
15016                 </xsl:if>
15017         </xsl:template>
15018         <xsl:template name="subjectAnyOrder">
15019                 <xsl:for-each select="marc:subfield[@code='v' or @code='x' or @code='y' or @code='z']">
15020                         <xsl:choose>
15021                                 <xsl:when test="@code='v'">
15022                                         <xsl:call-template name="subjectGenre"></xsl:call-template>
15023                                 </xsl:when>
15024                                 <xsl:when test="@code='x'">
15025                                         <xsl:call-template name="subjectTopic"></xsl:call-template>
15026                                 </xsl:when>
15027                                 <xsl:when test="@code='y'">
15028                                         <xsl:call-template name="subjectTemporalY"></xsl:call-template>
15029                                 </xsl:when>
15030                                 <xsl:when test="@code='z'">
15031                                         <xsl:call-template name="subjectGeographicZ"></xsl:call-template>
15032                                 </xsl:when>
15033                         </xsl:choose>
15034                 </xsl:for-each>
15035         </xsl:template>
15036         <xsl:template name="specialSubfieldSelect">
15037                 <xsl:param name="anyCodes"></xsl:param>
15038                 <xsl:param name="axis"></xsl:param>
15039                 <xsl:param name="beforeCodes"></xsl:param>
15040                 <xsl:param name="afterCodes"></xsl:param>
15041                 <xsl:variable name="str">
15042                         <xsl:for-each select="marc:subfield">
15043                                 <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])">
15044                                         <xsl:value-of select="text()"></xsl:value-of>
15045                                         <xsl:text> </xsl:text>
15046                                 </xsl:if>
15047                         </xsl:for-each>
15048                 </xsl:variable>
15049                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
15050         </xsl:template>
15051         
15052         <!-- 3.2 change tmee 6xx $v genre -->
15053         <xsl:template match="marc:datafield[@tag=600]">
15054                 <subject>
15055                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15056                         <name type="personal">
15057                                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
15058                                 <namePart>
15059                                         <xsl:call-template name="chopPunctuation">
15060                                                 <xsl:with-param name="chopString">
15061                                                         <xsl:call-template name="subfieldSelect">
15062                                                                 <xsl:with-param name="codes">aq</xsl:with-param>
15063                                                         </xsl:call-template>
15064                                                 </xsl:with-param>
15065                                         </xsl:call-template>
15066                                 </namePart>
15067                                 <xsl:call-template name="nameDate"></xsl:call-template>
15068                                 <xsl:call-template name="affiliation"></xsl:call-template>
15069                                 <xsl:call-template name="role"></xsl:call-template>
15070                         </name>
15071                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15072                 </subject>
15073         </xsl:template>
15074         <xsl:template match="marc:datafield[@tag=610]">
15075                 <subject>
15076                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15077                         <name type="corporate">
15078                                 <xsl:for-each select="marc:subfield[@code='a']">
15079                                         <namePart>
15080                                                 <xsl:value-of select="."></xsl:value-of>
15081                                         </namePart>
15082                                 </xsl:for-each>
15083                                 <xsl:for-each select="marc:subfield[@code='b']">
15084                                         <namePart>
15085                                                 <xsl:value-of select="."></xsl:value-of>
15086                                         </namePart>
15087                                 </xsl:for-each>
15088                                 <xsl:if test="marc:subfield[@code='c' or @code='d' or @code='n' or @code='p']">
15089                                         <namePart>
15090                                                 <xsl:call-template name="subfieldSelect">
15091                                                         <xsl:with-param name="codes">cdnp</xsl:with-param>
15092                                                 </xsl:call-template>
15093                                         </namePart>
15094                                 </xsl:if>
15095                                 <xsl:call-template name="role"></xsl:call-template>
15096                         </name>
15097                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15098                 </subject>
15099         </xsl:template>
15100         <xsl:template match="marc:datafield[@tag=611]">
15101                 <subject>
15102                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15103                         <name type="conference">
15104                                 <namePart>
15105                                         <xsl:call-template name="subfieldSelect">
15106                                                 <xsl:with-param name="codes">abcdeqnp</xsl:with-param>
15107                                         </xsl:call-template>
15108                                 </namePart>
15109                                 <xsl:for-each select="marc:subfield[@code='4']">
15110                                         <role>
15111                                                 <roleTerm authority="marcrelator" type="code">
15112                                                         <xsl:value-of select="."></xsl:value-of>
15113                                                 </roleTerm>
15114                                         </role>
15115                                 </xsl:for-each>
15116                         </name>
15117                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15118                 </subject>
15119         </xsl:template>
15120         <xsl:template match="marc:datafield[@tag=630]">
15121                 <subject>
15122                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15123                         <titleInfo>
15124                                 <title>
15125                                         <xsl:call-template name="chopPunctuation">
15126                                                 <xsl:with-param name="chopString">
15127                                                         <xsl:call-template name="subfieldSelect">
15128                                                                 <xsl:with-param name="codes">adfhklor</xsl:with-param>
15129                                                         </xsl:call-template>
15130                                                 </xsl:with-param>
15131                                         </xsl:call-template>
15132                                         <xsl:call-template name="part"></xsl:call-template>
15133                                 </title>
15134                         </titleInfo>
15135                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15136                 </subject>
15137         </xsl:template>
15138         <xsl:template match="marc:datafield[@tag=650]">
15139                 <subject>
15140                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15141                         <topic>
15142                                 <xsl:call-template name="chopPunctuation">
15143                                         <xsl:with-param name="chopString">
15144                                                 <xsl:call-template name="subfieldSelect">
15145                                                         <xsl:with-param name="codes">abcd</xsl:with-param>
15146                                                 </xsl:call-template>
15147                                         </xsl:with-param>
15148                                 </xsl:call-template>
15149                         </topic>
15150                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15151                 </subject>
15152         </xsl:template>
15153         <xsl:template match="marc:datafield[@tag=651]">
15154                 <subject>
15155                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
15156                         <xsl:for-each select="marc:subfield[@code='a']">
15157                                 <geographic>
15158                                         <xsl:call-template name="chopPunctuation">
15159                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
15160                                         </xsl:call-template>
15161                                 </geographic>
15162                         </xsl:for-each>
15163                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
15164                 </subject>
15165         </xsl:template>
15166         <xsl:template match="marc:datafield[@tag=653]">
15167                 <subject>
15168                         <xsl:for-each select="marc:subfield[@code='a']">
15169                                 <topic>
15170                                         <xsl:value-of select="."></xsl:value-of>
15171                                 </topic>
15172                         </xsl:for-each>
15173                 </subject>
15174         </xsl:template>
15175         <xsl:template match="marc:datafield[@tag=656]">
15176                 <subject>
15177                         <xsl:if test="marc:subfield[@code=2]">
15178                                 <xsl:attribute name="authority">
15179                                         <xsl:value-of select="marc:subfield[@code=2]"></xsl:value-of>
15180                                 </xsl:attribute>
15181                         </xsl:if>
15182                         <occupation>
15183                                 <xsl:call-template name="chopPunctuation">
15184                                         <xsl:with-param name="chopString">
15185                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
15186                                         </xsl:with-param>
15187                                 </xsl:call-template>
15188                         </occupation>
15189                 </subject>
15190         </xsl:template>
15191         <xsl:template name="termsOfAddress">
15192                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
15193                         <namePart type="termsOfAddress">
15194                                 <xsl:call-template name="chopPunctuation">
15195                                         <xsl:with-param name="chopString">
15196                                                 <xsl:call-template name="subfieldSelect">
15197                                                         <xsl:with-param name="codes">bc</xsl:with-param>
15198                                                 </xsl:call-template>
15199                                         </xsl:with-param>
15200                                 </xsl:call-template>
15201                         </namePart>
15202                 </xsl:if>
15203         </xsl:template>
15204         <xsl:template name="displayLabel">
15205                 <xsl:if test="marc:subfield[@code='i']">
15206                         <xsl:attribute name="displayLabel">
15207                                 <xsl:value-of select="marc:subfield[@code='i']"></xsl:value-of>
15208                         </xsl:attribute>
15209                 </xsl:if>
15210                 <xsl:if test="marc:subfield[@code='3']">
15211                         <xsl:attribute name="displayLabel">
15212                                 <xsl:value-of select="marc:subfield[@code='3']"></xsl:value-of>
15213                         </xsl:attribute>
15214                 </xsl:if>
15215         </xsl:template>
15216         <xsl:template name="isInvalid">
15217                 <xsl:param name="type"/>
15218                 <xsl:if test="marc:subfield[@code='z'] or marc:subfield[@code='y']">
15219                         <identifier>
15220                                 <xsl:attribute name="type">
15221                                         <xsl:value-of select="$type"/>
15222                                 </xsl:attribute>
15223                                 <xsl:attribute name="invalid">
15224                                         <xsl:text>yes</xsl:text>
15225                                 </xsl:attribute>
15226                                 <xsl:if test="marc:subfield[@code='z']">
15227                                         <xsl:value-of select="marc:subfield[@code='z']"/>
15228                                 </xsl:if>
15229                                 <xsl:if test="marc:subfield[@code='y']">
15230                                         <xsl:value-of select="marc:subfield[@code='y']"/>
15231                                 </xsl:if>
15232                         </identifier>
15233                 </xsl:if>
15234         </xsl:template>
15235         <xsl:template name="subtitle">
15236                 <xsl:if test="marc:subfield[@code='b']">
15237                         <subTitle>
15238                                 <xsl:call-template name="chopPunctuation">
15239                                         <xsl:with-param name="chopString">
15240                                                 <xsl:value-of select="marc:subfield[@code='b']"/>
15241                                                 <!--<xsl:call-template name="subfieldSelect">
15242                                                         <xsl:with-param name="codes">b</xsl:with-param>                                                                 
15243                                                 </xsl:call-template>-->
15244                                         </xsl:with-param>
15245                                 </xsl:call-template>
15246                         </subTitle>
15247                 </xsl:if>
15248         </xsl:template>
15249         <xsl:template name="script">
15250                 <xsl:param name="scriptCode"></xsl:param>
15251                 <xsl:attribute name="script">
15252                         <xsl:choose>
15253                                 <xsl:when test="$scriptCode='(3'">Arabic</xsl:when>
15254                                 <xsl:when test="$scriptCode='(B'">Latin</xsl:when>
15255                                 <xsl:when test="$scriptCode='$1'">Chinese, Japanese, Korean</xsl:when>
15256                                 <xsl:when test="$scriptCode='(N'">Cyrillic</xsl:when>
15257                                 <xsl:when test="$scriptCode='(2'">Hebrew</xsl:when>
15258                                 <xsl:when test="$scriptCode='(S'">Greek</xsl:when>
15259                         </xsl:choose>
15260                 </xsl:attribute>
15261         </xsl:template>
15262         <xsl:template name="parsePart">
15263                 <!-- assumes 773$q= 1:2:3<4
15264                      with up to 3 levels and one optional start page
15265                 -->
15266                 <xsl:variable name="level1">
15267                         <xsl:choose>
15268                                 <xsl:when test="contains(text(),':')">
15269                                         <!-- 1:2 -->
15270                                         <xsl:value-of select="substring-before(text(),':')"></xsl:value-of>
15271                                 </xsl:when>
15272                                 <xsl:when test="not(contains(text(),':'))">
15273                                         <!-- 1 or 1<3 -->
15274                                         <xsl:if test="contains(text(),'&lt;')">
15275                                                 <!-- 1<3 -->
15276                                                 <xsl:value-of select="substring-before(text(),'&lt;')"></xsl:value-of>
15277                                         </xsl:if>
15278                                         <xsl:if test="not(contains(text(),'&lt;'))">
15279                                                 <!-- 1 -->
15280                                                 <xsl:value-of select="text()"></xsl:value-of>
15281                                         </xsl:if>
15282                                 </xsl:when>
15283                         </xsl:choose>
15284                 </xsl:variable>
15285                 <xsl:variable name="sici2">
15286                         <xsl:choose>
15287                                 <xsl:when test="starts-with(substring-after(text(),$level1),':')">
15288                                         <xsl:value-of select="substring(substring-after(text(),$level1),2)"></xsl:value-of>
15289                                 </xsl:when>
15290                                 <xsl:otherwise>
15291                                         <xsl:value-of select="substring-after(text(),$level1)"></xsl:value-of>
15292                                 </xsl:otherwise>
15293                         </xsl:choose>
15294                 </xsl:variable>
15295                 <xsl:variable name="level2">
15296                         <xsl:choose>
15297                                 <xsl:when test="contains($sici2,':')">
15298                                         <!--  2:3<4  -->
15299                                         <xsl:value-of select="substring-before($sici2,':')"></xsl:value-of>
15300                                 </xsl:when>
15301                                 <xsl:when test="contains($sici2,'&lt;')">
15302                                         <!-- 1: 2<4 -->
15303                                         <xsl:value-of select="substring-before($sici2,'&lt;')"></xsl:value-of>
15304                                 </xsl:when>
15305                                 <xsl:otherwise>
15306                                         <xsl:value-of select="$sici2"></xsl:value-of>
15307                                         <!-- 1:2 -->
15308                                 </xsl:otherwise>
15309                         </xsl:choose>
15310                 </xsl:variable>
15311                 <xsl:variable name="sici3">
15312                         <xsl:choose>
15313                                 <xsl:when test="starts-with(substring-after($sici2,$level2),':')">
15314                                         <xsl:value-of select="substring(substring-after($sici2,$level2),2)"></xsl:value-of>
15315                                 </xsl:when>
15316                                 <xsl:otherwise>
15317                                         <xsl:value-of select="substring-after($sici2,$level2)"></xsl:value-of>
15318                                 </xsl:otherwise>
15319                         </xsl:choose>
15320                 </xsl:variable>
15321                 <xsl:variable name="level3">
15322                         <xsl:choose>
15323                                 <xsl:when test="contains($sici3,'&lt;')">
15324                                         <!-- 2<4 -->
15325                                         <xsl:value-of select="substring-before($sici3,'&lt;')"></xsl:value-of>
15326                                 </xsl:when>
15327                                 <xsl:otherwise>
15328                                         <xsl:value-of select="$sici3"></xsl:value-of>
15329                                         <!-- 3 -->
15330                                 </xsl:otherwise>
15331                         </xsl:choose>
15332                 </xsl:variable>
15333                 <xsl:variable name="page">
15334                         <xsl:if test="contains(text(),'&lt;')">
15335                                 <xsl:value-of select="substring-after(text(),'&lt;')"></xsl:value-of>
15336                         </xsl:if>
15337                 </xsl:variable>
15338                 <xsl:if test="$level1">
15339                         <detail level="1">
15340                                 <number>
15341                                         <xsl:value-of select="$level1"></xsl:value-of>
15342                                 </number>
15343                         </detail>
15344                 </xsl:if>
15345                 <xsl:if test="$level2">
15346                         <detail level="2">
15347                                 <number>
15348                                         <xsl:value-of select="$level2"></xsl:value-of>
15349                                 </number>
15350                         </detail>
15351                 </xsl:if>
15352                 <xsl:if test="$level3">
15353                         <detail level="3">
15354                                 <number>
15355                                         <xsl:value-of select="$level3"></xsl:value-of>
15356                                 </number>
15357                         </detail>
15358                 </xsl:if>
15359                 <xsl:if test="$page">
15360                         <extent unit="page">
15361                                 <start>
15362                                         <xsl:value-of select="$page"></xsl:value-of>
15363                                 </start>
15364                         </extent>
15365                 </xsl:if>
15366         </xsl:template>
15367         <xsl:template name="getLanguage">
15368                 <xsl:param name="langString"></xsl:param>
15369                 <xsl:param name="controlField008-35-37"></xsl:param>
15370                 <xsl:variable name="length" select="string-length($langString)"></xsl:variable>
15371                 <xsl:choose>
15372                         <xsl:when test="$length=0"></xsl:when>
15373                         <xsl:when test="$controlField008-35-37=substring($langString,1,3)">
15374                                 <xsl:call-template name="getLanguage">
15375                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15376                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15377                                 </xsl:call-template>
15378                         </xsl:when>
15379                         <xsl:otherwise>
15380                                 <language>
15381                                         <languageTerm authority="iso639-2b" type="code">
15382                                                 <xsl:value-of select="substring($langString,1,3)"></xsl:value-of>
15383                                         </languageTerm>
15384                                 </language>
15385                                 <xsl:call-template name="getLanguage">
15386                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
15387                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
15388                                 </xsl:call-template>
15389                         </xsl:otherwise>
15390                 </xsl:choose>
15391         </xsl:template>
15392         <xsl:template name="isoLanguage">
15393                 <xsl:param name="currentLanguage"></xsl:param>
15394                 <xsl:param name="usedLanguages"></xsl:param>
15395                 <xsl:param name="remainingLanguages"></xsl:param>
15396                 <xsl:choose>
15397                         <xsl:when test="string-length($currentLanguage)=0"></xsl:when>
15398                         <xsl:when test="not(contains($usedLanguages, $currentLanguage))">
15399                                 <language>
15400                                         <xsl:if test="@code!='a'">
15401                                                 <xsl:attribute name="objectPart">
15402                                                         <xsl:choose>
15403                                                                 <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15404                                                                 <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15405                                                                 <xsl:when test="@code='e'">libretto</xsl:when>
15406                                                                 <xsl:when test="@code='f'">table of contents</xsl:when>
15407                                                                 <xsl:when test="@code='g'">accompanying material</xsl:when>
15408                                                                 <xsl:when test="@code='h'">translation</xsl:when>
15409                                                         </xsl:choose>
15410                                                 </xsl:attribute>
15411                                         </xsl:if>
15412                                         <languageTerm authority="iso639-2b" type="code">
15413                                                 <xsl:value-of select="$currentLanguage"></xsl:value-of>
15414                                         </languageTerm>
15415                                 </language>
15416                                 <xsl:call-template name="isoLanguage">
15417                                         <xsl:with-param name="currentLanguage">
15418                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15419                                         </xsl:with-param>
15420                                         <xsl:with-param name="usedLanguages">
15421                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15422                                         </xsl:with-param>
15423                                         <xsl:with-param name="remainingLanguages">
15424                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15425                                         </xsl:with-param>
15426                                 </xsl:call-template>
15427                         </xsl:when>
15428                         <xsl:otherwise>
15429                                 <xsl:call-template name="isoLanguage">
15430                                         <xsl:with-param name="currentLanguage">
15431                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
15432                                         </xsl:with-param>
15433                                         <xsl:with-param name="usedLanguages">
15434                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
15435                                         </xsl:with-param>
15436                                         <xsl:with-param name="remainingLanguages">
15437                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
15438                                         </xsl:with-param>
15439                                 </xsl:call-template>
15440                         </xsl:otherwise>
15441                 </xsl:choose>
15442         </xsl:template>
15443         <xsl:template name="chopBrackets">
15444                 <xsl:param name="chopString"></xsl:param>
15445                 <xsl:variable name="string">
15446                         <xsl:call-template name="chopPunctuation">
15447                                 <xsl:with-param name="chopString" select="$chopString"></xsl:with-param>
15448                         </xsl:call-template>
15449                 </xsl:variable>
15450                 <xsl:if test="substring($string, 1,1)='['">
15451                         <xsl:value-of select="substring($string,2, string-length($string)-2)"></xsl:value-of>
15452                 </xsl:if>
15453                 <xsl:if test="substring($string, 1,1)!='['">
15454                         <xsl:value-of select="$string"></xsl:value-of>
15455                 </xsl:if>
15456         </xsl:template>
15457         <xsl:template name="rfcLanguages">
15458                 <xsl:param name="nodeNum"></xsl:param>
15459                 <xsl:param name="usedLanguages"></xsl:param>
15460                 <xsl:param name="controlField008-35-37"></xsl:param>
15461                 <xsl:variable name="currentLanguage" select="."></xsl:variable>
15462                 <xsl:choose>
15463                         <xsl:when test="not($currentLanguage)"></xsl:when>
15464                         <xsl:when test="$currentLanguage!=$controlField008-35-37 and $currentLanguage!='rfc3066'">
15465                                 <xsl:if test="not(contains($usedLanguages,$currentLanguage))">
15466                                         <language>
15467                                                 <xsl:if test="@code!='a'">
15468                                                         <xsl:attribute name="objectPart">
15469                                                                 <xsl:choose>
15470                                                                         <xsl:when test="@code='b'">summary or subtitle</xsl:when>
15471                                                                         <xsl:when test="@code='d'">sung or spoken text</xsl:when>
15472                                                                         <xsl:when test="@code='e'">libretto</xsl:when>
15473                                                                         <xsl:when test="@code='f'">table of contents</xsl:when>
15474                                                                         <xsl:when test="@code='g'">accompanying material</xsl:when>
15475                                                                         <xsl:when test="@code='h'">translation</xsl:when>
15476                                                                 </xsl:choose>
15477                                                         </xsl:attribute>
15478                                                 </xsl:if>
15479                                                 <languageTerm authority="rfc3066" type="code">
15480                                                         <xsl:value-of select="$currentLanguage"/>
15481                                                 </languageTerm>
15482                                         </language>
15483                                 </xsl:if>
15484                         </xsl:when>
15485                         <xsl:otherwise>
15486                         </xsl:otherwise>
15487                 </xsl:choose>
15488         </xsl:template>
15489         <xsl:template name="datafield">
15490                 <xsl:param name="tag"/>
15491                 <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param>
15492                 <xsl:param name="ind2"><xsl:text> </xsl:text></xsl:param>
15493                 <xsl:param name="subfields"/>
15494                 <xsl:element name="marc:datafield">
15495                         <xsl:attribute name="tag">
15496                                 <xsl:value-of select="$tag"/>
15497                         </xsl:attribute>
15498                         <xsl:attribute name="ind1">
15499                                 <xsl:value-of select="$ind1"/>
15500                         </xsl:attribute>
15501                         <xsl:attribute name="ind2">
15502                                 <xsl:value-of select="$ind2"/>
15503                         </xsl:attribute>
15504                         <xsl:copy-of select="$subfields"/>
15505                 </xsl:element>
15506         </xsl:template>
15507
15508         <xsl:template name="subfieldSelect">
15509                 <xsl:param name="codes"/>
15510                 <xsl:param name="delimeter"><xsl:text> </xsl:text></xsl:param>
15511                 <xsl:variable name="str">
15512                         <xsl:for-each select="marc:subfield">
15513                                 <xsl:if test="contains($codes, @code)">
15514                                         <xsl:value-of select="text()"/><xsl:value-of select="$delimeter"/>
15515                                 </xsl:if>
15516                         </xsl:for-each>
15517                 </xsl:variable>
15518                 <xsl:value-of select="substring($str,1,string-length($str)-string-length($delimeter))"/>
15519         </xsl:template>
15520
15521         <xsl:template name="buildSpaces">
15522                 <xsl:param name="spaces"/>
15523                 <xsl:param name="char"><xsl:text> </xsl:text></xsl:param>
15524                 <xsl:if test="$spaces>0">
15525                         <xsl:value-of select="$char"/>
15526                         <xsl:call-template name="buildSpaces">
15527                                 <xsl:with-param name="spaces" select="$spaces - 1"/>
15528                                 <xsl:with-param name="char" select="$char"/>
15529                         </xsl:call-template>
15530                 </xsl:if>
15531         </xsl:template>
15532
15533         <xsl:template name="chopPunctuation">
15534                 <xsl:param name="chopString"/>
15535                 <xsl:param name="punctuation"><xsl:text>.:,;/ </xsl:text></xsl:param>
15536                 <xsl:variable name="length" select="string-length($chopString)"/>
15537                 <xsl:choose>
15538                         <xsl:when test="$length=0"/>
15539                         <xsl:when test="contains($punctuation, substring($chopString,$length,1))">
15540                                 <xsl:call-template name="chopPunctuation">
15541                                         <xsl:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
15542                                         <xsl:with-param name="punctuation" select="$punctuation"/>
15543                                 </xsl:call-template>
15544                         </xsl:when>
15545                         <xsl:when test="not($chopString)"/>
15546                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15547                 </xsl:choose>
15548         </xsl:template>
15549
15550         <xsl:template name="chopPunctuationFront">
15551                 <xsl:param name="chopString"/>
15552                 <xsl:variable name="length" select="string-length($chopString)"/>
15553                 <xsl:choose>
15554                         <xsl:when test="$length=0"/>
15555                         <xsl:when test="contains('.:,;/[ ', substring($chopString,1,1))">
15556                                 <xsl:call-template name="chopPunctuationFront">
15557                                         <xsl:with-param name="chopString" select="substring($chopString,2,$length - 1)"/>
15558                                 </xsl:call-template>
15559                         </xsl:when>
15560                         <xsl:when test="not($chopString)"/>
15561                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
15562                 </xsl:choose>
15563         </xsl:template>
15564 </xsl:stylesheet>$$ WHERE name = 'mods32';
15565
15566 -- Currently, the only difference from naco_normalize is that search_normalize
15567 -- turns apostrophes into spaces, while naco_normalize collapses them.
15568 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
15569
15570     use strict;
15571     use Unicode::Normalize;
15572     use Encode;
15573
15574     my $str = decode_utf8(shift);
15575     my $sf = shift;
15576
15577     # Apply NACO normalization to input string; based on
15578     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
15579     #
15580     # Note that unlike a strict reading of the NACO normalization rules,
15581     # output is returned as lowercase instead of uppercase for compatibility
15582     # with previous versions of the Evergreen naco_normalize routine.
15583
15584     # Convert to upper-case first; even though final output will be lowercase, doing this will
15585     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
15586     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
15587     $str = uc $str;
15588
15589     # remove non-filing strings
15590     $str =~ s/\x{0098}.*?\x{009C}//g;
15591
15592     $str = NFKD($str);
15593
15594     # additional substitutions - 3.6.
15595     $str =~ s/\x{00C6}/AE/g;
15596     $str =~ s/\x{00DE}/TH/g;
15597     $str =~ s/\x{0152}/OE/g;
15598     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;
15599
15600     # transformations based on Unicode category codes
15601     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
15602
15603         if ($sf && $sf =~ /^a/o) {
15604                 my $commapos = index($str, ',');
15605                 if ($commapos > -1) {
15606                         if ($commapos != length($str) - 1) {
15607                 $str =~ s/,/\x07/; # preserve first comma
15608                         }
15609                 }
15610         }
15611
15612     # since we've stripped out the control characters, we can now
15613     # use a few as placeholders temporarily
15614     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
15615     $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;
15616     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
15617
15618     # decimal digits
15619     $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/;
15620
15621     # intentionally skipping step 8 of the NACO algorithm; if the string
15622     # gets normalized away, that's fine.
15623
15624     # leading and trailing spaces
15625     $str =~ s/\s+/ /g;
15626     $str =~ s/^\s+//;
15627     $str =~ s/\s+$//g;
15628
15629     return lc $str;
15630 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
15631
15632 CREATE OR REPLACE FUNCTION public.search_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
15633         SELECT public.search_normalize($1,'a');
15634 $func$ LANGUAGE SQL STRICT IMMUTABLE;
15635
15636 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT ) RETURNS TEXT AS $func$
15637         SELECT public.search_normalize($1,'');
15638 $func$ LANGUAGE 'sql' STRICT IMMUTABLE;
15639
15640 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15641         'Search Normalize',
15642         'Apply search normalization rules to the extracted text. A less extreme version of NACO normalization.',
15643         'search_normalize',
15644         0
15645 );
15646
15647 UPDATE config.metabib_field_index_norm_map
15648     SET norm = (
15649         SELECT id FROM config.index_normalizer WHERE func = 'search_normalize'
15650     )
15651     WHERE norm = (
15652         SELECT id FROM config.index_normalizer WHERE func = 'naco_normalize'
15653     )
15654 ;
15655
15656
15657 -- This could take a long time if you have a very non-English bib database
15658 -- Run it outside of a transaction to avoid lock escalation
15659 SELECT metabib.reingest_metabib_field_entries(record)
15660     FROM metabib.full_rec
15661     WHERE tag = '245'
15662     AND subfield = 'a'
15663     AND value LIKE '%''%'
15664 ;
15665
15666 COMMIT;
15667
15668 -- This is split out because it takes forever to run on large bib collections.
15669 \qecho ************************************************************************
15670 \qecho The following transaction, wrapping upgrades 0679 and 0680, may take a
15671 \qecho *really* long time, and you might be able to run it by itself in
15672 \qecho parallel with other operations using a separate sesion.
15673 \qecho ************************************************************************
15674
15675 BEGIN;
15676 SELECT evergreen.upgrade_deps_block_check('0679', :eg_version);
15677
15678 -- Address typo in column name
15679 ALTER TABLE config.metabib_class ADD COLUMN buoyant BOOL DEFAULT FALSE NOT NULL;
15680 UPDATE config.metabib_class SET buoyant = bouyant;
15681 ALTER TABLE config.metabib_class DROP COLUMN bouyant;
15682
15683 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
15684 DECLARE
15685     normalizer      RECORD;
15686     value           TEXT := '';
15687 BEGIN
15688
15689     value := NEW.value;
15690
15691     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15692         FOR normalizer IN
15693             SELECT  n.func AS func,
15694                     n.param_count AS param_count,
15695                     m.params AS params
15696               FROM  config.index_normalizer n
15697                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15698               WHERE field = NEW.field AND m.pos < 0
15699               ORDER BY m.pos LOOP
15700                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15701                     quote_literal( value ) ||
15702                     CASE
15703                         WHEN normalizer.param_count > 0
15704                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15705                             ELSE ''
15706                         END ||
15707                     ')' INTO value;
15708
15709         END LOOP;
15710
15711         NEW.value := value;
15712     END IF;
15713
15714     IF NEW.index_vector = ''::tsvector THEN
15715         RETURN NEW;
15716     END IF;
15717
15718     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
15719         FOR normalizer IN
15720             SELECT  n.func AS func,
15721                     n.param_count AS param_count,
15722                     m.params AS params
15723               FROM  config.index_normalizer n
15724                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
15725               WHERE field = NEW.field AND m.pos >= 0
15726               ORDER BY m.pos LOOP
15727                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
15728                     quote_literal( value ) ||
15729                     CASE
15730                         WHEN normalizer.param_count > 0
15731                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
15732                             ELSE ''
15733                         END ||
15734                     ')' INTO value;
15735
15736         END LOOP;
15737     END IF;
15738
15739     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
15740         value :=  ARRAY_TO_STRING(
15741             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15742         );
15743         value := public.search_normalize(value);
15744     END IF;
15745
15746     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
15747
15748     RETURN NEW;
15749 END;
15750 $$ LANGUAGE PLPGSQL;
15751
15752 -- Given a string such as a user might type into a search box, prepare
15753 -- two changed variants for TO_TSQUERY(). See
15754 -- http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
15755 -- The first variant is normalized to match indexed documents regardless
15756 -- of diacritics.  The second variant keeps its diacritics for proper
15757 -- highlighting via TS_HEADLINE().
15758 CREATE OR REPLACE
15759     FUNCTION metabib.autosuggest_prepare_tsquery(orig TEXT) RETURNS TEXT[] AS
15760 $$
15761 DECLARE
15762     orig_ended_in_space     BOOLEAN;
15763     result                  RECORD;
15764     plain                   TEXT;
15765     normalized              TEXT;
15766 BEGIN
15767     orig_ended_in_space := orig ~ E'\\s$';
15768
15769     orig := ARRAY_TO_STRING(
15770         evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
15771     );
15772
15773     normalized := public.search_normalize(orig); -- also trim()s
15774     plain := trim(orig);
15775
15776     IF NOT orig_ended_in_space THEN
15777         plain := plain || ':*';
15778         normalized := normalized || ':*';
15779     END IF;
15780
15781     plain := ARRAY_TO_STRING(
15782         evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
15783     );
15784     normalized := ARRAY_TO_STRING(
15785         evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
15786     );
15787
15788     RETURN ARRAY[normalized, plain];
15789 END;
15790 $$ LANGUAGE PLPGSQL;
15791
15792
15793 -- Definition of OUT parameters changes, so must drop first
15794 DROP FUNCTION IF EXISTS metabib.suggest_browse_entries (TEXT, TEXT, TEXT, INTEGER, INTEGER, INTEGER);
15795
15796 CREATE OR REPLACE
15797     FUNCTION metabib.suggest_browse_entries(
15798         raw_query_text  TEXT,   -- actually typed by humans at the UI level
15799         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
15800         headline_opts   TEXT,   -- markup options for ts_headline()
15801         visibility_org  INTEGER,-- null if you don't want opac visibility test
15802         query_limit     INTEGER,-- use in LIMIT clause of interal query
15803         normalization   INTEGER -- argument to TS_RANK_CD()
15804     ) RETURNS TABLE (
15805         value                   TEXT,   -- plain
15806         field                   INTEGER,
15807         buoyant_and_class_match BOOL,
15808         field_match             BOOL,
15809         field_weight            INTEGER,
15810         rank                    REAL,
15811         buoyant                 BOOL,
15812         match                   TEXT    -- marked up
15813     ) AS $func$
15814 DECLARE
15815     prepared_query_texts    TEXT[];
15816     query                   TSQUERY;
15817     plain_query             TSQUERY;
15818     opac_visibility_join    TEXT;
15819     search_class_join       TEXT;
15820     r_fields                RECORD;
15821 BEGIN
15822     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
15823
15824     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
15825     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
15826
15827     IF visibility_org IS NOT NULL THEN
15828         opac_visibility_join := '
15829     JOIN asset.opac_visible_copies aovc ON (
15830         aovc.record = mbedm.source AND
15831         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
15832     )';
15833     ELSE
15834         opac_visibility_join := '';
15835     END IF;
15836
15837     -- The following determines whether we only provide suggestsons matching
15838     -- the user's selected search_class, or whether we show other suggestions
15839     -- too. The reason for MIN() is that for search_classes like
15840     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
15841     -- implication is that if title as a class doesn't have restrict,
15842     -- nor does the proper field, but the uniform field does, you're going
15843     -- to get 'false' for your overall evaluation of 'should we restrict?'
15844     -- To invert that, change from MIN() to MAX().
15845
15846     SELECT
15847         INTO r_fields
15848             MIN(cmc.restrict::INT) AS restrict_class,
15849             MIN(cmf.restrict::INT) AS restrict_field
15850         FROM metabib.search_class_to_registered_components(search_class)
15851             AS _registered (field_class TEXT, field INT)
15852         JOIN
15853             config.metabib_class cmc ON (cmc.name = _registered.field_class)
15854         LEFT JOIN
15855             config.metabib_field cmf ON (cmf.id = _registered.field);
15856
15857     -- evaluate 'should we restrict?'
15858     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
15859         search_class_join := '
15860     JOIN
15861         metabib.search_class_to_registered_components($2)
15862         AS _registered (field_class TEXT, field INT) ON (
15863             (_registered.field IS NULL AND
15864                 _registered.field_class = cmf.field_class) OR
15865             (_registered.field = cmf.id)
15866         )
15867     ';
15868     ELSE
15869         search_class_join := '
15870     LEFT JOIN
15871         metabib.search_class_to_registered_components($2)
15872         AS _registered (field_class TEXT, field INT) ON (
15873             _registered.field_class = cmc.name
15874         )
15875     ';
15876     END IF;
15877
15878     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $7, $3) FROM (SELECT DISTINCT
15879         mbe.value,
15880         cmf.id,
15881         cmc.buoyant AND _registered.field_class IS NOT NULL,
15882         _registered.field = cmf.id,
15883         cmf.weight,
15884         TS_RANK_CD(mbe.index_vector, $1, $6),
15885         cmc.buoyant
15886     FROM metabib.browse_entry_def_map mbedm
15887     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
15888     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
15889     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
15890     '  || search_class_join || opac_visibility_join ||
15891     ' WHERE $1 @@ mbe.index_vector
15892     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15893     LIMIT $5) x
15894     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
15895     '   -- sic, repeat the order by clause in the outer select too
15896     USING
15897         query, search_class, headline_opts,
15898         visibility_org, query_limit, normalization, plain_query
15899         ;
15900
15901     -- sort order:
15902     --  buoyant AND chosen class = match class
15903     --  chosen field = match field
15904     --  field weight
15905     --  rank
15906     --  buoyancy
15907     --  value itself
15908
15909 END;
15910 $func$ LANGUAGE PLPGSQL;
15911
15912
15913 \qecho 
15914 \qecho The following takes about a minute per 100,000 rows in
15915 \qecho metabib.browse_entry on my development system, which is only a VM with
15916 \qecho 4 GB of memory and 2 cores.
15917 \qecho 
15918 \qecho The following is a very loose estimate of how long the next UPDATE
15919 \qecho statement would take to finish on MY machine, based on YOUR number
15920 \qecho of rows in metabib.browse_entry:
15921 \qecho 
15922
15923 SELECT (COUNT(id) / 100000.0) * INTERVAL '1 minute'
15924     AS "approximate duration of following UPDATE statement"
15925     FROM metabib.browse_entry;
15926
15927 UPDATE metabib.browse_entry SET index_vector = TO_TSVECTOR(
15928     'keyword',
15929     public.search_normalize(
15930         ARRAY_TO_STRING(
15931             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
15932         )
15933     )
15934 );
15935
15936
15937 SELECT evergreen.upgrade_deps_block_check('0680', :eg_version);
15938
15939 -- Not much use in having identifier-class fields be suggestions. Credit for the idea goes to Ben Shum.
15940 UPDATE config.metabib_field SET browse_field = FALSE WHERE id < 100 AND field_class = 'identifier';
15941
15942
15943 ---------------------------------------------------------------------------
15944 -- The rest of this was tested on Evergreen Indiana's dev server, which has
15945 -- a large data set  of 2.6M bibs, and was instrumental in sussing out the
15946 -- needed adjustments.  Thanks, EG-IN!
15947 ---------------------------------------------------------------------------
15948
15949 -- GIN indexes are /much/ better for prefix matching, which is important for browse and autosuggest
15950 --Commented out the creation earlier, so we don't need to drop it here.
15951 --DROP INDEX metabib.metabib_browse_entry_index_vector_idx;
15952 CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIN (index_vector);
15953
15954
15955 -- We need thes to make the autosuggest limiting joins fast
15956 CREATE INDEX browse_entry_def_map_def_idx ON metabib.browse_entry_def_map (def);
15957 CREATE INDEX browse_entry_def_map_entry_idx ON metabib.browse_entry_def_map (entry);
15958 CREATE INDEX browse_entry_def_map_source_idx ON metabib.browse_entry_def_map (source);
15959
15960 -- In practice this will always be ~1 row, and the default of 1000 causes terrible plans
15961 ALTER FUNCTION metabib.search_class_to_registered_components(text) ROWS 1;
15962
15963 -- Reworking of the generated query to act in a sane manner in the face of large datasets
15964 CREATE OR REPLACE
15965     FUNCTION metabib.suggest_browse_entries(
15966         raw_query_text  TEXT,   -- actually typed by humans at the UI level
15967         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
15968         headline_opts   TEXT,   -- markup options for ts_headline()
15969         visibility_org  INTEGER,-- null if you don't want opac visibility test
15970         query_limit     INTEGER,-- use in LIMIT clause of interal query
15971         normalization   INTEGER -- argument to TS_RANK_CD()
15972     ) RETURNS TABLE (
15973         value                   TEXT,   -- plain
15974         field                   INTEGER,
15975         buoyant_and_class_match BOOL,
15976         field_match             BOOL,
15977         field_weight            INTEGER,
15978         rank                    REAL,
15979         buoyant                 BOOL,
15980         match                   TEXT    -- marked up
15981     ) AS $func$
15982 DECLARE
15983     prepared_query_texts    TEXT[];
15984     query                   TSQUERY;
15985     plain_query             TSQUERY;
15986     opac_visibility_join    TEXT;
15987     search_class_join       TEXT;
15988     r_fields                RECORD;
15989 BEGIN
15990     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
15991
15992     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
15993     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
15994
15995     IF visibility_org IS NOT NULL THEN
15996         opac_visibility_join := '
15997     JOIN asset.opac_visible_copies aovc ON (
15998         aovc.record = x.source AND
15999         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
16000     )';
16001     ELSE
16002         opac_visibility_join := '';
16003     END IF;
16004
16005     -- The following determines whether we only provide suggestsons matching
16006     -- the user's selected search_class, or whether we show other suggestions
16007     -- too. The reason for MIN() is that for search_classes like
16008     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
16009     -- implication is that if title as a class doesn't have restrict,
16010     -- nor does the proper field, but the uniform field does, you're going
16011     -- to get 'false' for your overall evaluation of 'should we restrict?'
16012     -- To invert that, change from MIN() to MAX().
16013
16014     SELECT
16015         INTO r_fields
16016             MIN(cmc.restrict::INT) AS restrict_class,
16017             MIN(cmf.restrict::INT) AS restrict_field
16018         FROM metabib.search_class_to_registered_components(search_class)
16019             AS _registered (field_class TEXT, field INT)
16020         JOIN
16021             config.metabib_class cmc ON (cmc.name = _registered.field_class)
16022         LEFT JOIN
16023             config.metabib_field cmf ON (cmf.id = _registered.field);
16024
16025     -- evaluate 'should we restrict?'
16026     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
16027         search_class_join := '
16028     JOIN
16029         metabib.search_class_to_registered_components($2)
16030         AS _registered (field_class TEXT, field INT) ON (
16031             (_registered.field IS NULL AND
16032                 _registered.field_class = cmf.field_class) OR
16033             (_registered.field = cmf.id)
16034         )
16035     ';
16036     ELSE
16037         search_class_join := '
16038     LEFT JOIN
16039         metabib.search_class_to_registered_components($2)
16040         AS _registered (field_class TEXT, field INT) ON (
16041             _registered.field_class = cmc.name
16042         )
16043     ';
16044     END IF;
16045
16046     RETURN QUERY EXECUTE '
16047 SELECT  DISTINCT
16048         x.value,
16049         x.id,
16050         x.push,
16051         x.restrict,
16052         x.weight,
16053         x.ts_rank_cd,
16054         x.buoyant,
16055         TS_HEADLINE(value, $7, $3)
16056   FROM  (SELECT DISTINCT
16057                 mbe.value,
16058                 cmf.id,
16059                 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
16060                 _registered.field = cmf.id AS restrict,
16061                 cmf.weight,
16062                 TS_RANK_CD(mbe.index_vector, $1, $6),
16063                 cmc.buoyant,
16064                 mbedm.source
16065           FROM  metabib.browse_entry_def_map mbedm
16066
16067                 -- Start with a pre-limited set of 10k possible suggestions. More than that is not going to be useful anyway
16068                 JOIN (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) mbe ON (mbe.id = mbedm.entry)
16069
16070                 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
16071                 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
16072                 '  || search_class_join || '
16073           ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16074           LIMIT 1000) AS x -- This outer limit makes testing for opac visibility usably fast
16075         ' || opac_visibility_join || '
16076   ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
16077   LIMIT $5
16078 '   -- sic, repeat the order by clause in the outer select too
16079     USING
16080         query, search_class, headline_opts,
16081         visibility_org, query_limit, normalization, plain_query
16082         ;
16083
16084     -- sort order:
16085     --  buoyant AND chosen class = match class
16086     --  chosen field = match field
16087     --  field weight
16088     --  rank
16089     --  buoyancy
16090     --  value itself
16091
16092 END;
16093 $func$ LANGUAGE PLPGSQL;
16094
16095 COMMIT;
16096
16097 -- This is split out because it was backported to 2.1, but may not exist before upgrades
16098 -- It can safely fail
16099 -- Also, lets say that. <_<
16100 \qecho
16101 \qecho *************************************************************************
16102 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16103 \qecho We are about to apply a patch that may not be needed. It can fail safely.
16104 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16105 \qecho *************************************************************************
16106 \qecho
16107
16108 -- Evergreen DB patch 0693.schema.do_not_despace_issns.sql
16109 --
16110 -- FIXME: insert description of change, if needed
16111 --
16112 BEGIN;
16113
16114
16115 -- check whether patch can be applied
16116 SELECT evergreen.upgrade_deps_block_check('0693', :eg_version);
16117
16118 -- FIXME: add/check SQL statements to perform the upgrade
16119 -- Delete the index normalizer that was meant to remove spaces from ISSNs
16120 -- but ended up breaking records with multiple ISSNs
16121 DELETE FROM config.metabib_field_index_norm_map WHERE id IN (
16122     SELECT map.id FROM config.metabib_field_index_norm_map map
16123         INNER JOIN config.metabib_field cmf ON cmf.id = map.field
16124         INNER JOIN config.index_normalizer cin ON cin.id = map.norm
16125     WHERE cin.func = 'replace'
16126         AND cmf.field_class = 'identifier'
16127         AND cmf.name = 'issn'
16128         AND map.params = $$[" ",""]$$
16129 );
16130
16131 -- Reindex records that have more than just a single ISSN
16132 -- to ensure that spaces are maintained
16133 SELECT metabib.reingest_metabib_field_entries(source)
16134   FROM metabib.identifier_field_entry mife
16135     INNER JOIN config.metabib_field cmf ON cmf.id = mife.field
16136   WHERE cmf.field_class = 'identifier'
16137     AND cmf.name = 'issn'
16138     AND char_length(value) > 9
16139 ;
16140
16141
16142 COMMIT;
16143