]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
Add missing upgrade scripts 0705, 0707 to point-to-point upgrade script
[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 applied_to;
10 DROP FUNCTION evergreen.upgrade_list_applied_deprecates(TEXT);
11 DROP FUNCTION 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 0672.fix-nonfiling-titles.sql
8388 --
8389 -- Titles that begin with non-filing articles using apostrophes
8390 -- (for example, "L'armée") get spaces injected between the article
8391 -- and the subsequent text, which then breaks searching for titles
8392 -- beginning with those articles.
8393 --
8394 -- This patch adds a nonfiling title element to MODS32 that can then
8395 -- be used to retrieve the title proper without affecting the spaces
8396 -- in the title. It's what we want, what we really really want, for
8397 -- title searches.
8398 --
8399
8400
8401 -- check whether patch can be applied
8402 SELECT evergreen.upgrade_deps_block_check('0672', :eg_version);
8403
8404 -- Update the XPath definition before the titleNonfiling element exists;
8405 -- but are you really going to read through the whole XSL below before
8406 -- seeing this important bit?
8407 UPDATE config.metabib_field
8408     SET xpath = $$//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]$$,
8409         format = 'mods32'
8410     WHERE field_class = 'title' AND name = 'proper';
8411
8412 UPDATE config.xml_transform SET xslt=$$<?xml version="1.0" encoding="UTF-8"?>
8413 <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">
8414         <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
8415 <!--
8416 Revision 1.14 - Fixed template isValid and fields 010, 020, 022, 024, 028, and 037 to output additional identifier elements 
8417   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
8418
8419 Revision 1.13 - Changed order of output under cartographics to reflect schema  2006/11/28 tmee
8420         
8421 Revision 1.12 - Updated to reflect MODS 3.2 Mapping  2006/10/11 tmee
8422                 
8423 Revision 1.11 - The attribute objectPart moved from <languageTerm> to <language>
8424       2006/04/08  jrad
8425
8426 Revision 1.10 MODS 3.1 revisions to language and classification elements  
8427                                 (plus ability to find marc:collection embedded in wrapper elements such as SRU zs: wrappers)
8428                                 2006/02/06  ggar
8429
8430 Revision 1.9 subfield $y was added to field 242 2004/09/02 10:57 jrad
8431
8432 Revision 1.8 Subject chopPunctuation expanded and attribute fixes 2004/08/12 jrad
8433
8434 Revision 1.7 2004/03/25 08:29 jrad
8435
8436 Revision 1.6 various validation fixes 2004/02/20 ntra
8437
8438 Revision 1.5  2003/10/02 16:18:58  ntra
8439 MODS2 to MODS3 updates, language unstacking and 
8440 de-duping, chopPunctuation expanded
8441
8442 Revision 1.3  2003/04/03 00:07:19  ntra
8443 Revision 1.3 Additional Changes not related to MODS Version 2.0 by ntra
8444
8445 Revision 1.2  2003/03/24 19:37:42  ckeith
8446 Added Log Comment
8447
8448 -->
8449         <xsl:template match="/">
8450                 <xsl:choose>
8451                         <xsl:when test="//marc:collection">
8452                                 <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">
8453                                         <xsl:for-each select="//marc:collection/marc:record">
8454                                                 <mods version="3.2">
8455                                                         <xsl:call-template name="marcRecord"/>
8456                                                 </mods>
8457                                         </xsl:for-each>
8458                                 </modsCollection>
8459                         </xsl:when>
8460                         <xsl:otherwise>
8461                                 <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">
8462                                         <xsl:for-each select="//marc:record">
8463                                                 <xsl:call-template name="marcRecord"/>
8464                                         </xsl:for-each>
8465                                 </mods>
8466                         </xsl:otherwise>
8467                 </xsl:choose>
8468         </xsl:template>
8469         <xsl:template name="marcRecord">
8470                 <xsl:variable name="leader" select="marc:leader"/>
8471                 <xsl:variable name="leader6" select="substring($leader,7,1)"/>
8472                 <xsl:variable name="leader7" select="substring($leader,8,1)"/>
8473                 <xsl:variable name="controlField008" select="marc:controlfield[@tag='008']"/>
8474                 <xsl:variable name="typeOf008">
8475                         <xsl:choose>
8476                                 <xsl:when test="$leader6='a'">
8477                                         <xsl:choose>
8478                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">BK</xsl:when>
8479                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">SE</xsl:when>
8480                                         </xsl:choose>
8481                                 </xsl:when>
8482                                 <xsl:when test="$leader6='t'">BK</xsl:when>
8483                                 <xsl:when test="$leader6='p'">MM</xsl:when>
8484                                 <xsl:when test="$leader6='m'">CF</xsl:when>
8485                                 <xsl:when test="$leader6='e' or $leader6='f'">MP</xsl:when>
8486                                 <xsl:when test="$leader6='g' or $leader6='k' or $leader6='o' or $leader6='r'">VM</xsl:when>
8487                                 <xsl:when test="$leader6='c' or $leader6='d' or $leader6='i' or $leader6='j'">MU</xsl:when>
8488                         </xsl:choose>
8489                 </xsl:variable>
8490                 <xsl:for-each select="marc:datafield[@tag='245']">
8491                         <titleInfo>
8492                                 <xsl:variable name="title">
8493                                         <xsl:choose>
8494                                                 <xsl:when test="marc:subfield[@code='b']">
8495                                                         <xsl:call-template name="specialSubfieldSelect">
8496                                                                 <xsl:with-param name="axis">b</xsl:with-param>
8497                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
8498                                                         </xsl:call-template>
8499                                                 </xsl:when>
8500                                                 <xsl:otherwise>
8501                                                         <xsl:call-template name="subfieldSelect">
8502                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
8503                                                         </xsl:call-template>
8504                                                 </xsl:otherwise>
8505                                         </xsl:choose>
8506                                 </xsl:variable>
8507                                 <xsl:variable name="titleChop">
8508                                         <xsl:call-template name="chopPunctuation">
8509                                                 <xsl:with-param name="chopString">
8510                                                         <xsl:value-of select="$title"/>
8511                                                 </xsl:with-param>
8512                                         </xsl:call-template>
8513                                 </xsl:variable>
8514                                 <xsl:choose>
8515                                         <xsl:when test="@ind2>0">
8516                                                 <nonSort>
8517                                                         <xsl:value-of select="substring($titleChop,1,@ind2)"/>
8518                                                 </nonSort>
8519                                                 <title>
8520                                                         <xsl:value-of select="substring($titleChop,@ind2+1)"/>
8521                                                 </title>
8522                                         </xsl:when>
8523                                         <xsl:otherwise>
8524                                                 <title>
8525                                                         <xsl:value-of select="$titleChop"/>
8526                                                 </title>
8527                                         </xsl:otherwise>
8528                                 </xsl:choose>
8529                                 <xsl:if test="marc:subfield[@code='b']">
8530                                         <subTitle>
8531                                                 <xsl:call-template name="chopPunctuation">
8532                                                         <xsl:with-param name="chopString">
8533                                                                 <xsl:call-template name="specialSubfieldSelect">
8534                                                                         <xsl:with-param name="axis">b</xsl:with-param>
8535                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
8536                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
8537                                                                 </xsl:call-template>
8538                                                         </xsl:with-param>
8539                                                 </xsl:call-template>
8540                                         </subTitle>
8541                                 </xsl:if>
8542                                 <xsl:call-template name="part"></xsl:call-template>
8543                         </titleInfo>
8544                         <!-- A form of title that ignores non-filing characters; useful
8545                                  for not converting "L'Oreal" into "L' Oreal" at index time -->
8546                         <titleNonfiling>
8547                                 <xsl:variable name="title">
8548                                         <xsl:choose>
8549                                                 <xsl:when test="marc:subfield[@code='b']">
8550                                                         <xsl:call-template name="specialSubfieldSelect">
8551                                                                 <xsl:with-param name="axis">b</xsl:with-param>
8552                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
8553                                                         </xsl:call-template>
8554                                                 </xsl:when>
8555                                                 <xsl:otherwise>
8556                                                         <xsl:call-template name="subfieldSelect">
8557                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
8558                                                         </xsl:call-template>
8559                                                 </xsl:otherwise>
8560                                         </xsl:choose>
8561                                 </xsl:variable>
8562                                 <title>
8563                                         <xsl:value-of select="$title"/>
8564                                 </title>
8565                                 <xsl:if test="marc:subfield[@code='b']">
8566                                         <subTitle>
8567                                                 <xsl:call-template name="chopPunctuation">
8568                                                         <xsl:with-param name="chopString">
8569                                                                 <xsl:call-template name="specialSubfieldSelect">
8570                                                                         <xsl:with-param name="axis">b</xsl:with-param>
8571                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
8572                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
8573                                                                 </xsl:call-template>
8574                                                         </xsl:with-param>
8575                                                 </xsl:call-template>
8576                                         </subTitle>
8577                                 </xsl:if>
8578                                 <xsl:call-template name="part"></xsl:call-template>
8579                         </titleNonfiling>
8580                 </xsl:for-each>
8581                 <xsl:for-each select="marc:datafield[@tag='210']">
8582                         <titleInfo type="abbreviated">
8583                                 <title>
8584                                         <xsl:call-template name="chopPunctuation">
8585                                                 <xsl:with-param name="chopString">
8586                                                         <xsl:call-template name="subfieldSelect">
8587                                                                 <xsl:with-param name="codes">a</xsl:with-param>
8588                                                         </xsl:call-template>
8589                                                 </xsl:with-param>
8590                                         </xsl:call-template>
8591                                 </title>
8592                                 <xsl:call-template name="subtitle"/>
8593                         </titleInfo>
8594                 </xsl:for-each>
8595                 <xsl:for-each select="marc:datafield[@tag='242']">
8596                         <titleInfo type="translated">
8597                                 <!--09/01/04 Added subfield $y-->
8598                                 <xsl:for-each select="marc:subfield[@code='y']">
8599                                         <xsl:attribute name="lang">
8600                                                 <xsl:value-of select="text()"/>
8601                                         </xsl:attribute>
8602                                 </xsl:for-each>
8603                                 <title>
8604                                         <xsl:call-template name="chopPunctuation">
8605                                                 <xsl:with-param name="chopString">
8606                                                         <xsl:call-template name="subfieldSelect">
8607                                                                 <!-- 1/04 removed $h, b -->
8608                                                                 <xsl:with-param name="codes">a</xsl:with-param>
8609                                                         </xsl:call-template>
8610                                                 </xsl:with-param>
8611                                         </xsl:call-template>
8612                                 </title>
8613                                 <!-- 1/04 fix -->
8614                                 <xsl:call-template name="subtitle"/>
8615                                 <xsl:call-template name="part"/>
8616                         </titleInfo>
8617                 </xsl:for-each>
8618                 <xsl:for-each select="marc:datafield[@tag='246']">
8619                         <titleInfo type="alternative">
8620                                 <xsl:for-each select="marc:subfield[@code='i']">
8621                                         <xsl:attribute name="displayLabel">
8622                                                 <xsl:value-of select="text()"/>
8623                                         </xsl:attribute>
8624                                 </xsl:for-each>
8625                                 <title>
8626                                         <xsl:call-template name="chopPunctuation">
8627                                                 <xsl:with-param name="chopString">
8628                                                         <xsl:call-template name="subfieldSelect">
8629                                                                 <!-- 1/04 removed $h, $b -->
8630                                                                 <xsl:with-param name="codes">af</xsl:with-param>
8631                                                         </xsl:call-template>
8632                                                 </xsl:with-param>
8633                                         </xsl:call-template>
8634                                 </title>
8635                                 <xsl:call-template name="subtitle"/>
8636                                 <xsl:call-template name="part"/>
8637                         </titleInfo>
8638                 </xsl:for-each>
8639                 <xsl:for-each select="marc:datafield[@tag='130']|marc:datafield[@tag='240']|marc:datafield[@tag='730'][@ind2!='2']">
8640                         <titleInfo type="uniform">
8641                                 <title>
8642                                         <xsl:variable name="str">
8643                                                 <xsl:for-each select="marc:subfield">
8644                                                         <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'])))">
8645                                                                 <xsl:value-of select="text()"/>
8646                                                                 <xsl:text> </xsl:text>
8647                                                         </xsl:if>
8648                                                 </xsl:for-each>
8649                                         </xsl:variable>
8650                                         <xsl:call-template name="chopPunctuation">
8651                                                 <xsl:with-param name="chopString">
8652                                                         <xsl:value-of select="substring($str,1,string-length($str)-1)"/>
8653                                                 </xsl:with-param>
8654                                         </xsl:call-template>
8655                                 </title>
8656                                 <xsl:call-template name="part"/>
8657                         </titleInfo>
8658                 </xsl:for-each>
8659                 <xsl:for-each select="marc:datafield[@tag='740'][@ind2!='2']">
8660                         <titleInfo type="alternative">
8661                                 <title>
8662                                         <xsl:call-template name="chopPunctuation">
8663                                                 <xsl:with-param name="chopString">
8664                                                         <xsl:call-template name="subfieldSelect">
8665                                                                 <xsl:with-param name="codes">ah</xsl:with-param>
8666                                                         </xsl:call-template>
8667                                                 </xsl:with-param>
8668                                         </xsl:call-template>
8669                                 </title>
8670                                 <xsl:call-template name="part"/>
8671                         </titleInfo>
8672                 </xsl:for-each>
8673                 <xsl:for-each select="marc:datafield[@tag='100']">
8674                         <name type="personal">
8675                                 <xsl:call-template name="nameABCDQ"/>
8676                                 <xsl:call-template name="affiliation"/>
8677                                 <role>
8678                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8679                                 </role>
8680                                 <xsl:call-template name="role"/>
8681                         </name>
8682                 </xsl:for-each>
8683                 <xsl:for-each select="marc:datafield[@tag='110']">
8684                         <name type="corporate">
8685                                 <xsl:call-template name="nameABCDN"/>
8686                                 <role>
8687                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8688                                 </role>
8689                                 <xsl:call-template name="role"/>
8690                         </name>
8691                 </xsl:for-each>
8692                 <xsl:for-each select="marc:datafield[@tag='111']">
8693                         <name type="conference">
8694                                 <xsl:call-template name="nameACDEQ"/>
8695                                 <role>
8696                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8697                                 </role>
8698                                 <xsl:call-template name="role"/>
8699                         </name>
8700                 </xsl:for-each>
8701                 <xsl:for-each select="marc:datafield[@tag='700'][not(marc:subfield[@code='t'])]">
8702                         <name type="personal">
8703                                 <xsl:call-template name="nameABCDQ"/>
8704                                 <xsl:call-template name="affiliation"/>
8705                                 <xsl:call-template name="role"/>
8706                         </name>
8707                 </xsl:for-each>
8708                 <xsl:for-each select="marc:datafield[@tag='710'][not(marc:subfield[@code='t'])]">
8709                         <name type="corporate">
8710                                 <xsl:call-template name="nameABCDN"/>
8711                                 <xsl:call-template name="role"/>
8712                         </name>
8713                 </xsl:for-each>
8714                 <xsl:for-each select="marc:datafield[@tag='711'][not(marc:subfield[@code='t'])]">
8715                         <name type="conference">
8716                                 <xsl:call-template name="nameACDEQ"/>
8717                                 <xsl:call-template name="role"/>
8718                         </name>
8719                 </xsl:for-each>
8720                 <xsl:for-each select="marc:datafield[@tag='720'][not(marc:subfield[@code='t'])]">
8721                         <name>
8722                                 <xsl:if test="@ind1=1">
8723                                         <xsl:attribute name="type">
8724                                                 <xsl:text>personal</xsl:text>
8725                                         </xsl:attribute>
8726                                 </xsl:if>
8727                                 <namePart>
8728                                         <xsl:value-of select="marc:subfield[@code='a']"/>
8729                                 </namePart>
8730                                 <xsl:call-template name="role"/>
8731                         </name>
8732                 </xsl:for-each>
8733                 <typeOfResource>
8734                         <xsl:if test="$leader7='c'">
8735                                 <xsl:attribute name="collection">yes</xsl:attribute>
8736                         </xsl:if>
8737                         <xsl:if test="$leader6='d' or $leader6='f' or $leader6='p' or $leader6='t'">
8738                                 <xsl:attribute name="manuscript">yes</xsl:attribute>
8739                         </xsl:if>
8740                         <xsl:choose>
8741                                 <xsl:when test="$leader6='a' or $leader6='t'">text</xsl:when>
8742                                 <xsl:when test="$leader6='e' or $leader6='f'">cartographic</xsl:when>
8743                                 <xsl:when test="$leader6='c' or $leader6='d'">notated music</xsl:when>
8744                                 <xsl:when test="$leader6='i'">sound recording-nonmusical</xsl:when>
8745                                 <xsl:when test="$leader6='j'">sound recording-musical</xsl:when>
8746                                 <xsl:when test="$leader6='k'">still image</xsl:when>
8747                                 <xsl:when test="$leader6='g'">moving image</xsl:when>
8748                                 <xsl:when test="$leader6='r'">three dimensional object</xsl:when>
8749                                 <xsl:when test="$leader6='m'">software, multimedia</xsl:when>
8750                                 <xsl:when test="$leader6='p'">mixed material</xsl:when>
8751                         </xsl:choose>
8752                 </typeOfResource>
8753                 <xsl:if test="substring($controlField008,26,1)='d'">
8754                         <genre authority="marc">globe</genre>
8755                 </xsl:if>
8756                 <xsl:if test="marc:controlfield[@tag='007'][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
8757                         <genre authority="marc">remote sensing image</genre>
8758                 </xsl:if>
8759                 <xsl:if test="$typeOf008='MP'">
8760                         <xsl:variable name="controlField008-25" select="substring($controlField008,26,1)"></xsl:variable>
8761                         <xsl:choose>
8762                                 <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']">
8763                                         <genre authority="marc">map</genre>
8764                                 </xsl:when>
8765                                 <xsl:when test="$controlField008-25='e' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
8766                                         <genre authority="marc">atlas</genre>
8767                                 </xsl:when>
8768                         </xsl:choose>
8769                 </xsl:if>
8770                 <xsl:if test="$typeOf008='SE'">
8771                         <xsl:variable name="controlField008-21" select="substring($controlField008,22,1)"></xsl:variable>
8772                         <xsl:choose>
8773                                 <xsl:when test="$controlField008-21='d'">
8774                                         <genre authority="marc">database</genre>
8775                                 </xsl:when>
8776                                 <xsl:when test="$controlField008-21='l'">
8777                                         <genre authority="marc">loose-leaf</genre>
8778                                 </xsl:when>
8779                                 <xsl:when test="$controlField008-21='m'">
8780                                         <genre authority="marc">series</genre>
8781                                 </xsl:when>
8782                                 <xsl:when test="$controlField008-21='n'">
8783                                         <genre authority="marc">newspaper</genre>
8784                                 </xsl:when>
8785                                 <xsl:when test="$controlField008-21='p'">
8786                                         <genre authority="marc">periodical</genre>
8787                                 </xsl:when>
8788                                 <xsl:when test="$controlField008-21='w'">
8789                                         <genre authority="marc">web site</genre>
8790                                 </xsl:when>
8791                         </xsl:choose>
8792                 </xsl:if>
8793                 <xsl:if test="$typeOf008='BK' or $typeOf008='SE'">
8794                         <xsl:variable name="controlField008-24" select="substring($controlField008,25,4)"></xsl:variable>
8795                         <xsl:choose>
8796                                 <xsl:when test="contains($controlField008-24,'a')">
8797                                         <genre authority="marc">abstract or summary</genre>
8798                                 </xsl:when>
8799                                 <xsl:when test="contains($controlField008-24,'b')">
8800                                         <genre authority="marc">bibliography</genre>
8801                                 </xsl:when>
8802                                 <xsl:when test="contains($controlField008-24,'c')">
8803                                         <genre authority="marc">catalog</genre>
8804                                 </xsl:when>
8805                                 <xsl:when test="contains($controlField008-24,'d')">
8806                                         <genre authority="marc">dictionary</genre>
8807                                 </xsl:when>
8808                                 <xsl:when test="contains($controlField008-24,'e')">
8809                                         <genre authority="marc">encyclopedia</genre>
8810                                 </xsl:when>
8811                                 <xsl:when test="contains($controlField008-24,'f')">
8812                                         <genre authority="marc">handbook</genre>
8813                                 </xsl:when>
8814                                 <xsl:when test="contains($controlField008-24,'g')">
8815                                         <genre authority="marc">legal article</genre>
8816                                 </xsl:when>
8817                                 <xsl:when test="contains($controlField008-24,'i')">
8818                                         <genre authority="marc">index</genre>
8819                                 </xsl:when>
8820                                 <xsl:when test="contains($controlField008-24,'k')">
8821                                         <genre authority="marc">discography</genre>
8822                                 </xsl:when>
8823                                 <xsl:when test="contains($controlField008-24,'l')">
8824                                         <genre authority="marc">legislation</genre>
8825                                 </xsl:when>
8826                                 <xsl:when test="contains($controlField008-24,'m')">
8827                                         <genre authority="marc">theses</genre>
8828                                 </xsl:when>
8829                                 <xsl:when test="contains($controlField008-24,'n')">
8830                                         <genre authority="marc">survey of literature</genre>
8831                                 </xsl:when>
8832                                 <xsl:when test="contains($controlField008-24,'o')">
8833                                         <genre authority="marc">review</genre>
8834                                 </xsl:when>
8835                                 <xsl:when test="contains($controlField008-24,'p')">
8836                                         <genre authority="marc">programmed text</genre>
8837                                 </xsl:when>
8838                                 <xsl:when test="contains($controlField008-24,'q')">
8839                                         <genre authority="marc">filmography</genre>
8840                                 </xsl:when>
8841                                 <xsl:when test="contains($controlField008-24,'r')">
8842                                         <genre authority="marc">directory</genre>
8843                                 </xsl:when>
8844                                 <xsl:when test="contains($controlField008-24,'s')">
8845                                         <genre authority="marc">statistics</genre>
8846                                 </xsl:when>
8847                                 <xsl:when test="contains($controlField008-24,'t')">
8848                                         <genre authority="marc">technical report</genre>
8849                                 </xsl:when>
8850                                 <xsl:when test="contains($controlField008-24,'v')">
8851                                         <genre authority="marc">legal case and case notes</genre>
8852                                 </xsl:when>
8853                                 <xsl:when test="contains($controlField008-24,'w')">
8854                                         <genre authority="marc">law report or digest</genre>
8855                                 </xsl:when>
8856                                 <xsl:when test="contains($controlField008-24,'z')">
8857                                         <genre authority="marc">treaty</genre>
8858                                 </xsl:when>
8859                         </xsl:choose>
8860                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
8861                         <xsl:choose>
8862                                 <xsl:when test="$controlField008-29='1'">
8863                                         <genre authority="marc">conference publication</genre>
8864                                 </xsl:when>
8865                         </xsl:choose>
8866                 </xsl:if>
8867                 <xsl:if test="$typeOf008='CF'">
8868                         <xsl:variable name="controlField008-26" select="substring($controlField008,27,1)"></xsl:variable>
8869                         <xsl:choose>
8870                                 <xsl:when test="$controlField008-26='a'">
8871                                         <genre authority="marc">numeric data</genre>
8872                                 </xsl:when>
8873                                 <xsl:when test="$controlField008-26='e'">
8874                                         <genre authority="marc">database</genre>
8875                                 </xsl:when>
8876                                 <xsl:when test="$controlField008-26='f'">
8877                                         <genre authority="marc">font</genre>
8878                                 </xsl:when>
8879                                 <xsl:when test="$controlField008-26='g'">
8880                                         <genre authority="marc">game</genre>
8881                                 </xsl:when>
8882                         </xsl:choose>
8883                 </xsl:if>
8884                 <xsl:if test="$typeOf008='BK'">
8885                         <xsl:if test="substring($controlField008,25,1)='j'">
8886                                 <genre authority="marc">patent</genre>
8887                         </xsl:if>
8888                         <xsl:if test="substring($controlField008,31,1)='1'">
8889                                 <genre authority="marc">festschrift</genre>
8890                         </xsl:if>
8891                         <xsl:variable name="controlField008-34" select="substring($controlField008,35,1)"></xsl:variable>
8892                         <xsl:if test="$controlField008-34='a' or $controlField008-34='b' or $controlField008-34='c' or $controlField008-34='d'">
8893                                 <genre authority="marc">biography</genre>
8894                         </xsl:if>
8895                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
8896                         <xsl:choose>
8897                                 <xsl:when test="$controlField008-33='e'">
8898                                         <genre authority="marc">essay</genre>
8899                                 </xsl:when>
8900                                 <xsl:when test="$controlField008-33='d'">
8901                                         <genre authority="marc">drama</genre>
8902                                 </xsl:when>
8903                                 <xsl:when test="$controlField008-33='c'">
8904                                         <genre authority="marc">comic strip</genre>
8905                                 </xsl:when>
8906                                 <xsl:when test="$controlField008-33='l'">
8907                                         <genre authority="marc">fiction</genre>
8908                                 </xsl:when>
8909                                 <xsl:when test="$controlField008-33='h'">
8910                                         <genre authority="marc">humor, satire</genre>
8911                                 </xsl:when>
8912                                 <xsl:when test="$controlField008-33='i'">
8913                                         <genre authority="marc">letter</genre>
8914                                 </xsl:when>
8915                                 <xsl:when test="$controlField008-33='f'">
8916                                         <genre authority="marc">novel</genre>
8917                                 </xsl:when>
8918                                 <xsl:when test="$controlField008-33='j'">
8919                                         <genre authority="marc">short story</genre>
8920                                 </xsl:when>
8921                                 <xsl:when test="$controlField008-33='s'">
8922                                         <genre authority="marc">speech</genre>
8923                                 </xsl:when>
8924                         </xsl:choose>
8925                 </xsl:if>
8926                 <xsl:if test="$typeOf008='MU'">
8927                         <xsl:variable name="controlField008-30-31" select="substring($controlField008,31,2)"></xsl:variable>
8928                         <xsl:if test="contains($controlField008-30-31,'b')">
8929                                 <genre authority="marc">biography</genre>
8930                         </xsl:if>
8931                         <xsl:if test="contains($controlField008-30-31,'c')">
8932                                 <genre authority="marc">conference publication</genre>
8933                         </xsl:if>
8934                         <xsl:if test="contains($controlField008-30-31,'d')">
8935                                 <genre authority="marc">drama</genre>
8936                         </xsl:if>
8937                         <xsl:if test="contains($controlField008-30-31,'e')">
8938                                 <genre authority="marc">essay</genre>
8939                         </xsl:if>
8940                         <xsl:if test="contains($controlField008-30-31,'f')">
8941                                 <genre authority="marc">fiction</genre>
8942                         </xsl:if>
8943                         <xsl:if test="contains($controlField008-30-31,'o')">
8944                                 <genre authority="marc">folktale</genre>
8945                         </xsl:if>
8946                         <xsl:if test="contains($controlField008-30-31,'h')">
8947                                 <genre authority="marc">history</genre>
8948                         </xsl:if>
8949                         <xsl:if test="contains($controlField008-30-31,'k')">
8950                                 <genre authority="marc">humor, satire</genre>
8951                         </xsl:if>
8952                         <xsl:if test="contains($controlField008-30-31,'m')">
8953                                 <genre authority="marc">memoir</genre>
8954                         </xsl:if>
8955                         <xsl:if test="contains($controlField008-30-31,'p')">
8956                                 <genre authority="marc">poetry</genre>
8957                         </xsl:if>
8958                         <xsl:if test="contains($controlField008-30-31,'r')">
8959                                 <genre authority="marc">rehearsal</genre>
8960                         </xsl:if>
8961                         <xsl:if test="contains($controlField008-30-31,'g')">
8962                                 <genre authority="marc">reporting</genre>
8963                         </xsl:if>
8964                         <xsl:if test="contains($controlField008-30-31,'s')">
8965                                 <genre authority="marc">sound</genre>
8966                         </xsl:if>
8967                         <xsl:if test="contains($controlField008-30-31,'l')">
8968                                 <genre authority="marc">speech</genre>
8969                         </xsl:if>
8970                 </xsl:if>
8971                 <xsl:if test="$typeOf008='VM'">
8972                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
8973                         <xsl:choose>
8974                                 <xsl:when test="$controlField008-33='a'">
8975                                         <genre authority="marc">art original</genre>
8976                                 </xsl:when>
8977                                 <xsl:when test="$controlField008-33='b'">
8978                                         <genre authority="marc">kit</genre>
8979                                 </xsl:when>
8980                                 <xsl:when test="$controlField008-33='c'">
8981                                         <genre authority="marc">art reproduction</genre>
8982                                 </xsl:when>
8983                                 <xsl:when test="$controlField008-33='d'">
8984                                         <genre authority="marc">diorama</genre>
8985                                 </xsl:when>
8986                                 <xsl:when test="$controlField008-33='f'">
8987                                         <genre authority="marc">filmstrip</genre>
8988                                 </xsl:when>
8989                                 <xsl:when test="$controlField008-33='g'">
8990                                         <genre authority="marc">legal article</genre>
8991                                 </xsl:when>
8992                                 <xsl:when test="$controlField008-33='i'">
8993                                         <genre authority="marc">picture</genre>
8994                                 </xsl:when>
8995                                 <xsl:when test="$controlField008-33='k'">
8996                                         <genre authority="marc">graphic</genre>
8997                                 </xsl:when>
8998                                 <xsl:when test="$controlField008-33='l'">
8999                                         <genre authority="marc">technical drawing</genre>
9000                                 </xsl:when>
9001                                 <xsl:when test="$controlField008-33='m'">
9002                                         <genre authority="marc">motion picture</genre>
9003                                 </xsl:when>
9004                                 <xsl:when test="$controlField008-33='n'">
9005                                         <genre authority="marc">chart</genre>
9006                                 </xsl:when>
9007                                 <xsl:when test="$controlField008-33='o'">
9008                                         <genre authority="marc">flash card</genre>
9009                                 </xsl:when>
9010                                 <xsl:when test="$controlField008-33='p'">
9011                                         <genre authority="marc">microscope slide</genre>
9012                                 </xsl:when>
9013                                 <xsl:when test="$controlField008-33='q' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
9014                                         <genre authority="marc">model</genre>
9015                                 </xsl:when>
9016                                 <xsl:when test="$controlField008-33='r'">
9017                                         <genre authority="marc">realia</genre>
9018                                 </xsl:when>
9019                                 <xsl:when test="$controlField008-33='s'">
9020                                         <genre authority="marc">slide</genre>
9021                                 </xsl:when>
9022                                 <xsl:when test="$controlField008-33='t'">
9023                                         <genre authority="marc">transparency</genre>
9024                                 </xsl:when>
9025                                 <xsl:when test="$controlField008-33='v'">
9026                                         <genre authority="marc">videorecording</genre>
9027                                 </xsl:when>
9028                                 <xsl:when test="$controlField008-33='w'">
9029                                         <genre authority="marc">toy</genre>
9030                                 </xsl:when>
9031                         </xsl:choose>
9032                 </xsl:if>
9033                 <xsl:for-each select="marc:datafield[@tag=655]">
9034                         <genre authority="marc">
9035                                 <xsl:attribute name="authority">
9036                                         <xsl:value-of select="marc:subfield[@code='2']"/>
9037                                 </xsl:attribute>
9038                                 <xsl:call-template name="subfieldSelect">
9039                                         <xsl:with-param name="codes">abvxyz</xsl:with-param>
9040                                         <xsl:with-param name="delimeter">-</xsl:with-param>
9041                                 </xsl:call-template>
9042                         </genre>
9043                 </xsl:for-each>
9044                 <originInfo>
9045                         <xsl:variable name="MARCpublicationCode" select="normalize-space(substring($controlField008,16,3))"></xsl:variable>
9046                         <xsl:if test="translate($MARCpublicationCode,'|','')">
9047                                 <place>
9048                                         <placeTerm>
9049                                                 <xsl:attribute name="type">code</xsl:attribute>
9050                                                 <xsl:attribute name="authority">marccountry</xsl:attribute>
9051                                                 <xsl:value-of select="$MARCpublicationCode"/>
9052                                         </placeTerm>
9053                                 </place>
9054                         </xsl:if>
9055                         <xsl:for-each select="marc:datafield[@tag=044]/marc:subfield[@code='c']">
9056                                 <place>
9057                                         <placeTerm>
9058                                                 <xsl:attribute name="type">code</xsl:attribute>
9059                                                 <xsl:attribute name="authority">iso3166</xsl:attribute>
9060                                                 <xsl:value-of select="."/>
9061                                         </placeTerm>
9062                                 </place>
9063                         </xsl:for-each>
9064                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='a']">
9065                                 <place>
9066                                         <placeTerm>
9067                                                 <xsl:attribute name="type">text</xsl:attribute>
9068                                                 <xsl:call-template name="chopPunctuationFront">
9069                                                         <xsl:with-param name="chopString">
9070                                                                 <xsl:call-template name="chopPunctuation">
9071                                                                         <xsl:with-param name="chopString" select="."/>
9072                                                                 </xsl:call-template>
9073                                                         </xsl:with-param>
9074                                                 </xsl:call-template>
9075                                         </placeTerm>
9076                                 </place>
9077                         </xsl:for-each>
9078                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='m']">
9079                                 <dateValid point="start">
9080                                         <xsl:value-of select="."/>
9081                                 </dateValid>
9082                         </xsl:for-each>
9083                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='n']">
9084                                 <dateValid point="end">
9085                                         <xsl:value-of select="."/>
9086                                 </dateValid>
9087                         </xsl:for-each>
9088                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='j']">
9089                                 <dateModified>
9090                                         <xsl:value-of select="."/>
9091                                 </dateModified>
9092                         </xsl:for-each>
9093                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='b' or @code='c' or @code='g']">
9094                                 <xsl:choose>
9095                                         <xsl:when test="@code='b'">
9096                                                 <publisher>
9097                                                         <xsl:call-template name="chopPunctuation">
9098                                                                 <xsl:with-param name="chopString" select="."/>
9099                                                                 <xsl:with-param name="punctuation">
9100                                                                         <xsl:text>:,;/ </xsl:text>
9101                                                                 </xsl:with-param>
9102                                                         </xsl:call-template>
9103                                                 </publisher>
9104                                         </xsl:when>
9105                                         <xsl:when test="@code='c'">
9106                                                 <dateIssued>
9107                                                         <xsl:call-template name="chopPunctuation">
9108                                                                 <xsl:with-param name="chopString" select="."/>
9109                                                         </xsl:call-template>
9110                                                 </dateIssued>
9111                                         </xsl:when>
9112                                         <xsl:when test="@code='g'">
9113                                                 <dateCreated>
9114                                                         <xsl:value-of select="."/>
9115                                                 </dateCreated>
9116                                         </xsl:when>
9117                                 </xsl:choose>
9118                         </xsl:for-each>
9119                         <xsl:variable name="dataField260c">
9120                                 <xsl:call-template name="chopPunctuation">
9121                                         <xsl:with-param name="chopString" select="marc:datafield[@tag=260]/marc:subfield[@code='c']"></xsl:with-param>
9122                                 </xsl:call-template>
9123                         </xsl:variable>
9124                         <xsl:variable name="controlField008-7-10" select="normalize-space(substring($controlField008, 8, 4))"></xsl:variable>
9125                         <xsl:variable name="controlField008-11-14" select="normalize-space(substring($controlField008, 12, 4))"></xsl:variable>
9126                         <xsl:variable name="controlField008-6" select="normalize-space(substring($controlField008, 7, 1))"></xsl:variable>
9127                         <xsl:if test="$controlField008-6='e' or $controlField008-6='p' or $controlField008-6='r' or $controlField008-6='t' or $controlField008-6='s'">
9128                                 <xsl:if test="$controlField008-7-10 and ($controlField008-7-10 != $dataField260c)">
9129                                         <dateIssued encoding="marc">
9130                                                 <xsl:value-of select="$controlField008-7-10"/>
9131                                         </dateIssued>
9132                                 </xsl:if>
9133                         </xsl:if>
9134                         <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'">
9135                                 <xsl:if test="$controlField008-7-10">
9136                                         <dateIssued encoding="marc" point="start">
9137                                                 <xsl:value-of select="$controlField008-7-10"/>
9138                                         </dateIssued>
9139                                 </xsl:if>
9140                         </xsl:if>
9141                         <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'">
9142                                 <xsl:if test="$controlField008-11-14">
9143                                         <dateIssued encoding="marc" point="end">
9144                                                 <xsl:value-of select="$controlField008-11-14"/>
9145                                         </dateIssued>
9146                                 </xsl:if>
9147                         </xsl:if>
9148                         <xsl:if test="$controlField008-6='q'">
9149                                 <xsl:if test="$controlField008-7-10">
9150                                         <dateIssued encoding="marc" point="start" qualifier="questionable">
9151                                                 <xsl:value-of select="$controlField008-7-10"/>
9152                                         </dateIssued>
9153                                 </xsl:if>
9154                         </xsl:if>
9155                         <xsl:if test="$controlField008-6='q'">
9156                                 <xsl:if test="$controlField008-11-14">
9157                                         <dateIssued encoding="marc" point="end" qualifier="questionable">
9158                                                 <xsl:value-of select="$controlField008-11-14"/>
9159                                         </dateIssued>
9160                                 </xsl:if>
9161                         </xsl:if>
9162                         <xsl:if test="$controlField008-6='t'">
9163                                 <xsl:if test="$controlField008-11-14">
9164                                         <copyrightDate encoding="marc">
9165                                                 <xsl:value-of select="$controlField008-11-14"/>
9166                                         </copyrightDate>
9167                                 </xsl:if>
9168                         </xsl:if>
9169                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=0 or @ind1=1]/marc:subfield[@code='a']">
9170                                 <dateCaptured encoding="iso8601">
9171                                         <xsl:value-of select="."/>
9172                                 </dateCaptured>
9173                         </xsl:for-each>
9174                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][1]">
9175                                 <dateCaptured encoding="iso8601" point="start">
9176                                         <xsl:value-of select="."/>
9177                                 </dateCaptured>
9178                         </xsl:for-each>
9179                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][2]">
9180                                 <dateCaptured encoding="iso8601" point="end">
9181                                         <xsl:value-of select="."/>
9182                                 </dateCaptured>
9183                         </xsl:for-each>
9184                         <xsl:for-each select="marc:datafield[@tag=250]/marc:subfield[@code='a']">
9185                                 <edition>
9186                                         <xsl:value-of select="."/>
9187                                 </edition>
9188                         </xsl:for-each>
9189                         <xsl:for-each select="marc:leader">
9190                                 <issuance>
9191                                         <xsl:choose>
9192                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">monographic</xsl:when>
9193                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">continuing</xsl:when>
9194                                         </xsl:choose>
9195                                 </issuance>
9196                         </xsl:for-each>
9197                         <xsl:for-each select="marc:datafield[@tag=310]|marc:datafield[@tag=321]">
9198                                 <frequency>
9199                                         <xsl:call-template name="subfieldSelect">
9200                                                 <xsl:with-param name="codes">ab</xsl:with-param>
9201                                         </xsl:call-template>
9202                                 </frequency>
9203                         </xsl:for-each>
9204                 </originInfo>
9205                 <xsl:variable name="controlField008-35-37" select="normalize-space(translate(substring($controlField008,36,3),'|#',''))"></xsl:variable>
9206                 <xsl:if test="$controlField008-35-37">
9207                         <language>
9208                                 <languageTerm authority="iso639-2b" type="code">
9209                                         <xsl:value-of select="substring($controlField008,36,3)"/>
9210                                 </languageTerm>
9211                         </language>
9212                 </xsl:if>
9213                 <xsl:for-each select="marc:datafield[@tag=041]">
9214                         <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']">
9215                                 <xsl:variable name="langCodes" select="."/>
9216                                 <xsl:choose>
9217                                         <xsl:when test="../marc:subfield[@code='2']='rfc3066'">
9218                                                 <!-- not stacked but could be repeated -->
9219                                                 <xsl:call-template name="rfcLanguages">
9220                                                         <xsl:with-param name="nodeNum">
9221                                                                 <xsl:value-of select="1"/>
9222                                                         </xsl:with-param>
9223                                                         <xsl:with-param name="usedLanguages">
9224                                                                 <xsl:text></xsl:text>
9225                                                         </xsl:with-param>
9226                                                         <xsl:with-param name="controlField008-35-37">
9227                                                                 <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
9228                                                         </xsl:with-param>
9229                                                 </xsl:call-template>
9230                                         </xsl:when>
9231                                         <xsl:otherwise>
9232                                                 <!-- iso -->
9233                                                 <xsl:variable name="allLanguages">
9234                                                         <xsl:copy-of select="$langCodes"></xsl:copy-of>
9235                                                 </xsl:variable>
9236                                                 <xsl:variable name="currentLanguage">
9237                                                         <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
9238                                                 </xsl:variable>
9239                                                 <xsl:call-template name="isoLanguage">
9240                                                         <xsl:with-param name="currentLanguage">
9241                                                                 <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
9242                                                         </xsl:with-param>
9243                                                         <xsl:with-param name="remainingLanguages">
9244                                                                 <xsl:value-of select="substring($allLanguages,4,string-length($allLanguages)-3)"></xsl:value-of>
9245                                                         </xsl:with-param>
9246                                                         <xsl:with-param name="usedLanguages">
9247                                                                 <xsl:if test="$controlField008-35-37">
9248                                                                         <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
9249                                                                 </xsl:if>
9250                                                         </xsl:with-param>
9251                                                 </xsl:call-template>
9252                                         </xsl:otherwise>
9253                                 </xsl:choose>
9254                         </xsl:for-each>
9255                 </xsl:for-each>
9256                 <xsl:variable name="physicalDescription">
9257                         <!--3.2 change tmee 007/11 -->
9258                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='a']">
9259                                 <digitalOrigin>reformatted digital</digitalOrigin>
9260                         </xsl:if>
9261                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='b']">
9262                                 <digitalOrigin>digitized microfilm</digitalOrigin>
9263                         </xsl:if>
9264                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='d']">
9265                                 <digitalOrigin>digitized other analog</digitalOrigin>
9266                         </xsl:if>
9267                         <xsl:variable name="controlField008-23" select="substring($controlField008,24,1)"></xsl:variable>
9268                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
9269                         <xsl:variable name="check008-23">
9270                                 <xsl:if test="$typeOf008='BK' or $typeOf008='MU' or $typeOf008='SE' or $typeOf008='MM'">
9271                                         <xsl:value-of select="true()"></xsl:value-of>
9272                                 </xsl:if>
9273                         </xsl:variable>
9274                         <xsl:variable name="check008-29">
9275                                 <xsl:if test="$typeOf008='MP' or $typeOf008='VM'">
9276                                         <xsl:value-of select="true()"></xsl:value-of>
9277                                 </xsl:if>
9278                         </xsl:variable>
9279                         <xsl:choose>
9280                                 <xsl:when test="($check008-23 and $controlField008-23='f') or ($check008-29 and $controlField008-29='f')">
9281                                         <form authority="marcform">braille</form>
9282                                 </xsl:when>
9283                                 <xsl:when test="($controlField008-23=' ' and ($leader6='c' or $leader6='d')) or (($typeOf008='BK' or $typeOf008='SE') and ($controlField008-23=' ' or $controlField008='r'))">
9284                                         <form authority="marcform">print</form>
9285                                 </xsl:when>
9286                                 <xsl:when test="$leader6 = 'm' or ($check008-23 and $controlField008-23='s') or ($check008-29 and $controlField008-29='s')">
9287                                         <form authority="marcform">electronic</form>
9288                                 </xsl:when>
9289                                 <xsl:when test="($check008-23 and $controlField008-23='b') or ($check008-29 and $controlField008-29='b')">
9290                                         <form authority="marcform">microfiche</form>
9291                                 </xsl:when>
9292                                 <xsl:when test="($check008-23 and $controlField008-23='a') or ($check008-29 and $controlField008-29='a')">
9293                                         <form authority="marcform">microfilm</form>
9294                                 </xsl:when>
9295                         </xsl:choose>
9296                         <!-- 1/04 fix -->
9297                         <xsl:if test="marc:datafield[@tag=130]/marc:subfield[@code='h']">
9298                                 <form authority="gmd">
9299                                         <xsl:call-template name="chopBrackets">
9300                                                 <xsl:with-param name="chopString">
9301                                                         <xsl:value-of select="marc:datafield[@tag=130]/marc:subfield[@code='h']"></xsl:value-of>
9302                                                 </xsl:with-param>
9303                                         </xsl:call-template>
9304                                 </form>
9305                         </xsl:if>
9306                         <xsl:if test="marc:datafield[@tag=240]/marc:subfield[@code='h']">
9307                                 <form authority="gmd">
9308                                         <xsl:call-template name="chopBrackets">
9309                                                 <xsl:with-param name="chopString">
9310                                                         <xsl:value-of select="marc:datafield[@tag=240]/marc:subfield[@code='h']"></xsl:value-of>
9311                                                 </xsl:with-param>
9312                                         </xsl:call-template>
9313                                 </form>
9314                         </xsl:if>
9315                         <xsl:if test="marc:datafield[@tag=242]/marc:subfield[@code='h']">
9316                                 <form authority="gmd">
9317                                         <xsl:call-template name="chopBrackets">
9318                                                 <xsl:with-param name="chopString">
9319                                                         <xsl:value-of select="marc:datafield[@tag=242]/marc:subfield[@code='h']"></xsl:value-of>
9320                                                 </xsl:with-param>
9321                                         </xsl:call-template>
9322                                 </form>
9323                         </xsl:if>
9324                         <xsl:if test="marc:datafield[@tag=245]/marc:subfield[@code='h']">
9325                                 <form authority="gmd">
9326                                         <xsl:call-template name="chopBrackets">
9327                                                 <xsl:with-param name="chopString">
9328                                                         <xsl:value-of select="marc:datafield[@tag=245]/marc:subfield[@code='h']"></xsl:value-of>
9329                                                 </xsl:with-param>
9330                                         </xsl:call-template>
9331                                 </form>
9332                         </xsl:if>
9333                         <xsl:if test="marc:datafield[@tag=246]/marc:subfield[@code='h']">
9334                                 <form authority="gmd">
9335                                         <xsl:call-template name="chopBrackets">
9336                                                 <xsl:with-param name="chopString">
9337                                                         <xsl:value-of select="marc:datafield[@tag=246]/marc:subfield[@code='h']"></xsl:value-of>
9338                                                 </xsl:with-param>
9339                                         </xsl:call-template>
9340                                 </form>
9341                         </xsl:if>
9342                         <xsl:if test="marc:datafield[@tag=730]/marc:subfield[@code='h']">
9343                                 <form authority="gmd">
9344                                         <xsl:call-template name="chopBrackets">
9345                                                 <xsl:with-param name="chopString">
9346                                                         <xsl:value-of select="marc:datafield[@tag=730]/marc:subfield[@code='h']"></xsl:value-of>
9347                                                 </xsl:with-param>
9348                                         </xsl:call-template>
9349                                 </form>
9350                         </xsl:if>
9351                         <xsl:for-each select="marc:datafield[@tag=256]/marc:subfield[@code='a']">
9352                                 <form>
9353                                         <xsl:value-of select="."></xsl:value-of>
9354                                 </form>
9355                         </xsl:for-each>
9356                         <xsl:for-each select="marc:controlfield[@tag=007][substring(text(),1,1)='c']">
9357                                 <xsl:choose>
9358                                         <xsl:when test="substring(text(),14,1)='a'">
9359                                                 <reformattingQuality>access</reformattingQuality>
9360                                         </xsl:when>
9361                                         <xsl:when test="substring(text(),14,1)='p'">
9362                                                 <reformattingQuality>preservation</reformattingQuality>
9363                                         </xsl:when>
9364                                         <xsl:when test="substring(text(),14,1)='r'">
9365                                                 <reformattingQuality>replacement</reformattingQuality>
9366                                         </xsl:when>
9367                                 </xsl:choose>
9368                         </xsl:for-each>
9369                         <!--3.2 change tmee 007/01 -->
9370                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='b']">
9371                                 <form authority="smd">chip cartridge</form>
9372                         </xsl:if>
9373                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='c']">
9374                                 <form authority="smd">computer optical disc cartridge</form>
9375                         </xsl:if>
9376                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='j']">
9377                                 <form authority="smd">magnetic disc</form>
9378                         </xsl:if>
9379                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='m']">
9380                                 <form authority="smd">magneto-optical disc</form>
9381                         </xsl:if>
9382                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='o']">
9383                                 <form authority="smd">optical disc</form>
9384                         </xsl:if>
9385                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='r']">
9386                                 <form authority="smd">remote</form>
9387                         </xsl:if>
9388                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='a']">
9389                                 <form authority="smd">tape cartridge</form>
9390                         </xsl:if>
9391                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='f']">
9392                                 <form authority="smd">tape cassette</form>
9393                         </xsl:if>
9394                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='h']">
9395                                 <form authority="smd">tape reel</form>
9396                         </xsl:if>
9397                         
9398                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='a']">
9399                                 <form authority="smd">celestial globe</form>
9400                         </xsl:if>
9401                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='e']">
9402                                 <form authority="smd">earth moon globe</form>
9403                         </xsl:if>
9404                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='b']">
9405                                 <form authority="smd">planetary or lunar globe</form>
9406                         </xsl:if>
9407                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='c']">
9408                                 <form authority="smd">terrestrial globe</form>
9409                         </xsl:if>
9410                         
9411                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='o'][substring(text(),2,1)='o']">
9412                                 <form authority="smd">kit</form>
9413                         </xsl:if>
9414                         
9415                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
9416                                 <form authority="smd">atlas</form>
9417                         </xsl:if>
9418                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='g']">
9419                                 <form authority="smd">diagram</form>
9420                         </xsl:if>
9421                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
9422                                 <form authority="smd">map</form>
9423                         </xsl:if>
9424                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
9425                                 <form authority="smd">model</form>
9426                         </xsl:if>
9427                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='k']">
9428                                 <form authority="smd">profile</form>
9429                         </xsl:if>
9430                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
9431                                 <form authority="smd">remote-sensing image</form>
9432                         </xsl:if>
9433                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='s']">
9434                                 <form authority="smd">section</form>
9435                         </xsl:if>
9436                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='y']">
9437                                 <form authority="smd">view</form>
9438                         </xsl:if>
9439                         
9440                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='a']">
9441                                 <form authority="smd">aperture card</form>
9442                         </xsl:if>
9443                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='e']">
9444                                 <form authority="smd">microfiche</form>
9445                         </xsl:if>
9446                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='f']">
9447                                 <form authority="smd">microfiche cassette</form>
9448                         </xsl:if>
9449                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='b']">
9450                                 <form authority="smd">microfilm cartridge</form>
9451                         </xsl:if>
9452                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='c']">
9453                                 <form authority="smd">microfilm cassette</form>
9454                         </xsl:if>
9455                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='d']">
9456                                 <form authority="smd">microfilm reel</form>
9457                         </xsl:if>
9458                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='g']">
9459                                 <form authority="smd">microopaque</form>
9460                         </xsl:if>
9461                         
9462                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='c']">
9463                                 <form authority="smd">film cartridge</form>
9464                         </xsl:if>
9465                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='f']">
9466                                 <form authority="smd">film cassette</form>
9467                         </xsl:if>
9468                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='r']">
9469                                 <form authority="smd">film reel</form>
9470                         </xsl:if>
9471                         
9472                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='n']">
9473                                 <form authority="smd">chart</form>
9474                         </xsl:if>
9475                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='c']">
9476                                 <form authority="smd">collage</form>
9477                         </xsl:if>
9478                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='d']">
9479                                 <form authority="smd">drawing</form>
9480                         </xsl:if>
9481                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='o']">
9482                                 <form authority="smd">flash card</form>
9483                         </xsl:if>
9484                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='e']">
9485                                 <form authority="smd">painting</form>
9486                         </xsl:if>
9487                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='f']">
9488                                 <form authority="smd">photomechanical print</form>
9489                         </xsl:if>
9490                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='g']">
9491                                 <form authority="smd">photonegative</form>
9492                         </xsl:if>
9493                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='h']">
9494                                 <form authority="smd">photoprint</form>
9495                         </xsl:if>
9496                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='i']">
9497                                 <form authority="smd">picture</form>
9498                         </xsl:if>
9499                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='j']">
9500                                 <form authority="smd">print</form>
9501                         </xsl:if>
9502                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='l']">
9503                                 <form authority="smd">technical drawing</form>
9504                         </xsl:if>
9505                         
9506                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='q'][substring(text(),2,1)='q']">
9507                                 <form authority="smd">notated music</form>
9508                         </xsl:if>
9509                         
9510                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='d']">
9511                                 <form authority="smd">filmslip</form>
9512                         </xsl:if>
9513                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='c']">
9514                                 <form authority="smd">filmstrip cartridge</form>
9515                         </xsl:if>
9516                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='o']">
9517                                 <form authority="smd">filmstrip roll</form>
9518                         </xsl:if>
9519                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='f']">
9520                                 <form authority="smd">other filmstrip type</form>
9521                         </xsl:if>
9522                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='s']">
9523                                 <form authority="smd">slide</form>
9524                         </xsl:if>
9525                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='t']">
9526                                 <form authority="smd">transparency</form>
9527                         </xsl:if>
9528                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='r'][substring(text(),2,1)='r']">
9529                                 <form authority="smd">remote-sensing image</form>
9530                         </xsl:if>
9531                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='e']">
9532                                 <form authority="smd">cylinder</form>
9533                         </xsl:if>
9534                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='q']">
9535                                 <form authority="smd">roll</form>
9536                         </xsl:if>
9537                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='g']">
9538                                 <form authority="smd">sound cartridge</form>
9539                         </xsl:if>
9540                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='s']">
9541                                 <form authority="smd">sound cassette</form>
9542                         </xsl:if>
9543                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='d']">
9544                                 <form authority="smd">sound disc</form>
9545                         </xsl:if>
9546                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='t']">
9547                                 <form authority="smd">sound-tape reel</form>
9548                         </xsl:if>
9549                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='i']">
9550                                 <form authority="smd">sound-track film</form>
9551                         </xsl:if>
9552                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='w']">
9553                                 <form authority="smd">wire recording</form>
9554                         </xsl:if>
9555                         
9556                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='c']">
9557                                 <form authority="smd">braille</form>
9558                         </xsl:if>
9559                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='b']">
9560                                 <form authority="smd">combination</form>
9561                         </xsl:if>
9562                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='a']">
9563                                 <form authority="smd">moon</form>
9564                         </xsl:if>
9565                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='d']">
9566                                 <form authority="smd">tactile, with no writing system</form>
9567                         </xsl:if>
9568                         
9569                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='c']">
9570                                 <form authority="smd">braille</form>
9571                         </xsl:if>
9572                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='b']">
9573                                 <form authority="smd">large print</form>
9574                         </xsl:if>
9575                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='a']">
9576                                 <form authority="smd">regular print</form>
9577                         </xsl:if>
9578                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='d']">
9579                                 <form authority="smd">text in looseleaf binder</form>
9580                         </xsl:if>
9581                         
9582                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='c']">
9583                                 <form authority="smd">videocartridge</form>
9584                         </xsl:if>
9585                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='f']">
9586                                 <form authority="smd">videocassette</form>
9587                         </xsl:if>
9588                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='d']">
9589                                 <form authority="smd">videodisc</form>
9590                         </xsl:if>
9591                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='r']">
9592                                 <form authority="smd">videoreel</form>
9593                         </xsl:if>
9594                         
9595                         <xsl:for-each select="marc:datafield[@tag=856]/marc:subfield[@code='q'][string-length(.)>1]">
9596                                 <internetMediaType>
9597                                         <xsl:value-of select="."></xsl:value-of>
9598                                 </internetMediaType>
9599                         </xsl:for-each>
9600                         <xsl:for-each select="marc:datafield[@tag=300]">
9601                                 <extent>
9602                                         <xsl:call-template name="subfieldSelect">
9603                                                 <xsl:with-param name="codes">abce</xsl:with-param>
9604                                         </xsl:call-template>
9605                                 </extent>
9606                         </xsl:for-each>
9607                 </xsl:variable>
9608                 <xsl:if test="string-length(normalize-space($physicalDescription))">
9609                         <physicalDescription>
9610                                 <xsl:copy-of select="$physicalDescription"></xsl:copy-of>
9611                         </physicalDescription>
9612                 </xsl:if>
9613                 <xsl:for-each select="marc:datafield[@tag=520]">
9614                         <abstract>
9615                                 <xsl:call-template name="uri"></xsl:call-template>
9616                                 <xsl:call-template name="subfieldSelect">
9617                                         <xsl:with-param name="codes">ab</xsl:with-param>
9618                                 </xsl:call-template>
9619                         </abstract>
9620                 </xsl:for-each>
9621                 <xsl:for-each select="marc:datafield[@tag=505]">
9622                         <tableOfContents>
9623                                 <xsl:call-template name="uri"></xsl:call-template>
9624                                 <xsl:call-template name="subfieldSelect">
9625                                         <xsl:with-param name="codes">agrt</xsl:with-param>
9626                                 </xsl:call-template>
9627                         </tableOfContents>
9628                 </xsl:for-each>
9629                 <xsl:for-each select="marc:datafield[@tag=521]">
9630                         <targetAudience>
9631                                 <xsl:call-template name="subfieldSelect">
9632                                         <xsl:with-param name="codes">ab</xsl:with-param>
9633                                 </xsl:call-template>
9634                         </targetAudience>
9635                 </xsl:for-each>
9636                 <xsl:if test="$typeOf008='BK' or $typeOf008='CF' or $typeOf008='MU' or $typeOf008='VM'">
9637                         <xsl:variable name="controlField008-22" select="substring($controlField008,23,1)"></xsl:variable>
9638                         <xsl:choose>
9639                                 <!-- 01/04 fix -->
9640                                 <xsl:when test="$controlField008-22='d'">
9641                                         <targetAudience authority="marctarget">adolescent</targetAudience>
9642                                 </xsl:when>
9643                                 <xsl:when test="$controlField008-22='e'">
9644                                         <targetAudience authority="marctarget">adult</targetAudience>
9645                                 </xsl:when>
9646                                 <xsl:when test="$controlField008-22='g'">
9647                                         <targetAudience authority="marctarget">general</targetAudience>
9648                                 </xsl:when>
9649                                 <xsl:when test="$controlField008-22='b' or $controlField008-22='c' or $controlField008-22='j'">
9650                                         <targetAudience authority="marctarget">juvenile</targetAudience>
9651                                 </xsl:when>
9652                                 <xsl:when test="$controlField008-22='a'">
9653                                         <targetAudience authority="marctarget">preschool</targetAudience>
9654                                 </xsl:when>
9655                                 <xsl:when test="$controlField008-22='f'">
9656                                         <targetAudience authority="marctarget">specialized</targetAudience>
9657                                 </xsl:when>
9658                         </xsl:choose>
9659                 </xsl:if>
9660                 <xsl:for-each select="marc:datafield[@tag=245]/marc:subfield[@code='c']">
9661                         <note type="statement of responsibility">
9662                                 <xsl:value-of select="."></xsl:value-of>
9663                         </note>
9664                 </xsl:for-each>
9665                 <xsl:for-each select="marc:datafield[@tag=500]">
9666                         <note>
9667                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9668                                 <xsl:call-template name="uri"></xsl:call-template>
9669                         </note>
9670                 </xsl:for-each>
9671                 
9672                 <!--3.2 change tmee additional note fields-->
9673                 
9674                 <xsl:for-each select="marc:datafield[@tag=506]">
9675                         <note type="restrictions">
9676                                 <xsl:call-template name="uri"></xsl:call-template>
9677                                 <xsl:variable name="str">
9678                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9679                                                 <xsl:value-of select="."></xsl:value-of>
9680                                                 <xsl:text> </xsl:text>
9681                                         </xsl:for-each>
9682                                 </xsl:variable>
9683                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9684                         </note>
9685                 </xsl:for-each>
9686                 
9687                 <xsl:for-each select="marc:datafield[@tag=510]">
9688                         <note  type="citation/reference">
9689                                 <xsl:call-template name="uri"></xsl:call-template>
9690                                 <xsl:variable name="str">
9691                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9692                                                 <xsl:value-of select="."></xsl:value-of>
9693                                                 <xsl:text> </xsl:text>
9694                                         </xsl:for-each>
9695                                 </xsl:variable>
9696                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9697                         </note>
9698                 </xsl:for-each>
9699                 
9700                         
9701                 <xsl:for-each select="marc:datafield[@tag=511]">
9702                         <note type="performers">
9703                                 <xsl:call-template name="uri"></xsl:call-template>
9704                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9705                         </note>
9706                 </xsl:for-each>
9707                 <xsl:for-each select="marc:datafield[@tag=518]">
9708                         <note type="venue">
9709                                 <xsl:call-template name="uri"></xsl:call-template>
9710                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9711                         </note>
9712                 </xsl:for-each>
9713                 
9714                 <xsl:for-each select="marc:datafield[@tag=530]">
9715                         <note  type="additional physical form">
9716                                 <xsl:call-template name="uri"></xsl:call-template>
9717                                 <xsl:variable name="str">
9718                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9719                                                 <xsl:value-of select="."></xsl:value-of>
9720                                                 <xsl:text> </xsl:text>
9721                                         </xsl:for-each>
9722                                 </xsl:variable>
9723                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9724                         </note>
9725                 </xsl:for-each>
9726                 
9727                 <xsl:for-each select="marc:datafield[@tag=533]">
9728                         <note  type="reproduction">
9729                                 <xsl:call-template name="uri"></xsl:call-template>
9730                                 <xsl:variable name="str">
9731                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9732                                                 <xsl:value-of select="."></xsl:value-of>
9733                                                 <xsl:text> </xsl:text>
9734                                         </xsl:for-each>
9735                                 </xsl:variable>
9736                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9737                         </note>
9738                 </xsl:for-each>
9739                 
9740                 <xsl:for-each select="marc:datafield[@tag=534]">
9741                         <note  type="original version">
9742                                 <xsl:call-template name="uri"></xsl:call-template>
9743                                 <xsl:variable name="str">
9744                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9745                                                 <xsl:value-of select="."></xsl:value-of>
9746                                                 <xsl:text> </xsl:text>
9747                                         </xsl:for-each>
9748                                 </xsl:variable>
9749                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9750                         </note>
9751                 </xsl:for-each>
9752                 
9753                 <xsl:for-each select="marc:datafield[@tag=538]">
9754                         <note  type="system details">
9755                                 <xsl:call-template name="uri"></xsl:call-template>
9756                                 <xsl:variable name="str">
9757                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9758                                                 <xsl:value-of select="."></xsl:value-of>
9759                                                 <xsl:text> </xsl:text>
9760                                         </xsl:for-each>
9761                                 </xsl:variable>
9762                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9763                         </note>
9764                 </xsl:for-each>
9765                 
9766                 <xsl:for-each select="marc:datafield[@tag=583]">
9767                         <note type="action">
9768                                 <xsl:call-template name="uri"></xsl:call-template>
9769                                 <xsl:variable name="str">
9770                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9771                                                 <xsl:value-of select="."></xsl:value-of>
9772                                                 <xsl:text> </xsl:text>
9773                                         </xsl:for-each>
9774                                 </xsl:variable>
9775                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9776                         </note>
9777                 </xsl:for-each>
9778                 
9779
9780                 
9781                 
9782                 
9783                 <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]">
9784                         <note>
9785                                 <xsl:call-template name="uri"></xsl:call-template>
9786                                 <xsl:variable name="str">
9787                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9788                                                 <xsl:value-of select="."></xsl:value-of>
9789                                                 <xsl:text> </xsl:text>
9790                                         </xsl:for-each>
9791                                 </xsl:variable>
9792                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9793                         </note>
9794                 </xsl:for-each>
9795                 <xsl:for-each select="marc:datafield[@tag=034][marc:subfield[@code='d' or @code='e' or @code='f' or @code='g']]">
9796                         <subject>
9797                                 <cartographics>
9798                                         <coordinates>
9799                                                 <xsl:call-template name="subfieldSelect">
9800                                                         <xsl:with-param name="codes">defg</xsl:with-param>
9801                                                 </xsl:call-template>
9802                                         </coordinates>
9803                                 </cartographics>
9804                         </subject>
9805                 </xsl:for-each>
9806                 <xsl:for-each select="marc:datafield[@tag=043]">
9807                         <subject>
9808                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
9809                                         <geographicCode>
9810                                                 <xsl:attribute name="authority">
9811                                                         <xsl:if test="@code='a'">
9812                                                                 <xsl:text>marcgac</xsl:text>
9813                                                         </xsl:if>
9814                                                         <xsl:if test="@code='b'">
9815                                                                 <xsl:value-of select="following-sibling::marc:subfield[@code=2]"></xsl:value-of>
9816                                                         </xsl:if>
9817                                                         <xsl:if test="@code='c'">
9818                                                                 <xsl:text>iso3166</xsl:text>
9819                                                         </xsl:if>
9820                                                 </xsl:attribute>
9821                                                 <xsl:value-of select="self::marc:subfield"></xsl:value-of>
9822                                         </geographicCode>
9823                                 </xsl:for-each>
9824                         </subject>
9825                 </xsl:for-each>
9826                 <!-- tmee 2006/11/27 -->
9827                 <xsl:for-each select="marc:datafield[@tag=255]">
9828                         <subject>
9829                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
9830                                 <cartographics>
9831                                         <xsl:if test="@code='a'">
9832                                                 <scale>
9833                                                         <xsl:value-of select="."></xsl:value-of>
9834                                                 </scale>
9835                                         </xsl:if>
9836                                         <xsl:if test="@code='b'">
9837                                                 <projection>
9838                                                         <xsl:value-of select="."></xsl:value-of>
9839                                                 </projection>
9840                                         </xsl:if>
9841                                         <xsl:if test="@code='c'">
9842                                                 <coordinates>
9843                                                         <xsl:value-of select="."></xsl:value-of>
9844                                                 </coordinates>
9845                                         </xsl:if>
9846                                 </cartographics>
9847                                 </xsl:for-each>
9848                         </subject>
9849                 </xsl:for-each>
9850                                 
9851                 <xsl:apply-templates select="marc:datafield[653 >= @tag and @tag >= 600]"></xsl:apply-templates>
9852                 <xsl:apply-templates select="marc:datafield[@tag=656]"></xsl:apply-templates>
9853                 <xsl:for-each select="marc:datafield[@tag=752]">
9854                         <subject>
9855                                 <hierarchicalGeographic>
9856                                         <xsl:for-each select="marc:subfield[@code='a']">
9857                                                 <country>
9858                                                         <xsl:call-template name="chopPunctuation">
9859                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9860                                                         </xsl:call-template>
9861                                                 </country>
9862                                         </xsl:for-each>
9863                                         <xsl:for-each select="marc:subfield[@code='b']">
9864                                                 <state>
9865                                                         <xsl:call-template name="chopPunctuation">
9866                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9867                                                         </xsl:call-template>
9868                                                 </state>
9869                                         </xsl:for-each>
9870                                         <xsl:for-each select="marc:subfield[@code='c']">
9871                                                 <county>
9872                                                         <xsl:call-template name="chopPunctuation">
9873                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9874                                                         </xsl:call-template>
9875                                                 </county>
9876                                         </xsl:for-each>
9877                                         <xsl:for-each select="marc:subfield[@code='d']">
9878                                                 <city>
9879                                                         <xsl:call-template name="chopPunctuation">
9880                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9881                                                         </xsl:call-template>
9882                                                 </city>
9883                                         </xsl:for-each>
9884                                 </hierarchicalGeographic>
9885                         </subject>
9886                 </xsl:for-each>
9887                 <xsl:for-each select="marc:datafield[@tag=045][marc:subfield[@code='b']]">
9888                         <subject>
9889                                 <xsl:choose>
9890                                         <xsl:when test="@ind1=2">
9891                                                 <temporal encoding="iso8601" point="start">
9892                                                         <xsl:call-template name="chopPunctuation">
9893                                                                 <xsl:with-param name="chopString">
9894                                                                         <xsl:value-of select="marc:subfield[@code='b'][1]"></xsl:value-of>
9895                                                                 </xsl:with-param>
9896                                                         </xsl:call-template>
9897                                                 </temporal>
9898                                                 <temporal encoding="iso8601" point="end">
9899                                                         <xsl:call-template name="chopPunctuation">
9900                                                                 <xsl:with-param name="chopString">
9901                                                                         <xsl:value-of select="marc:subfield[@code='b'][2]"></xsl:value-of>
9902                                                                 </xsl:with-param>
9903                                                         </xsl:call-template>
9904                                                 </temporal>
9905                                         </xsl:when>
9906                                         <xsl:otherwise>
9907                                                 <xsl:for-each select="marc:subfield[@code='b']">
9908                                                         <temporal encoding="iso8601">
9909                                                                 <xsl:call-template name="chopPunctuation">
9910                                                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
9911                                                                 </xsl:call-template>
9912                                                         </temporal>
9913                                                 </xsl:for-each>
9914                                         </xsl:otherwise>
9915                                 </xsl:choose>
9916                         </subject>
9917                 </xsl:for-each>
9918                 <xsl:for-each select="marc:datafield[@tag=050]">
9919                         <xsl:for-each select="marc:subfield[@code='b']">
9920                                 <classification authority="lcc">
9921                                         <xsl:if test="../marc:subfield[@code='3']">
9922                                                 <xsl:attribute name="displayLabel">
9923                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
9924                                                 </xsl:attribute>
9925                                         </xsl:if>
9926                                         <xsl:value-of select="preceding-sibling::marc:subfield[@code='a'][1]"></xsl:value-of>
9927                                         <xsl:text> </xsl:text>
9928                                         <xsl:value-of select="text()"></xsl:value-of>
9929                                 </classification>
9930                         </xsl:for-each>
9931                         <xsl:for-each select="marc:subfield[@code='a'][not(following-sibling::marc:subfield[@code='b'])]">
9932                                 <classification authority="lcc">
9933                                         <xsl:if test="../marc:subfield[@code='3']">
9934                                                 <xsl:attribute name="displayLabel">
9935                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
9936                                                 </xsl:attribute>
9937                                         </xsl:if>
9938                                         <xsl:value-of select="text()"></xsl:value-of>
9939                                 </classification>
9940                         </xsl:for-each>
9941                 </xsl:for-each>
9942                 <xsl:for-each select="marc:datafield[@tag=082]">
9943                         <classification authority="ddc">
9944                                 <xsl:if test="marc:subfield[@code='2']">
9945                                         <xsl:attribute name="edition">
9946                                                 <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9947                                         </xsl:attribute>
9948                                 </xsl:if>
9949                                 <xsl:call-template name="subfieldSelect">
9950                                         <xsl:with-param name="codes">ab</xsl:with-param>
9951                                 </xsl:call-template>
9952                         </classification>
9953                 </xsl:for-each>
9954                 <xsl:for-each select="marc:datafield[@tag=080]">
9955                         <classification authority="udc">
9956                                 <xsl:call-template name="subfieldSelect">
9957                                         <xsl:with-param name="codes">abx</xsl:with-param>
9958                                 </xsl:call-template>
9959                         </classification>
9960                 </xsl:for-each>
9961                 <xsl:for-each select="marc:datafield[@tag=060]">
9962                         <classification authority="nlm">
9963                                 <xsl:call-template name="subfieldSelect">
9964                                         <xsl:with-param name="codes">ab</xsl:with-param>
9965                                 </xsl:call-template>
9966                         </classification>
9967                 </xsl:for-each>
9968                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=0]">
9969                         <classification authority="sudocs">
9970                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9971                         </classification>
9972                 </xsl:for-each>
9973                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=1]">
9974                         <classification authority="candoc">
9975                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9976                         </classification>
9977                 </xsl:for-each>
9978                 <xsl:for-each select="marc:datafield[@tag=086]">
9979                         <classification>
9980                                 <xsl:attribute name="authority">
9981                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9982                                 </xsl:attribute>
9983                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9984                         </classification>
9985                 </xsl:for-each>
9986                 <xsl:for-each select="marc:datafield[@tag=084]">
9987                         <classification>
9988                                 <xsl:attribute name="authority">
9989                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9990                                 </xsl:attribute>
9991                                 <xsl:call-template name="subfieldSelect">
9992                                         <xsl:with-param name="codes">ab</xsl:with-param>
9993                                 </xsl:call-template>
9994                         </classification>
9995                 </xsl:for-each>
9996                 <xsl:for-each select="marc:datafield[@tag=440]">
9997                         <relatedItem type="series">
9998                                 <titleInfo>
9999                                         <title>
10000                                                 <xsl:call-template name="chopPunctuation">
10001                                                         <xsl:with-param name="chopString">
10002                                                                 <xsl:call-template name="subfieldSelect">
10003                                                                         <xsl:with-param name="codes">av</xsl:with-param>
10004                                                                 </xsl:call-template>
10005                                                         </xsl:with-param>
10006                                                 </xsl:call-template>
10007                                         </title>
10008                                         <xsl:call-template name="part"></xsl:call-template>
10009                                 </titleInfo>
10010                         </relatedItem>
10011                 </xsl:for-each>
10012                 <xsl:for-each select="marc:datafield[@tag=490][@ind1=0]">
10013                         <relatedItem type="series">
10014                                 <titleInfo>
10015                                         <title>
10016                                                 <xsl:call-template name="chopPunctuation">
10017                                                         <xsl:with-param name="chopString">
10018                                                                 <xsl:call-template name="subfieldSelect">
10019                                                                         <xsl:with-param name="codes">av</xsl:with-param>
10020                                                                 </xsl:call-template>
10021                                                         </xsl:with-param>
10022                                                 </xsl:call-template>
10023                                         </title>
10024                                         <xsl:call-template name="part"></xsl:call-template>
10025                                 </titleInfo>
10026                         </relatedItem>
10027                 </xsl:for-each>
10028                 <xsl:for-each select="marc:datafield[@tag=510]">
10029                         <relatedItem type="isReferencedBy">
10030                                 <note>
10031                                         <xsl:call-template name="subfieldSelect">
10032                                                 <xsl:with-param name="codes">abcx3</xsl:with-param>
10033                                         </xsl:call-template>
10034                                 </note>
10035                         </relatedItem>
10036                 </xsl:for-each>
10037                 <xsl:for-each select="marc:datafield[@tag=534]">
10038                         <relatedItem type="original">
10039                                 <xsl:call-template name="relatedTitle"></xsl:call-template>
10040                                 <xsl:call-template name="relatedName"></xsl:call-template>
10041                                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
10042                                         <originInfo>
10043                                                 <xsl:for-each select="marc:subfield[@code='c']">
10044                                                         <publisher>
10045                                                                 <xsl:value-of select="."></xsl:value-of>
10046                                                         </publisher>
10047                                                 </xsl:for-each>
10048                                                 <xsl:for-each select="marc:subfield[@code='b']">
10049                                                         <edition>
10050                                                                 <xsl:value-of select="."></xsl:value-of>
10051                                                         </edition>
10052                                                 </xsl:for-each>
10053                                         </originInfo>
10054                                 </xsl:if>
10055                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10056                                 <xsl:for-each select="marc:subfield[@code='z']">
10057                                         <identifier type="isbn">
10058                                                 <xsl:value-of select="."></xsl:value-of>
10059                                         </identifier>
10060                                 </xsl:for-each>
10061                                 <xsl:call-template name="relatedNote"></xsl:call-template>
10062                         </relatedItem>
10063                 </xsl:for-each>
10064                 <xsl:for-each select="marc:datafield[@tag=700][marc:subfield[@code='t']]">
10065                         <relatedItem>
10066                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10067                                 <titleInfo>
10068                                         <title>
10069                                                 <xsl:call-template name="chopPunctuation">
10070                                                         <xsl:with-param name="chopString">
10071                                                                 <xsl:call-template name="specialSubfieldSelect">
10072                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10073                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10074                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10075                                                                 </xsl:call-template>
10076                                                         </xsl:with-param>
10077                                                 </xsl:call-template>
10078                                         </title>
10079                                         <xsl:call-template name="part"></xsl:call-template>
10080                                 </titleInfo>
10081                                 <name type="personal">
10082                                         <namePart>
10083                                                 <xsl:call-template name="specialSubfieldSelect">
10084                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
10085                                                         <xsl:with-param name="axis">t</xsl:with-param>
10086                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
10087                                                 </xsl:call-template>
10088                                         </namePart>
10089                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
10090                                         <xsl:call-template name="nameDate"></xsl:call-template>
10091                                         <xsl:call-template name="role"></xsl:call-template>
10092                                 </name>
10093                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10094                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10095                         </relatedItem>
10096                 </xsl:for-each>
10097                 <xsl:for-each select="marc:datafield[@tag=710][marc:subfield[@code='t']]">
10098                         <relatedItem>
10099                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10100                                 <titleInfo>
10101                                         <title>
10102                                                 <xsl:call-template name="chopPunctuation">
10103                                                         <xsl:with-param name="chopString">
10104                                                                 <xsl:call-template name="specialSubfieldSelect">
10105                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10106                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10107                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
10108                                                                 </xsl:call-template>
10109                                                         </xsl:with-param>
10110                                                 </xsl:call-template>
10111                                         </title>
10112                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10113                                 </titleInfo>
10114                                 <name type="corporate">
10115                                         <xsl:for-each select="marc:subfield[@code='a']">
10116                                                 <namePart>
10117                                                         <xsl:value-of select="."></xsl:value-of>
10118                                                 </namePart>
10119                                         </xsl:for-each>
10120                                         <xsl:for-each select="marc:subfield[@code='b']">
10121                                                 <namePart>
10122                                                         <xsl:value-of select="."></xsl:value-of>
10123                                                 </namePart>
10124                                         </xsl:for-each>
10125                                         <xsl:variable name="tempNamePart">
10126                                                 <xsl:call-template name="specialSubfieldSelect">
10127                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
10128                                                         <xsl:with-param name="axis">t</xsl:with-param>
10129                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
10130                                                 </xsl:call-template>
10131                                         </xsl:variable>
10132                                         <xsl:if test="normalize-space($tempNamePart)">
10133                                                 <namePart>
10134                                                         <xsl:value-of select="$tempNamePart"></xsl:value-of>
10135                                                 </namePart>
10136                                         </xsl:if>
10137                                         <xsl:call-template name="role"></xsl:call-template>
10138                                 </name>
10139                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10140                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10141                         </relatedItem>
10142                 </xsl:for-each>
10143                 <xsl:for-each select="marc:datafield[@tag=711][marc:subfield[@code='t']]">
10144                         <relatedItem>
10145                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10146                                 <titleInfo>
10147                                         <title>
10148                                                 <xsl:call-template name="chopPunctuation">
10149                                                         <xsl:with-param name="chopString">
10150                                                                 <xsl:call-template name="specialSubfieldSelect">
10151                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
10152                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10153                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10154                                                                 </xsl:call-template>
10155                                                         </xsl:with-param>
10156                                                 </xsl:call-template>
10157                                         </title>
10158                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10159                                 </titleInfo>
10160                                 <name type="conference">
10161                                         <namePart>
10162                                                 <xsl:call-template name="specialSubfieldSelect">
10163                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
10164                                                         <xsl:with-param name="axis">t</xsl:with-param>
10165                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
10166                                                 </xsl:call-template>
10167                                         </namePart>
10168                                 </name>
10169                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10170                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10171                         </relatedItem>
10172                 </xsl:for-each>
10173                 <xsl:for-each select="marc:datafield[@tag=730][@ind2=2]">
10174                         <relatedItem>
10175                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10176                                 <titleInfo>
10177                                         <title>
10178                                                 <xsl:call-template name="chopPunctuation">
10179                                                         <xsl:with-param name="chopString">
10180                                                                 <xsl:call-template name="subfieldSelect">
10181                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
10182                                                                 </xsl:call-template>
10183                                                         </xsl:with-param>
10184                                                 </xsl:call-template>
10185                                         </title>
10186                                         <xsl:call-template name="part"></xsl:call-template>
10187                                 </titleInfo>
10188                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10189                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10190                         </relatedItem>
10191                 </xsl:for-each>
10192                 <xsl:for-each select="marc:datafield[@tag=740][@ind2=2]">
10193                         <relatedItem>
10194                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10195                                 <titleInfo>
10196                                         <title>
10197                                                 <xsl:call-template name="chopPunctuation">
10198                                                         <xsl:with-param name="chopString">
10199                                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
10200                                                         </xsl:with-param>
10201                                                 </xsl:call-template>
10202                                         </title>
10203                                         <xsl:call-template name="part"></xsl:call-template>
10204                                 </titleInfo>
10205                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10206                         </relatedItem>
10207                 </xsl:for-each>
10208                 <xsl:for-each select="marc:datafield[@tag=760]|marc:datafield[@tag=762]">
10209                         <relatedItem type="series">
10210                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10211                         </relatedItem>
10212                 </xsl:for-each>
10213                 <xsl:for-each select="marc:datafield[@tag=765]|marc:datafield[@tag=767]|marc:datafield[@tag=777]|marc:datafield[@tag=787]">
10214                         <relatedItem>
10215                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10216                         </relatedItem>
10217                 </xsl:for-each>
10218                 <xsl:for-each select="marc:datafield[@tag=775]">
10219                         <relatedItem type="otherVersion">
10220                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10221                         </relatedItem>
10222                 </xsl:for-each>
10223                 <xsl:for-each select="marc:datafield[@tag=770]|marc:datafield[@tag=774]">
10224                         <relatedItem type="constituent">
10225                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10226                         </relatedItem>
10227                 </xsl:for-each>
10228                 <xsl:for-each select="marc:datafield[@tag=772]|marc:datafield[@tag=773]">
10229                         <relatedItem type="host">
10230                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10231                         </relatedItem>
10232                 </xsl:for-each>
10233                 <xsl:for-each select="marc:datafield[@tag=776]">
10234                         <relatedItem type="otherFormat">
10235                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10236                         </relatedItem>
10237                 </xsl:for-each>
10238                 <xsl:for-each select="marc:datafield[@tag=780]">
10239                         <relatedItem type="preceding">
10240                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10241                         </relatedItem>
10242                 </xsl:for-each>
10243                 <xsl:for-each select="marc:datafield[@tag=785]">
10244                         <relatedItem type="succeeding">
10245                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10246                         </relatedItem>
10247                 </xsl:for-each>
10248                 <xsl:for-each select="marc:datafield[@tag=786]">
10249                         <relatedItem type="original">
10250                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10251                         </relatedItem>
10252                 </xsl:for-each>
10253                 <xsl:for-each select="marc:datafield[@tag=800]">
10254                         <relatedItem type="series">
10255                                 <titleInfo>
10256                                         <title>
10257                                                 <xsl:call-template name="chopPunctuation">
10258                                                         <xsl:with-param name="chopString">
10259                                                                 <xsl:call-template name="specialSubfieldSelect">
10260                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10261                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10262                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10263                                                                 </xsl:call-template>
10264                                                         </xsl:with-param>
10265                                                 </xsl:call-template>
10266                                         </title>
10267                                         <xsl:call-template name="part"></xsl:call-template>
10268                                 </titleInfo>
10269                                 <name type="personal">
10270                                         <namePart>
10271                                                 <xsl:call-template name="chopPunctuation">
10272                                                         <xsl:with-param name="chopString">
10273                                                                 <xsl:call-template name="specialSubfieldSelect">
10274                                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
10275                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10276                                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
10277                                                                 </xsl:call-template>
10278                                                         </xsl:with-param>
10279                                                 </xsl:call-template>
10280                                         </namePart>
10281                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
10282                                         <xsl:call-template name="nameDate"></xsl:call-template>
10283                                         <xsl:call-template name="role"></xsl:call-template>
10284                                 </name>
10285                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10286                         </relatedItem>
10287                 </xsl:for-each>
10288                 <xsl:for-each select="marc:datafield[@tag=810]">
10289                         <relatedItem type="series">
10290                                 <titleInfo>
10291                                         <title>
10292                                                 <xsl:call-template name="chopPunctuation">
10293                                                         <xsl:with-param name="chopString">
10294                                                                 <xsl:call-template name="specialSubfieldSelect">
10295                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10296                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10297                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
10298                                                                 </xsl:call-template>
10299                                                         </xsl:with-param>
10300                                                 </xsl:call-template>
10301                                         </title>
10302                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10303                                 </titleInfo>
10304                                 <name type="corporate">
10305                                         <xsl:for-each select="marc:subfield[@code='a']">
10306                                                 <namePart>
10307                                                         <xsl:value-of select="."></xsl:value-of>
10308                                                 </namePart>
10309                                         </xsl:for-each>
10310                                         <xsl:for-each select="marc:subfield[@code='b']">
10311                                                 <namePart>
10312                                                         <xsl:value-of select="."></xsl:value-of>
10313                                                 </namePart>
10314                                         </xsl:for-each>
10315                                         <namePart>
10316                                                 <xsl:call-template name="specialSubfieldSelect">
10317                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
10318                                                         <xsl:with-param name="axis">t</xsl:with-param>
10319                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
10320                                                 </xsl:call-template>
10321                                         </namePart>
10322                                         <xsl:call-template name="role"></xsl:call-template>
10323                                 </name>
10324                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10325                         </relatedItem>
10326                 </xsl:for-each>
10327                 <xsl:for-each select="marc:datafield[@tag=811]">
10328                         <relatedItem type="series">
10329                                 <titleInfo>
10330                                         <title>
10331                                                 <xsl:call-template name="chopPunctuation">
10332                                                         <xsl:with-param name="chopString">
10333                                                                 <xsl:call-template name="specialSubfieldSelect">
10334                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
10335                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10336                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10337                                                                 </xsl:call-template>
10338                                                         </xsl:with-param>
10339                                                 </xsl:call-template>
10340                                         </title>
10341                                         <xsl:call-template name="relatedPartNumName"/>
10342                                 </titleInfo>
10343                                 <name type="conference">
10344                                         <namePart>
10345                                                 <xsl:call-template name="specialSubfieldSelect">
10346                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
10347                                                         <xsl:with-param name="axis">t</xsl:with-param>
10348                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
10349                                                 </xsl:call-template>
10350                                         </namePart>
10351                                         <xsl:call-template name="role"/>
10352                                 </name>
10353                                 <xsl:call-template name="relatedForm"/>
10354                         </relatedItem>
10355                 </xsl:for-each>
10356                 <xsl:for-each select="marc:datafield[@tag='830']">
10357                         <relatedItem type="series">
10358                                 <titleInfo>
10359                                         <title>
10360                                                 <xsl:call-template name="chopPunctuation">
10361                                                         <xsl:with-param name="chopString">
10362                                                                 <xsl:call-template name="subfieldSelect">
10363                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
10364                                                                 </xsl:call-template>
10365                                                         </xsl:with-param>
10366                                                 </xsl:call-template>
10367                                         </title>
10368                                         <xsl:call-template name="part"/>
10369                                 </titleInfo>
10370                                 <xsl:call-template name="relatedForm"/>
10371                         </relatedItem>
10372                 </xsl:for-each>
10373                 <xsl:for-each select="marc:datafield[@tag='856'][@ind2='2']/marc:subfield[@code='q']">
10374                         <relatedItem>
10375                                 <internetMediaType>
10376                                         <xsl:value-of select="."/>
10377                                 </internetMediaType>
10378                         </relatedItem>
10379                 </xsl:for-each>
10380                 <xsl:for-each select="marc:datafield[@tag='020']">
10381                         <xsl:call-template name="isInvalid">
10382                                 <xsl:with-param name="type">isbn</xsl:with-param>
10383                         </xsl:call-template>
10384                         <xsl:if test="marc:subfield[@code='a']">
10385                                 <identifier type="isbn">
10386                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10387                                 </identifier>
10388                         </xsl:if>
10389                 </xsl:for-each>
10390                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='0']">
10391                         <xsl:call-template name="isInvalid">
10392                                 <xsl:with-param name="type">isrc</xsl:with-param>
10393                         </xsl:call-template>
10394                         <xsl:if test="marc:subfield[@code='a']">
10395                                 <identifier type="isrc">
10396                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10397                                 </identifier>
10398                         </xsl:if>
10399                 </xsl:for-each>
10400                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='2']">
10401                         <xsl:call-template name="isInvalid">
10402                                 <xsl:with-param name="type">ismn</xsl:with-param>
10403                         </xsl:call-template>
10404                         <xsl:if test="marc:subfield[@code='a']">
10405                                 <identifier type="ismn">
10406                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10407                                 </identifier>
10408                         </xsl:if>
10409                 </xsl:for-each>
10410                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='4']">
10411                         <xsl:call-template name="isInvalid">
10412                                 <xsl:with-param name="type">sici</xsl:with-param>
10413                         </xsl:call-template>
10414                         <identifier type="sici">
10415                                 <xsl:call-template name="subfieldSelect">
10416                                         <xsl:with-param name="codes">ab</xsl:with-param>
10417                                 </xsl:call-template>
10418                         </identifier>
10419                 </xsl:for-each>
10420                 <xsl:for-each select="marc:datafield[@tag='022']">
10421                         <xsl:call-template name="isInvalid">
10422                                 <xsl:with-param name="type">issn</xsl:with-param>
10423                         </xsl:call-template>
10424                         <identifier type="issn">
10425                                 <xsl:value-of select="marc:subfield[@code='a']"/>
10426                         </identifier>
10427                 </xsl:for-each>
10428                 <xsl:for-each select="marc:datafield[@tag='010']">
10429                         <xsl:call-template name="isInvalid">
10430                                 <xsl:with-param name="type">lccn</xsl:with-param>
10431                         </xsl:call-template>
10432                         <identifier type="lccn">
10433                                 <xsl:value-of select="normalize-space(marc:subfield[@code='a'])"/>
10434                         </identifier>
10435                 </xsl:for-each>
10436                 <xsl:for-each select="marc:datafield[@tag='028']">
10437                         <identifier>
10438                                 <xsl:attribute name="type">
10439                                         <xsl:choose>
10440                                                 <xsl:when test="@ind1='0'">issue number</xsl:when>
10441                                                 <xsl:when test="@ind1='1'">matrix number</xsl:when>
10442                                                 <xsl:when test="@ind1='2'">music plate</xsl:when>
10443                                                 <xsl:when test="@ind1='3'">music publisher</xsl:when>
10444                                                 <xsl:when test="@ind1='4'">videorecording identifier</xsl:when>
10445                                         </xsl:choose>
10446                                 </xsl:attribute>
10447                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 028 -->
10448                                 <xsl:call-template name="subfieldSelect">
10449                                         <xsl:with-param name="codes">
10450                                                 <xsl:choose>
10451                                                         <xsl:when test="@ind1='0'">ba</xsl:when>
10452                                                         <xsl:otherwise>ab</xsl:otherwise>
10453                                                 </xsl:choose>
10454                                         </xsl:with-param>
10455                                 </xsl:call-template>
10456                         </identifier>
10457                 </xsl:for-each>
10458                 <xsl:for-each select="marc:datafield[@tag='037']">
10459                         <identifier type="stock number">
10460                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 037 -->
10461                                 <xsl:call-template name="subfieldSelect">
10462                                         <xsl:with-param name="codes">ab</xsl:with-param>
10463                                 </xsl:call-template>
10464                         </identifier>
10465                 </xsl:for-each>
10466                 <xsl:for-each select="marc:datafield[@tag='856'][marc:subfield[@code='u']]">
10467                         <identifier>
10468                                 <xsl:attribute name="type">
10469                                         <xsl:choose>
10470                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:doi') or starts-with(marc:subfield[@code='u'],'doi')">doi</xsl:when>
10471                                                 <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>
10472                                                 <xsl:otherwise>uri</xsl:otherwise>
10473                                         </xsl:choose>
10474                                 </xsl:attribute>
10475                                 <xsl:choose>
10476                                         <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') ">
10477                                                 <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
10478                                         </xsl:when>
10479                                         <xsl:otherwise>
10480                                                 <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
10481                                         </xsl:otherwise>
10482                                 </xsl:choose>
10483                         </identifier>
10484                         <xsl:if test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl')">
10485                                 <identifier type="hdl">
10486                                         <xsl:if test="marc:subfield[@code='y' or @code='3' or @code='z']">
10487                                                 <xsl:attribute name="displayLabel">
10488                                                         <xsl:call-template name="subfieldSelect">
10489                                                                 <xsl:with-param name="codes">y3z</xsl:with-param>
10490                                                         </xsl:call-template>
10491                                                 </xsl:attribute>
10492                                         </xsl:if>
10493                                         <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
10494                                 </identifier>
10495                         </xsl:if>
10496                 </xsl:for-each>
10497                 <xsl:for-each select="marc:datafield[@tag=024][@ind1=1]">
10498                         <identifier type="upc">
10499                                 <xsl:call-template name="isInvalid"/>
10500                                 <xsl:value-of select="marc:subfield[@code='a']"/>
10501                         </identifier>
10502                 </xsl:for-each>
10503                 <!-- 1/04 fix added $y -->
10504                 <xsl:for-each select="marc:datafield[@tag=856][marc:subfield[@code='u']]">
10505                         <location>
10506                                 <url>
10507                                         <xsl:if test="marc:subfield[@code='y' or @code='3']">
10508                                                 <xsl:attribute name="displayLabel">
10509                                                         <xsl:call-template name="subfieldSelect">
10510                                                                 <xsl:with-param name="codes">y3</xsl:with-param>
10511                                                         </xsl:call-template>
10512                                                 </xsl:attribute>
10513                                         </xsl:if>
10514                                         <xsl:if test="marc:subfield[@code='z' ]">
10515                                                 <xsl:attribute name="note">
10516                                                         <xsl:call-template name="subfieldSelect">
10517                                                                 <xsl:with-param name="codes">z</xsl:with-param>
10518                                                         </xsl:call-template>
10519                                                 </xsl:attribute>
10520                                         </xsl:if>
10521                                         <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
10522
10523                                 </url>
10524                         </location>
10525                 </xsl:for-each>
10526                         
10527                         <!-- 3.2 change tmee 856z  -->
10528
10529                 
10530                 <xsl:for-each select="marc:datafield[@tag=852]">
10531                         <location>
10532                                 <physicalLocation>
10533                                         <xsl:call-template name="displayLabel"></xsl:call-template>
10534                                         <xsl:call-template name="subfieldSelect">
10535                                                 <xsl:with-param name="codes">abje</xsl:with-param>
10536                                         </xsl:call-template>
10537                                 </physicalLocation>
10538                         </location>
10539                 </xsl:for-each>
10540                 <xsl:for-each select="marc:datafield[@tag=506]">
10541                         <accessCondition type="restrictionOnAccess">
10542                                 <xsl:call-template name="subfieldSelect">
10543                                         <xsl:with-param name="codes">abcd35</xsl:with-param>
10544                                 </xsl:call-template>
10545                         </accessCondition>
10546                 </xsl:for-each>
10547                 <xsl:for-each select="marc:datafield[@tag=540]">
10548                         <accessCondition type="useAndReproduction">
10549                                 <xsl:call-template name="subfieldSelect">
10550                                         <xsl:with-param name="codes">abcde35</xsl:with-param>
10551                                 </xsl:call-template>
10552                         </accessCondition>
10553                 </xsl:for-each>
10554                 <recordInfo>
10555                         <xsl:for-each select="marc:datafield[@tag=040]">
10556                                 <recordContentSource authority="marcorg">
10557                                         <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
10558                                 </recordContentSource>
10559                         </xsl:for-each>
10560                         <xsl:for-each select="marc:controlfield[@tag=008]">
10561                                 <recordCreationDate encoding="marc">
10562                                         <xsl:value-of select="substring(.,1,6)"></xsl:value-of>
10563                                 </recordCreationDate>
10564                         </xsl:for-each>
10565                         <xsl:for-each select="marc:controlfield[@tag=005]">
10566                                 <recordChangeDate encoding="iso8601">
10567                                         <xsl:value-of select="."></xsl:value-of>
10568                                 </recordChangeDate>
10569                         </xsl:for-each>
10570                         <xsl:for-each select="marc:controlfield[@tag=001]">
10571                                 <recordIdentifier>
10572                                         <xsl:if test="../marc:controlfield[@tag=003]">
10573                                                 <xsl:attribute name="source">
10574                                                         <xsl:value-of select="../marc:controlfield[@tag=003]"></xsl:value-of>
10575                                                 </xsl:attribute>
10576                                         </xsl:if>
10577                                         <xsl:value-of select="."></xsl:value-of>
10578                                 </recordIdentifier>
10579                         </xsl:for-each>
10580                         <xsl:for-each select="marc:datafield[@tag=040]/marc:subfield[@code='b']">
10581                                 <languageOfCataloging>
10582                                         <languageTerm authority="iso639-2b" type="code">
10583                                                 <xsl:value-of select="."></xsl:value-of>
10584                                         </languageTerm>
10585                                 </languageOfCataloging>
10586                         </xsl:for-each>
10587                 </recordInfo>
10588         </xsl:template>
10589         <xsl:template name="displayForm">
10590                 <xsl:for-each select="marc:subfield[@code='c']">
10591                         <displayForm>
10592                                 <xsl:value-of select="."></xsl:value-of>
10593                         </displayForm>
10594                 </xsl:for-each>
10595         </xsl:template>
10596         <xsl:template name="affiliation">
10597                 <xsl:for-each select="marc:subfield[@code='u']">
10598                         <affiliation>
10599                                 <xsl:value-of select="."></xsl:value-of>
10600                         </affiliation>
10601                 </xsl:for-each>
10602         </xsl:template>
10603         <xsl:template name="uri">
10604                 <xsl:for-each select="marc:subfield[@code='u']">
10605                         <xsl:attribute name="xlink:href">
10606                                 <xsl:value-of select="."></xsl:value-of>
10607                         </xsl:attribute>
10608                 </xsl:for-each>
10609         </xsl:template>
10610         <xsl:template name="role">
10611                 <xsl:for-each select="marc:subfield[@code='e']">
10612                         <role>
10613                                 <roleTerm type="text">
10614                                         <xsl:value-of select="."></xsl:value-of>
10615                                 </roleTerm>
10616                         </role>
10617                 </xsl:for-each>
10618                 <xsl:for-each select="marc:subfield[@code='4']">
10619                         <role>
10620                                 <roleTerm authority="marcrelator" type="code">
10621                                         <xsl:value-of select="."></xsl:value-of>
10622                                 </roleTerm>
10623                         </role>
10624                 </xsl:for-each>
10625         </xsl:template>
10626         <xsl:template name="part">
10627                 <xsl:variable name="partNumber">
10628                         <xsl:call-template name="specialSubfieldSelect">
10629                                 <xsl:with-param name="axis">n</xsl:with-param>
10630                                 <xsl:with-param name="anyCodes">n</xsl:with-param>
10631                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10632                         </xsl:call-template>
10633                 </xsl:variable>
10634                 <xsl:variable name="partName">
10635                         <xsl:call-template name="specialSubfieldSelect">
10636                                 <xsl:with-param name="axis">p</xsl:with-param>
10637                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
10638                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10639                         </xsl:call-template>
10640                 </xsl:variable>
10641                 <xsl:if test="string-length(normalize-space($partNumber))">
10642                         <partNumber>
10643                                 <xsl:call-template name="chopPunctuation">
10644                                         <xsl:with-param name="chopString" select="$partNumber"></xsl:with-param>
10645                                 </xsl:call-template>
10646                         </partNumber>
10647                 </xsl:if>
10648                 <xsl:if test="string-length(normalize-space($partName))">
10649                         <partName>
10650                                 <xsl:call-template name="chopPunctuation">
10651                                         <xsl:with-param name="chopString" select="$partName"></xsl:with-param>
10652                                 </xsl:call-template>
10653                         </partName>
10654                 </xsl:if>
10655         </xsl:template>
10656         <xsl:template name="relatedPart">
10657                 <xsl:if test="@tag=773">
10658                         <xsl:for-each select="marc:subfield[@code='g']">
10659                                 <part>
10660                                         <text>
10661                                                 <xsl:value-of select="."></xsl:value-of>
10662                                         </text>
10663                                 </part>
10664                         </xsl:for-each>
10665                         <xsl:for-each select="marc:subfield[@code='q']">
10666                                 <part>
10667                                         <xsl:call-template name="parsePart"></xsl:call-template>
10668                                 </part>
10669                         </xsl:for-each>
10670                 </xsl:if>
10671         </xsl:template>
10672         <xsl:template name="relatedPartNumName">
10673                 <xsl:variable name="partNumber">
10674                         <xsl:call-template name="specialSubfieldSelect">
10675                                 <xsl:with-param name="axis">g</xsl:with-param>
10676                                 <xsl:with-param name="anyCodes">g</xsl:with-param>
10677                                 <xsl:with-param name="afterCodes">pst</xsl:with-param>
10678                         </xsl:call-template>
10679                 </xsl:variable>
10680                 <xsl:variable name="partName">
10681                         <xsl:call-template name="specialSubfieldSelect">
10682                                 <xsl:with-param name="axis">p</xsl:with-param>
10683                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
10684                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10685                         </xsl:call-template>
10686                 </xsl:variable>
10687                 <xsl:if test="string-length(normalize-space($partNumber))">
10688                         <partNumber>
10689                                 <xsl:value-of select="$partNumber"></xsl:value-of>
10690                         </partNumber>
10691                 </xsl:if>
10692                 <xsl:if test="string-length(normalize-space($partName))">
10693                         <partName>
10694                                 <xsl:value-of select="$partName"></xsl:value-of>
10695                         </partName>
10696                 </xsl:if>
10697         </xsl:template>
10698         <xsl:template name="relatedName">
10699                 <xsl:for-each select="marc:subfield[@code='a']">
10700                         <name>
10701                                 <namePart>
10702                                         <xsl:value-of select="."></xsl:value-of>
10703                                 </namePart>
10704                         </name>
10705                 </xsl:for-each>
10706         </xsl:template>
10707         <xsl:template name="relatedForm">
10708                 <xsl:for-each select="marc:subfield[@code='h']">
10709                         <physicalDescription>
10710                                 <form>
10711                                         <xsl:value-of select="."></xsl:value-of>
10712                                 </form>
10713                         </physicalDescription>
10714                 </xsl:for-each>
10715         </xsl:template>
10716         <xsl:template name="relatedExtent">
10717                 <xsl:for-each select="marc:subfield[@code='h']">
10718                         <physicalDescription>
10719                                 <extent>
10720                                         <xsl:value-of select="."></xsl:value-of>
10721                                 </extent>
10722                         </physicalDescription>
10723                 </xsl:for-each>
10724         </xsl:template>
10725         <xsl:template name="relatedNote">
10726                 <xsl:for-each select="marc:subfield[@code='n']">
10727                         <note>
10728                                 <xsl:value-of select="."></xsl:value-of>
10729                         </note>
10730                 </xsl:for-each>
10731         </xsl:template>
10732         <xsl:template name="relatedSubject">
10733                 <xsl:for-each select="marc:subfield[@code='j']">
10734                         <subject>
10735                                 <temporal encoding="iso8601">
10736                                         <xsl:call-template name="chopPunctuation">
10737                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10738                                         </xsl:call-template>
10739                                 </temporal>
10740                         </subject>
10741                 </xsl:for-each>
10742         </xsl:template>
10743         <xsl:template name="relatedIdentifierISSN">
10744                 <xsl:for-each select="marc:subfield[@code='x']">
10745                         <identifier type="issn">
10746                                 <xsl:value-of select="."></xsl:value-of>
10747                         </identifier>
10748                 </xsl:for-each>
10749         </xsl:template>
10750         <xsl:template name="relatedIdentifierLocal">
10751                 <xsl:for-each select="marc:subfield[@code='w']">
10752                         <identifier type="local">
10753                                 <xsl:value-of select="."></xsl:value-of>
10754                         </identifier>
10755                 </xsl:for-each>
10756         </xsl:template>
10757         <xsl:template name="relatedIdentifier">
10758                 <xsl:for-each select="marc:subfield[@code='o']">
10759                         <identifier>
10760                                 <xsl:value-of select="."></xsl:value-of>
10761                         </identifier>
10762                 </xsl:for-each>
10763         </xsl:template>
10764         <xsl:template name="relatedItem76X-78X">
10765                 <xsl:call-template name="displayLabel"></xsl:call-template>
10766                 <xsl:call-template name="relatedTitle76X-78X"></xsl:call-template>
10767                 <xsl:call-template name="relatedName"></xsl:call-template>
10768                 <xsl:call-template name="relatedOriginInfo"></xsl:call-template>
10769                 <xsl:call-template name="relatedLanguage"></xsl:call-template>
10770                 <xsl:call-template name="relatedExtent"></xsl:call-template>
10771                 <xsl:call-template name="relatedNote"></xsl:call-template>
10772                 <xsl:call-template name="relatedSubject"></xsl:call-template>
10773                 <xsl:call-template name="relatedIdentifier"></xsl:call-template>
10774                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10775                 <xsl:call-template name="relatedIdentifierLocal"></xsl:call-template>
10776                 <xsl:call-template name="relatedPart"></xsl:call-template>
10777         </xsl:template>
10778         <xsl:template name="subjectGeographicZ">
10779                 <geographic>
10780                         <xsl:call-template name="chopPunctuation">
10781                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10782                         </xsl:call-template>
10783                 </geographic>
10784         </xsl:template>
10785         <xsl:template name="subjectTemporalY">
10786                 <temporal>
10787                         <xsl:call-template name="chopPunctuation">
10788                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10789                         </xsl:call-template>
10790                 </temporal>
10791         </xsl:template>
10792         <xsl:template name="subjectTopic">
10793                 <topic>
10794                         <xsl:call-template name="chopPunctuation">
10795                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10796                         </xsl:call-template>
10797                 </topic>
10798         </xsl:template> 
10799         <!-- 3.2 change tmee 6xx $v genre -->
10800         <xsl:template name="subjectGenre">
10801                 <genre>
10802                         <xsl:call-template name="chopPunctuation">
10803                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10804                         </xsl:call-template>
10805                 </genre>
10806         </xsl:template>
10807         
10808         <xsl:template name="nameABCDN">
10809                 <xsl:for-each select="marc:subfield[@code='a']">
10810                         <namePart>
10811                                 <xsl:call-template name="chopPunctuation">
10812                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
10813                                 </xsl:call-template>
10814                         </namePart>
10815                 </xsl:for-each>
10816                 <xsl:for-each select="marc:subfield[@code='b']">
10817                         <namePart>
10818                                 <xsl:value-of select="."></xsl:value-of>
10819                         </namePart>
10820                 </xsl:for-each>
10821                 <xsl:if test="marc:subfield[@code='c'] or marc:subfield[@code='d'] or marc:subfield[@code='n']">
10822                         <namePart>
10823                                 <xsl:call-template name="subfieldSelect">
10824                                         <xsl:with-param name="codes">cdn</xsl:with-param>
10825                                 </xsl:call-template>
10826                         </namePart>
10827                 </xsl:if>
10828         </xsl:template>
10829         <xsl:template name="nameABCDQ">
10830                 <namePart>
10831                         <xsl:call-template name="chopPunctuation">
10832                                 <xsl:with-param name="chopString">
10833                                         <xsl:call-template name="subfieldSelect">
10834                                                 <xsl:with-param name="codes">aq</xsl:with-param>
10835                                         </xsl:call-template>
10836                                 </xsl:with-param>
10837                                 <xsl:with-param name="punctuation">
10838                                         <xsl:text>:,;/ </xsl:text>
10839                                 </xsl:with-param>
10840                         </xsl:call-template>
10841                 </namePart>
10842                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
10843                 <xsl:call-template name="nameDate"></xsl:call-template>
10844         </xsl:template>
10845         <xsl:template name="nameACDEQ">
10846                 <namePart>
10847                         <xsl:call-template name="subfieldSelect">
10848                                 <xsl:with-param name="codes">acdeq</xsl:with-param>
10849                         </xsl:call-template>
10850                 </namePart>
10851         </xsl:template>
10852         <xsl:template name="constituentOrRelatedType">
10853                 <xsl:if test="@ind2=2">
10854                         <xsl:attribute name="type">constituent</xsl:attribute>
10855                 </xsl:if>
10856         </xsl:template>
10857         <xsl:template name="relatedTitle">
10858                 <xsl:for-each select="marc:subfield[@code='t']">
10859                         <titleInfo>
10860                                 <title>
10861                                         <xsl:call-template name="chopPunctuation">
10862                                                 <xsl:with-param name="chopString">
10863                                                         <xsl:value-of select="."></xsl:value-of>
10864                                                 </xsl:with-param>
10865                                         </xsl:call-template>
10866                                 </title>
10867                         </titleInfo>
10868                 </xsl:for-each>
10869         </xsl:template>
10870         <xsl:template name="relatedTitle76X-78X">
10871                 <xsl:for-each select="marc:subfield[@code='t']">
10872                         <titleInfo>
10873                                 <title>
10874                                         <xsl:call-template name="chopPunctuation">
10875                                                 <xsl:with-param name="chopString">
10876                                                         <xsl:value-of select="."></xsl:value-of>
10877                                                 </xsl:with-param>
10878                                         </xsl:call-template>
10879                                 </title>
10880                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10881                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10882                                 </xsl:if>
10883                         </titleInfo>
10884                 </xsl:for-each>
10885                 <xsl:for-each select="marc:subfield[@code='p']">
10886                         <titleInfo type="abbreviated">
10887                                 <title>
10888                                         <xsl:call-template name="chopPunctuation">
10889                                                 <xsl:with-param name="chopString">
10890                                                         <xsl:value-of select="."></xsl:value-of>
10891                                                 </xsl:with-param>
10892                                         </xsl:call-template>
10893                                 </title>
10894                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10895                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10896                                 </xsl:if>
10897                         </titleInfo>
10898                 </xsl:for-each>
10899                 <xsl:for-each select="marc:subfield[@code='s']">
10900                         <titleInfo type="uniform">
10901                                 <title>
10902                                         <xsl:call-template name="chopPunctuation">
10903                                                 <xsl:with-param name="chopString">
10904                                                         <xsl:value-of select="."></xsl:value-of>
10905                                                 </xsl:with-param>
10906                                         </xsl:call-template>
10907                                 </title>
10908                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10909                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10910                                 </xsl:if>
10911                         </titleInfo>
10912                 </xsl:for-each>
10913         </xsl:template>
10914         <xsl:template name="relatedOriginInfo">
10915                 <xsl:if test="marc:subfield[@code='b' or @code='d'] or marc:subfield[@code='f']">
10916                         <originInfo>
10917                                 <xsl:if test="@tag=775">
10918                                         <xsl:for-each select="marc:subfield[@code='f']">
10919                                                 <place>
10920                                                         <placeTerm>
10921                                                                 <xsl:attribute name="type">code</xsl:attribute>
10922                                                                 <xsl:attribute name="authority">marcgac</xsl:attribute>
10923                                                                 <xsl:value-of select="."></xsl:value-of>
10924                                                         </placeTerm>
10925                                                 </place>
10926                                         </xsl:for-each>
10927                                 </xsl:if>
10928                                 <xsl:for-each select="marc:subfield[@code='d']">
10929                                         <publisher>
10930                                                 <xsl:value-of select="."></xsl:value-of>
10931                                         </publisher>
10932                                 </xsl:for-each>
10933                                 <xsl:for-each select="marc:subfield[@code='b']">
10934                                         <edition>
10935                                                 <xsl:value-of select="."></xsl:value-of>
10936                                         </edition>
10937                                 </xsl:for-each>
10938                         </originInfo>
10939                 </xsl:if>
10940         </xsl:template>
10941         <xsl:template name="relatedLanguage">
10942                 <xsl:for-each select="marc:subfield[@code='e']">
10943                         <xsl:call-template name="getLanguage">
10944                                 <xsl:with-param name="langString">
10945                                         <xsl:value-of select="."></xsl:value-of>
10946                                 </xsl:with-param>
10947                         </xsl:call-template>
10948                 </xsl:for-each>
10949         </xsl:template>
10950         <xsl:template name="nameDate">
10951                 <xsl:for-each select="marc:subfield[@code='d']">
10952                         <namePart type="date">
10953                                 <xsl:call-template name="chopPunctuation">
10954                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
10955                                 </xsl:call-template>
10956                         </namePart>
10957                 </xsl:for-each>
10958         </xsl:template>
10959         <xsl:template name="subjectAuthority">
10960                 <xsl:if test="@ind2!=4">
10961                         <xsl:if test="@ind2!=' '">
10962                                 <xsl:if test="@ind2!=8">
10963                                         <xsl:if test="@ind2!=9">
10964                                                 <xsl:attribute name="authority">
10965                                                         <xsl:choose>
10966                                                                 <xsl:when test="@ind2=0">lcsh</xsl:when>
10967                                                                 <xsl:when test="@ind2=1">lcshac</xsl:when>
10968                                                                 <xsl:when test="@ind2=2">mesh</xsl:when>
10969                                                                 <!-- 1/04 fix -->
10970                                                                 <xsl:when test="@ind2=3">nal</xsl:when>
10971                                                                 <xsl:when test="@ind2=5">csh</xsl:when>
10972                                                                 <xsl:when test="@ind2=6">rvm</xsl:when>
10973                                                                 <xsl:when test="@ind2=7">
10974                                                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
10975                                                                 </xsl:when>
10976                                                         </xsl:choose>
10977                                                 </xsl:attribute>
10978                                         </xsl:if>
10979                                 </xsl:if>
10980                         </xsl:if>
10981                 </xsl:if>
10982         </xsl:template>
10983         <xsl:template name="subjectAnyOrder">
10984                 <xsl:for-each select="marc:subfield[@code='v' or @code='x' or @code='y' or @code='z']">
10985                         <xsl:choose>
10986                                 <xsl:when test="@code='v'">
10987                                         <xsl:call-template name="subjectGenre"></xsl:call-template>
10988                                 </xsl:when>
10989                                 <xsl:when test="@code='x'">
10990                                         <xsl:call-template name="subjectTopic"></xsl:call-template>
10991                                 </xsl:when>
10992                                 <xsl:when test="@code='y'">
10993                                         <xsl:call-template name="subjectTemporalY"></xsl:call-template>
10994                                 </xsl:when>
10995                                 <xsl:when test="@code='z'">
10996                                         <xsl:call-template name="subjectGeographicZ"></xsl:call-template>
10997                                 </xsl:when>
10998                         </xsl:choose>
10999                 </xsl:for-each>
11000         </xsl:template>
11001         <xsl:template name="specialSubfieldSelect">
11002                 <xsl:param name="anyCodes"></xsl:param>
11003                 <xsl:param name="axis"></xsl:param>
11004                 <xsl:param name="beforeCodes"></xsl:param>
11005                 <xsl:param name="afterCodes"></xsl:param>
11006                 <xsl:variable name="str">
11007                         <xsl:for-each select="marc:subfield">
11008                                 <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])">
11009                                         <xsl:value-of select="text()"></xsl:value-of>
11010                                         <xsl:text> </xsl:text>
11011                                 </xsl:if>
11012                         </xsl:for-each>
11013                 </xsl:variable>
11014                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
11015         </xsl:template>
11016         
11017         <!-- 3.2 change tmee 6xx $v genre -->
11018         <xsl:template match="marc:datafield[@tag=600]">
11019                 <subject>
11020                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11021                         <name type="personal">
11022                                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
11023                                 <namePart>
11024                                         <xsl:call-template name="chopPunctuation">
11025                                                 <xsl:with-param name="chopString">
11026                                                         <xsl:call-template name="subfieldSelect">
11027                                                                 <xsl:with-param name="codes">aq</xsl:with-param>
11028                                                         </xsl:call-template>
11029                                                 </xsl:with-param>
11030                                         </xsl:call-template>
11031                                 </namePart>
11032                                 <xsl:call-template name="nameDate"></xsl:call-template>
11033                                 <xsl:call-template name="affiliation"></xsl:call-template>
11034                                 <xsl:call-template name="role"></xsl:call-template>
11035                         </name>
11036                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11037                 </subject>
11038         </xsl:template>
11039         <xsl:template match="marc:datafield[@tag=610]">
11040                 <subject>
11041                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11042                         <name type="corporate">
11043                                 <xsl:for-each select="marc:subfield[@code='a']">
11044                                         <namePart>
11045                                                 <xsl:value-of select="."></xsl:value-of>
11046                                         </namePart>
11047                                 </xsl:for-each>
11048                                 <xsl:for-each select="marc:subfield[@code='b']">
11049                                         <namePart>
11050                                                 <xsl:value-of select="."></xsl:value-of>
11051                                         </namePart>
11052                                 </xsl:for-each>
11053                                 <xsl:if test="marc:subfield[@code='c' or @code='d' or @code='n' or @code='p']">
11054                                         <namePart>
11055                                                 <xsl:call-template name="subfieldSelect">
11056                                                         <xsl:with-param name="codes">cdnp</xsl:with-param>
11057                                                 </xsl:call-template>
11058                                         </namePart>
11059                                 </xsl:if>
11060                                 <xsl:call-template name="role"></xsl:call-template>
11061                         </name>
11062                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11063                 </subject>
11064         </xsl:template>
11065         <xsl:template match="marc:datafield[@tag=611]">
11066                 <subject>
11067                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11068                         <name type="conference">
11069                                 <namePart>
11070                                         <xsl:call-template name="subfieldSelect">
11071                                                 <xsl:with-param name="codes">abcdeqnp</xsl:with-param>
11072                                         </xsl:call-template>
11073                                 </namePart>
11074                                 <xsl:for-each select="marc:subfield[@code='4']">
11075                                         <role>
11076                                                 <roleTerm authority="marcrelator" type="code">
11077                                                         <xsl:value-of select="."></xsl:value-of>
11078                                                 </roleTerm>
11079                                         </role>
11080                                 </xsl:for-each>
11081                         </name>
11082                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11083                 </subject>
11084         </xsl:template>
11085         <xsl:template match="marc:datafield[@tag=630]">
11086                 <subject>
11087                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11088                         <titleInfo>
11089                                 <title>
11090                                         <xsl:call-template name="chopPunctuation">
11091                                                 <xsl:with-param name="chopString">
11092                                                         <xsl:call-template name="subfieldSelect">
11093                                                                 <xsl:with-param name="codes">adfhklor</xsl:with-param>
11094                                                         </xsl:call-template>
11095                                                 </xsl:with-param>
11096                                         </xsl:call-template>
11097                                         <xsl:call-template name="part"></xsl:call-template>
11098                                 </title>
11099                         </titleInfo>
11100                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11101                 </subject>
11102         </xsl:template>
11103         <xsl:template match="marc:datafield[@tag=650]">
11104                 <subject>
11105                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11106                         <topic>
11107                                 <xsl:call-template name="chopPunctuation">
11108                                         <xsl:with-param name="chopString">
11109                                                 <xsl:call-template name="subfieldSelect">
11110                                                         <xsl:with-param name="codes">abcd</xsl:with-param>
11111                                                 </xsl:call-template>
11112                                         </xsl:with-param>
11113                                 </xsl:call-template>
11114                         </topic>
11115                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11116                 </subject>
11117         </xsl:template>
11118         <xsl:template match="marc:datafield[@tag=651]">
11119                 <subject>
11120                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11121                         <xsl:for-each select="marc:subfield[@code='a']">
11122                                 <geographic>
11123                                         <xsl:call-template name="chopPunctuation">
11124                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
11125                                         </xsl:call-template>
11126                                 </geographic>
11127                         </xsl:for-each>
11128                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11129                 </subject>
11130         </xsl:template>
11131         <xsl:template match="marc:datafield[@tag=653]">
11132                 <subject>
11133                         <xsl:for-each select="marc:subfield[@code='a']">
11134                                 <topic>
11135                                         <xsl:value-of select="."></xsl:value-of>
11136                                 </topic>
11137                         </xsl:for-each>
11138                 </subject>
11139         </xsl:template>
11140         <xsl:template match="marc:datafield[@tag=656]">
11141                 <subject>
11142                         <xsl:if test="marc:subfield[@code=2]">
11143                                 <xsl:attribute name="authority">
11144                                         <xsl:value-of select="marc:subfield[@code=2]"></xsl:value-of>
11145                                 </xsl:attribute>
11146                         </xsl:if>
11147                         <occupation>
11148                                 <xsl:call-template name="chopPunctuation">
11149                                         <xsl:with-param name="chopString">
11150                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
11151                                         </xsl:with-param>
11152                                 </xsl:call-template>
11153                         </occupation>
11154                 </subject>
11155         </xsl:template>
11156         <xsl:template name="termsOfAddress">
11157                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
11158                         <namePart type="termsOfAddress">
11159                                 <xsl:call-template name="chopPunctuation">
11160                                         <xsl:with-param name="chopString">
11161                                                 <xsl:call-template name="subfieldSelect">
11162                                                         <xsl:with-param name="codes">bc</xsl:with-param>
11163                                                 </xsl:call-template>
11164                                         </xsl:with-param>
11165                                 </xsl:call-template>
11166                         </namePart>
11167                 </xsl:if>
11168         </xsl:template>
11169         <xsl:template name="displayLabel">
11170                 <xsl:if test="marc:subfield[@code='i']">
11171                         <xsl:attribute name="displayLabel">
11172                                 <xsl:value-of select="marc:subfield[@code='i']"></xsl:value-of>
11173                         </xsl:attribute>
11174                 </xsl:if>
11175                 <xsl:if test="marc:subfield[@code='3']">
11176                         <xsl:attribute name="displayLabel">
11177                                 <xsl:value-of select="marc:subfield[@code='3']"></xsl:value-of>
11178                         </xsl:attribute>
11179                 </xsl:if>
11180         </xsl:template>
11181         <xsl:template name="isInvalid">
11182                 <xsl:param name="type"/>
11183                 <xsl:if test="marc:subfield[@code='z'] or marc:subfield[@code='y']">
11184                         <identifier>
11185                                 <xsl:attribute name="type">
11186                                         <xsl:value-of select="$type"/>
11187                                 </xsl:attribute>
11188                                 <xsl:attribute name="invalid">
11189                                         <xsl:text>yes</xsl:text>
11190                                 </xsl:attribute>
11191                                 <xsl:if test="marc:subfield[@code='z']">
11192                                         <xsl:value-of select="marc:subfield[@code='z']"/>
11193                                 </xsl:if>
11194                                 <xsl:if test="marc:subfield[@code='y']">
11195                                         <xsl:value-of select="marc:subfield[@code='y']"/>
11196                                 </xsl:if>
11197                         </identifier>
11198                 </xsl:if>
11199         </xsl:template>
11200         <xsl:template name="subtitle">
11201                 <xsl:if test="marc:subfield[@code='b']">
11202                         <subTitle>
11203                                 <xsl:call-template name="chopPunctuation">
11204                                         <xsl:with-param name="chopString">
11205                                                 <xsl:value-of select="marc:subfield[@code='b']"/>
11206                                                 <!--<xsl:call-template name="subfieldSelect">
11207                                                         <xsl:with-param name="codes">b</xsl:with-param>                                                                 
11208                                                 </xsl:call-template>-->
11209                                         </xsl:with-param>
11210                                 </xsl:call-template>
11211                         </subTitle>
11212                 </xsl:if>
11213         </xsl:template>
11214         <xsl:template name="script">
11215                 <xsl:param name="scriptCode"></xsl:param>
11216                 <xsl:attribute name="script">
11217                         <xsl:choose>
11218                                 <xsl:when test="$scriptCode='(3'">Arabic</xsl:when>
11219                                 <xsl:when test="$scriptCode='(B'">Latin</xsl:when>
11220                                 <xsl:when test="$scriptCode='$1'">Chinese, Japanese, Korean</xsl:when>
11221                                 <xsl:when test="$scriptCode='(N'">Cyrillic</xsl:when>
11222                                 <xsl:when test="$scriptCode='(2'">Hebrew</xsl:when>
11223                                 <xsl:when test="$scriptCode='(S'">Greek</xsl:when>
11224                         </xsl:choose>
11225                 </xsl:attribute>
11226         </xsl:template>
11227         <xsl:template name="parsePart">
11228                 <!-- assumes 773$q= 1:2:3<4
11229                      with up to 3 levels and one optional start page
11230                 -->
11231                 <xsl:variable name="level1">
11232                         <xsl:choose>
11233                                 <xsl:when test="contains(text(),':')">
11234                                         <!-- 1:2 -->
11235                                         <xsl:value-of select="substring-before(text(),':')"></xsl:value-of>
11236                                 </xsl:when>
11237                                 <xsl:when test="not(contains(text(),':'))">
11238                                         <!-- 1 or 1<3 -->
11239                                         <xsl:if test="contains(text(),'&lt;')">
11240                                                 <!-- 1<3 -->
11241                                                 <xsl:value-of select="substring-before(text(),'&lt;')"></xsl:value-of>
11242                                         </xsl:if>
11243                                         <xsl:if test="not(contains(text(),'&lt;'))">
11244                                                 <!-- 1 -->
11245                                                 <xsl:value-of select="text()"></xsl:value-of>
11246                                         </xsl:if>
11247                                 </xsl:when>
11248                         </xsl:choose>
11249                 </xsl:variable>
11250                 <xsl:variable name="sici2">
11251                         <xsl:choose>
11252                                 <xsl:when test="starts-with(substring-after(text(),$level1),':')">
11253                                         <xsl:value-of select="substring(substring-after(text(),$level1),2)"></xsl:value-of>
11254                                 </xsl:when>
11255                                 <xsl:otherwise>
11256                                         <xsl:value-of select="substring-after(text(),$level1)"></xsl:value-of>
11257                                 </xsl:otherwise>
11258                         </xsl:choose>
11259                 </xsl:variable>
11260                 <xsl:variable name="level2">
11261                         <xsl:choose>
11262                                 <xsl:when test="contains($sici2,':')">
11263                                         <!--  2:3<4  -->
11264                                         <xsl:value-of select="substring-before($sici2,':')"></xsl:value-of>
11265                                 </xsl:when>
11266                                 <xsl:when test="contains($sici2,'&lt;')">
11267                                         <!-- 1: 2<4 -->
11268                                         <xsl:value-of select="substring-before($sici2,'&lt;')"></xsl:value-of>
11269                                 </xsl:when>
11270                                 <xsl:otherwise>
11271                                         <xsl:value-of select="$sici2"></xsl:value-of>
11272                                         <!-- 1:2 -->
11273                                 </xsl:otherwise>
11274                         </xsl:choose>
11275                 </xsl:variable>
11276                 <xsl:variable name="sici3">
11277                         <xsl:choose>
11278                                 <xsl:when test="starts-with(substring-after($sici2,$level2),':')">
11279                                         <xsl:value-of select="substring(substring-after($sici2,$level2),2)"></xsl:value-of>
11280                                 </xsl:when>
11281                                 <xsl:otherwise>
11282                                         <xsl:value-of select="substring-after($sici2,$level2)"></xsl:value-of>
11283                                 </xsl:otherwise>
11284                         </xsl:choose>
11285                 </xsl:variable>
11286                 <xsl:variable name="level3">
11287                         <xsl:choose>
11288                                 <xsl:when test="contains($sici3,'&lt;')">
11289                                         <!-- 2<4 -->
11290                                         <xsl:value-of select="substring-before($sici3,'&lt;')"></xsl:value-of>
11291                                 </xsl:when>
11292                                 <xsl:otherwise>
11293                                         <xsl:value-of select="$sici3"></xsl:value-of>
11294                                         <!-- 3 -->
11295                                 </xsl:otherwise>
11296                         </xsl:choose>
11297                 </xsl:variable>
11298                 <xsl:variable name="page">
11299                         <xsl:if test="contains(text(),'&lt;')">
11300                                 <xsl:value-of select="substring-after(text(),'&lt;')"></xsl:value-of>
11301                         </xsl:if>
11302                 </xsl:variable>
11303                 <xsl:if test="$level1">
11304                         <detail level="1">
11305                                 <number>
11306                                         <xsl:value-of select="$level1"></xsl:value-of>
11307                                 </number>
11308                         </detail>
11309                 </xsl:if>
11310                 <xsl:if test="$level2">
11311                         <detail level="2">
11312                                 <number>
11313                                         <xsl:value-of select="$level2"></xsl:value-of>
11314                                 </number>
11315                         </detail>
11316                 </xsl:if>
11317                 <xsl:if test="$level3">
11318                         <detail level="3">
11319                                 <number>
11320                                         <xsl:value-of select="$level3"></xsl:value-of>
11321                                 </number>
11322                         </detail>
11323                 </xsl:if>
11324                 <xsl:if test="$page">
11325                         <extent unit="page">
11326                                 <start>
11327                                         <xsl:value-of select="$page"></xsl:value-of>
11328                                 </start>
11329                         </extent>
11330                 </xsl:if>
11331         </xsl:template>
11332         <xsl:template name="getLanguage">
11333                 <xsl:param name="langString"></xsl:param>
11334                 <xsl:param name="controlField008-35-37"></xsl:param>
11335                 <xsl:variable name="length" select="string-length($langString)"></xsl:variable>
11336                 <xsl:choose>
11337                         <xsl:when test="$length=0"></xsl:when>
11338                         <xsl:when test="$controlField008-35-37=substring($langString,1,3)">
11339                                 <xsl:call-template name="getLanguage">
11340                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
11341                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
11342                                 </xsl:call-template>
11343                         </xsl:when>
11344                         <xsl:otherwise>
11345                                 <language>
11346                                         <languageTerm authority="iso639-2b" type="code">
11347                                                 <xsl:value-of select="substring($langString,1,3)"></xsl:value-of>
11348                                         </languageTerm>
11349                                 </language>
11350                                 <xsl:call-template name="getLanguage">
11351                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
11352                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
11353                                 </xsl:call-template>
11354                         </xsl:otherwise>
11355                 </xsl:choose>
11356         </xsl:template>
11357         <xsl:template name="isoLanguage">
11358                 <xsl:param name="currentLanguage"></xsl:param>
11359                 <xsl:param name="usedLanguages"></xsl:param>
11360                 <xsl:param name="remainingLanguages"></xsl:param>
11361                 <xsl:choose>
11362                         <xsl:when test="string-length($currentLanguage)=0"></xsl:when>
11363                         <xsl:when test="not(contains($usedLanguages, $currentLanguage))">
11364                                 <language>
11365                                         <xsl:if test="@code!='a'">
11366                                                 <xsl:attribute name="objectPart">
11367                                                         <xsl:choose>
11368                                                                 <xsl:when test="@code='b'">summary or subtitle</xsl:when>
11369                                                                 <xsl:when test="@code='d'">sung or spoken text</xsl:when>
11370                                                                 <xsl:when test="@code='e'">libretto</xsl:when>
11371                                                                 <xsl:when test="@code='f'">table of contents</xsl:when>
11372                                                                 <xsl:when test="@code='g'">accompanying material</xsl:when>
11373                                                                 <xsl:when test="@code='h'">translation</xsl:when>
11374                                                         </xsl:choose>
11375                                                 </xsl:attribute>
11376                                         </xsl:if>
11377                                         <languageTerm authority="iso639-2b" type="code">
11378                                                 <xsl:value-of select="$currentLanguage"></xsl:value-of>
11379                                         </languageTerm>
11380                                 </language>
11381                                 <xsl:call-template name="isoLanguage">
11382                                         <xsl:with-param name="currentLanguage">
11383                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
11384                                         </xsl:with-param>
11385                                         <xsl:with-param name="usedLanguages">
11386                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
11387                                         </xsl:with-param>
11388                                         <xsl:with-param name="remainingLanguages">
11389                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
11390                                         </xsl:with-param>
11391                                 </xsl:call-template>
11392                         </xsl:when>
11393                         <xsl:otherwise>
11394                                 <xsl:call-template name="isoLanguage">
11395                                         <xsl:with-param name="currentLanguage">
11396                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
11397                                         </xsl:with-param>
11398                                         <xsl:with-param name="usedLanguages">
11399                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
11400                                         </xsl:with-param>
11401                                         <xsl:with-param name="remainingLanguages">
11402                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
11403                                         </xsl:with-param>
11404                                 </xsl:call-template>
11405                         </xsl:otherwise>
11406                 </xsl:choose>
11407         </xsl:template>
11408         <xsl:template name="chopBrackets">
11409                 <xsl:param name="chopString"></xsl:param>
11410                 <xsl:variable name="string">
11411                         <xsl:call-template name="chopPunctuation">
11412                                 <xsl:with-param name="chopString" select="$chopString"></xsl:with-param>
11413                         </xsl:call-template>
11414                 </xsl:variable>
11415                 <xsl:if test="substring($string, 1,1)='['">
11416                         <xsl:value-of select="substring($string,2, string-length($string)-2)"></xsl:value-of>
11417                 </xsl:if>
11418                 <xsl:if test="substring($string, 1,1)!='['">
11419                         <xsl:value-of select="$string"></xsl:value-of>
11420                 </xsl:if>
11421         </xsl:template>
11422         <xsl:template name="rfcLanguages">
11423                 <xsl:param name="nodeNum"></xsl:param>
11424                 <xsl:param name="usedLanguages"></xsl:param>
11425                 <xsl:param name="controlField008-35-37"></xsl:param>
11426                 <xsl:variable name="currentLanguage" select="."></xsl:variable>
11427                 <xsl:choose>
11428                         <xsl:when test="not($currentLanguage)"></xsl:when>
11429                         <xsl:when test="$currentLanguage!=$controlField008-35-37 and $currentLanguage!='rfc3066'">
11430                                 <xsl:if test="not(contains($usedLanguages,$currentLanguage))">
11431                                         <language>
11432                                                 <xsl:if test="@code!='a'">
11433                                                         <xsl:attribute name="objectPart">
11434                                                                 <xsl:choose>
11435                                                                         <xsl:when test="@code='b'">summary or subtitle</xsl:when>
11436                                                                         <xsl:when test="@code='d'">sung or spoken text</xsl:when>
11437                                                                         <xsl:when test="@code='e'">libretto</xsl:when>
11438                                                                         <xsl:when test="@code='f'">table of contents</xsl:when>
11439                                                                         <xsl:when test="@code='g'">accompanying material</xsl:when>
11440                                                                         <xsl:when test="@code='h'">translation</xsl:when>
11441                                                                 </xsl:choose>
11442                                                         </xsl:attribute>
11443                                                 </xsl:if>
11444                                                 <languageTerm authority="rfc3066" type="code">
11445                                                         <xsl:value-of select="$currentLanguage"/>
11446                                                 </languageTerm>
11447                                         </language>
11448                                 </xsl:if>
11449                         </xsl:when>
11450                         <xsl:otherwise>
11451                         </xsl:otherwise>
11452                 </xsl:choose>
11453         </xsl:template>
11454         <xsl:template name="datafield">
11455                 <xsl:param name="tag"/>
11456                 <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param>
11457                 <xsl:param name="ind2"><xsl:text> </xsl:text></xsl:param>
11458                 <xsl:param name="subfields"/>
11459                 <xsl:element name="marc:datafield">
11460                         <xsl:attribute name="tag">
11461                                 <xsl:value-of select="$tag"/>
11462                         </xsl:attribute>
11463                         <xsl:attribute name="ind1">
11464                                 <xsl:value-of select="$ind1"/>
11465                         </xsl:attribute>
11466                         <xsl:attribute name="ind2">
11467                                 <xsl:value-of select="$ind2"/>
11468                         </xsl:attribute>
11469                         <xsl:copy-of select="$subfields"/>
11470                 </xsl:element>
11471         </xsl:template>
11472
11473         <xsl:template name="subfieldSelect">
11474                 <xsl:param name="codes"/>
11475                 <xsl:param name="delimeter"><xsl:text> </xsl:text></xsl:param>
11476                 <xsl:variable name="str">
11477                         <xsl:for-each select="marc:subfield">
11478                                 <xsl:if test="contains($codes, @code)">
11479                                         <xsl:value-of select="text()"/><xsl:value-of select="$delimeter"/>
11480                                 </xsl:if>
11481                         </xsl:for-each>
11482                 </xsl:variable>
11483                 <xsl:value-of select="substring($str,1,string-length($str)-string-length($delimeter))"/>
11484         </xsl:template>
11485
11486         <xsl:template name="buildSpaces">
11487                 <xsl:param name="spaces"/>
11488                 <xsl:param name="char"><xsl:text> </xsl:text></xsl:param>
11489                 <xsl:if test="$spaces>0">
11490                         <xsl:value-of select="$char"/>
11491                         <xsl:call-template name="buildSpaces">
11492                                 <xsl:with-param name="spaces" select="$spaces - 1"/>
11493                                 <xsl:with-param name="char" select="$char"/>
11494                         </xsl:call-template>
11495                 </xsl:if>
11496         </xsl:template>
11497
11498         <xsl:template name="chopPunctuation">
11499                 <xsl:param name="chopString"/>
11500                 <xsl:param name="punctuation"><xsl:text>.:,;/ </xsl:text></xsl:param>
11501                 <xsl:variable name="length" select="string-length($chopString)"/>
11502                 <xsl:choose>
11503                         <xsl:when test="$length=0"/>
11504                         <xsl:when test="contains($punctuation, substring($chopString,$length,1))">
11505                                 <xsl:call-template name="chopPunctuation">
11506                                         <xsl:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
11507                                         <xsl:with-param name="punctuation" select="$punctuation"/>
11508                                 </xsl:call-template>
11509                         </xsl:when>
11510                         <xsl:when test="not($chopString)"/>
11511                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
11512                 </xsl:choose>
11513         </xsl:template>
11514
11515         <xsl:template name="chopPunctuationFront">
11516                 <xsl:param name="chopString"/>
11517                 <xsl:variable name="length" select="string-length($chopString)"/>
11518                 <xsl:choose>
11519                         <xsl:when test="$length=0"/>
11520                         <xsl:when test="contains('.:,;/[ ', substring($chopString,1,1))">
11521                                 <xsl:call-template name="chopPunctuationFront">
11522                                         <xsl:with-param name="chopString" select="substring($chopString,2,$length - 1)"/>
11523                                 </xsl:call-template>
11524                         </xsl:when>
11525                         <xsl:when test="not($chopString)"/>
11526                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
11527                 </xsl:choose>
11528         </xsl:template>
11529 </xsl:stylesheet>$$ WHERE name = 'mods32';
11530
11531 -- Currently, the only difference from naco_normalize is that search_normalize
11532 -- turns apostrophes into spaces, while naco_normalize collapses them.
11533 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
11534
11535     use strict;
11536     use Unicode::Normalize;
11537     use Encode;
11538
11539     my $str = decode_utf8(shift);
11540     my $sf = shift;
11541
11542     # Apply NACO normalization to input string; based on
11543     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
11544     #
11545     # Note that unlike a strict reading of the NACO normalization rules,
11546     # output is returned as lowercase instead of uppercase for compatibility
11547     # with previous versions of the Evergreen naco_normalize routine.
11548
11549     # Convert to upper-case first; even though final output will be lowercase, doing this will
11550     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
11551     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
11552     $str = uc $str;
11553
11554     # remove non-filing strings
11555     $str =~ s/\x{0098}.*?\x{009C}//g;
11556
11557     $str = NFKD($str);
11558
11559     # additional substitutions - 3.6.
11560     $str =~ s/\x{00C6}/AE/g;
11561     $str =~ s/\x{00DE}/TH/g;
11562     $str =~ s/\x{0152}/OE/g;
11563     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;
11564
11565     # transformations based on Unicode category codes
11566     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
11567
11568         if ($sf && $sf =~ /^a/o) {
11569                 my $commapos = index($str, ',');
11570                 if ($commapos > -1) {
11571                         if ($commapos != length($str) - 1) {
11572                 $str =~ s/,/\x07/; # preserve first comma
11573                         }
11574                 }
11575         }
11576
11577     # since we've stripped out the control characters, we can now
11578     # use a few as placeholders temporarily
11579     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
11580     $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;
11581     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
11582
11583     # decimal digits
11584     $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/;
11585
11586     # intentionally skipping step 8 of the NACO algorithm; if the string
11587     # gets normalized away, that's fine.
11588
11589     # leading and trailing spaces
11590     $str =~ s/\s+/ /g;
11591     $str =~ s/^\s+//;
11592     $str =~ s/\s+$//g;
11593
11594     return lc $str;
11595 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
11596
11597 CREATE OR REPLACE FUNCTION public.search_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
11598         SELECT public.search_normalize($1,'a');
11599 $func$ LANGUAGE SQL STRICT IMMUTABLE;
11600
11601 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT ) RETURNS TEXT AS $func$
11602         SELECT public.search_normalize($1,'');
11603 $func$ LANGUAGE 'sql' STRICT IMMUTABLE;
11604
11605 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
11606         'Search Normalize',
11607         'Apply search normalization rules to the extracted text. A less extreme version of NACO normalization.',
11608         'search_normalize',
11609         0
11610 );
11611
11612 UPDATE config.metabib_field_index_norm_map
11613     SET norm = (
11614         SELECT id FROM config.index_normalizer WHERE func = 'search_normalize'
11615     )
11616     WHERE norm = (
11617         SELECT id FROM config.index_normalizer WHERE func = 'naco_normalize'
11618     )
11619 ;
11620
11621
11622 -- This could take a long time if you have a very non-English bib database
11623 -- Run it outside of a transaction to avoid lock escalation
11624 SELECT metabib.reingest_metabib_field_entries(record)
11625     FROM metabib.full_rec
11626     WHERE tag = '245'
11627     AND subfield = 'a'
11628     AND value LIKE '%''%'
11629 ;
11630 -- Evergreen DB patch 0673.data.acq-cancel-reason-cleanup.sql
11631 --
11632
11633 -- check whether patch can be applied
11634 SELECT evergreen.upgrade_deps_block_check('0673', :eg_version);
11635
11636 DELETE FROM
11637     acq.cancel_reason
11638 WHERE
11639     -- any entries with id >= 2000 were added locally.  
11640     id < 2000 
11641
11642     -- these cancel_reason's are actively used by the system
11643     AND id NOT IN (1, 2, 3, 1002, 1003, 1004, 1005, 1010, 1024, 1211, 1221, 1246, 1283)
11644
11645     -- don't delete any cancel_reason's that may be in use locally
11646     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.user_request WHERE cancel_reason IS NOT NULL)
11647     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.purchase_order WHERE cancel_reason IS NOT NULL)
11648     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem WHERE cancel_reason IS NOT NULL)
11649     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem_detail WHERE cancel_reason IS NOT NULL)
11650     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_lineitem_history WHERE cancel_reason IS NOT NULL)
11651     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_purchase_order_history WHERE cancel_reason IS NOT NULL);
11652
11653
11654 SELECT evergreen.upgrade_deps_block_check('0674', :eg_version);
11655
11656 ALTER TABLE config.copy_status
11657           ADD COLUMN restrict_copy_delete BOOL NOT NULL DEFAULT FALSE;
11658
11659 UPDATE config.copy_status
11660 SET restrict_copy_delete = TRUE
11661 WHERE id IN (1,3,6,8);
11662
11663 INSERT INTO permission.perm_list (id, code, description) VALUES (
11664     520,
11665     'COPY_DELETE_WARNING.override',
11666     'Allow a user to override warnings about deleting copies in problematic situations.'
11667 );
11668
11669
11670 SELECT evergreen.upgrade_deps_block_check('0675', :eg_version);
11671
11672 -- set expected row count to low value to avoid problem
11673 -- where use of this function by the circ tagging feature
11674 -- results in full scans of asset.call_number
11675 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
11676     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
11677 $$ LANGUAGE SQL ROWS 10;
11678
11679
11680 SELECT evergreen.upgrade_deps_block_check('0676', :eg_version);
11681
11682 INSERT INTO config.global_flag (name, label, enabled, value) VALUES (
11683     'opac.use_autosuggest',
11684     '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)',
11685     TRUE,
11686     'opac_visible'
11687 );
11688
11689 CREATE TABLE metabib.browse_entry (
11690     id BIGSERIAL PRIMARY KEY,
11691     value TEXT unique,
11692     index_vector tsvector
11693 );
11694 --Skip this, will be created differently later
11695 --CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIST (index_vector);
11696 CREATE TRIGGER metabib_browse_entry_fti_trigger
11697     BEFORE INSERT OR UPDATE ON metabib.browse_entry
11698     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
11699
11700
11701 CREATE TABLE metabib.browse_entry_def_map (
11702     id BIGSERIAL PRIMARY KEY,
11703     entry BIGINT REFERENCES metabib.browse_entry (id),
11704     def INT REFERENCES config.metabib_field (id),
11705     source BIGINT REFERENCES biblio.record_entry (id)
11706 );
11707
11708 ALTER TABLE config.metabib_field ADD COLUMN browse_field BOOLEAN DEFAULT TRUE NOT NULL;
11709 ALTER TABLE config.metabib_field ADD COLUMN browse_xpath TEXT;
11710
11711 ALTER TABLE config.metabib_class ADD COLUMN bouyant BOOLEAN DEFAULT FALSE NOT NULL;
11712 ALTER TABLE config.metabib_class ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
11713 ALTER TABLE config.metabib_field ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
11714
11715 -- one good exception to default true:
11716 UPDATE config.metabib_field
11717     SET browse_field = FALSE
11718     WHERE (field_class = 'keyword' AND name = 'keyword') OR
11719         (field_class = 'subject' AND name = 'complete');
11720
11721 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11722 -- We're only touching it here to add a DELETE statement to the IF NEW.deleted
11723 -- block.
11724
11725 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11726 DECLARE
11727     transformed_xml TEXT;
11728     prev_xfrm       TEXT;
11729     normalizer      RECORD;
11730     xfrm            config.xml_transform%ROWTYPE;
11731     attr_value      TEXT;
11732     new_attrs       HSTORE := ''::HSTORE;
11733     attr_def        config.record_attr_definition%ROWTYPE;
11734 BEGIN
11735
11736     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11737         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11738         DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
11739         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11740         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
11741         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
11742         RETURN NEW; -- and we're done
11743     END IF;
11744
11745     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11746         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11747
11748         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11749             RETURN NEW;
11750         END IF;
11751     END IF;
11752
11753     -- Record authority linking
11754     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11755     IF NOT FOUND THEN
11756         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11757     END IF;
11758
11759     -- Flatten and insert the mfr data
11760     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11761     IF NOT FOUND THEN
11762         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11763
11764         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
11765         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11766         IF NOT FOUND THEN
11767             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
11768
11769                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
11770                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
11771                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
11772                       WHERE record = NEW.id
11773                             AND tag LIKE attr_def.tag
11774                             AND CASE
11775                                 WHEN attr_def.sf_list IS NOT NULL 
11776                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
11777                                 ELSE TRUE
11778                                 END
11779                       GROUP BY tag
11780                       ORDER BY tag
11781                       LIMIT 1;
11782
11783                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
11784                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
11785
11786                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
11787
11788                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
11789             
11790                     -- See if we can skip the XSLT ... it's expensive
11791                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
11792                         -- Can't skip the transform
11793                         IF xfrm.xslt <> '---' THEN
11794                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
11795                         ELSE
11796                             transformed_xml := NEW.marc;
11797                         END IF;
11798             
11799                         prev_xfrm := xfrm.name;
11800                     END IF;
11801
11802                     IF xfrm.name IS NULL THEN
11803                         -- just grab the marcxml (empty) transform
11804                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
11805                         prev_xfrm := xfrm.name;
11806                     END IF;
11807
11808                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
11809
11810                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
11811                     SELECT  m.value INTO attr_value
11812                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
11813                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
11814                       WHERE v.subfield = attr_def.phys_char_sf
11815                       LIMIT 1; -- Just in case ...
11816
11817                 END IF;
11818
11819                 -- apply index normalizers to attr_value
11820                 FOR normalizer IN
11821                     SELECT  n.func AS func,
11822                             n.param_count AS param_count,
11823                             m.params AS params
11824                       FROM  config.index_normalizer n
11825                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
11826                       WHERE attr = attr_def.name
11827                       ORDER BY m.pos LOOP
11828                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
11829                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
11830                             CASE
11831                                 WHEN normalizer.param_count > 0
11832                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
11833                                     ELSE ''
11834                                 END ||
11835                             ')' INTO attr_value;
11836         
11837                 END LOOP;
11838
11839                 -- Add the new value to the hstore
11840                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
11841
11842             END LOOP;
11843
11844             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
11845                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
11846             ELSE
11847                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
11848             END IF;
11849
11850         END IF;
11851     END IF;
11852
11853     -- Gather and insert the field entry data
11854     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11855
11856     -- Located URI magic
11857     IF TG_OP = 'INSERT' THEN
11858         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11859         IF NOT FOUND THEN
11860             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11861         END IF;
11862     ELSE
11863         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11864         IF NOT FOUND THEN
11865             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11866         END IF;
11867     END IF;
11868
11869     -- (re)map metarecord-bib linking
11870     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11871         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11872         IF NOT FOUND THEN
11873             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11874         END IF;
11875     ELSE -- we're doing an update, and we're not deleted, remap
11876         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11877         IF NOT FOUND THEN
11878             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11879         END IF;
11880     END IF;
11881
11882     RETURN NEW;
11883 END;
11884 $func$ LANGUAGE PLPGSQL;
11885
11886 CREATE OR REPLACE FUNCTION metabib.browse_normalize(facet_text TEXT, mapped_field INT) RETURNS TEXT AS $$
11887 DECLARE
11888     normalizer  RECORD;
11889 BEGIN
11890
11891     FOR normalizer IN
11892         SELECT  n.func AS func,
11893                 n.param_count AS param_count,
11894                 m.params AS params
11895           FROM  config.index_normalizer n
11896                 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
11897           WHERE m.field = mapped_field AND m.pos < 0
11898           ORDER BY m.pos LOOP
11899
11900             EXECUTE 'SELECT ' || normalizer.func || '(' ||
11901                 quote_literal( facet_text ) ||
11902                 CASE
11903                     WHEN normalizer.param_count > 0
11904                         THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
11905                         ELSE ''
11906                     END ||
11907                 ')' INTO facet_text;
11908
11909     END LOOP;
11910
11911     RETURN facet_text;
11912 END;
11913
11914 $$ LANGUAGE PLPGSQL;
11915
11916 DROP FUNCTION biblio.extract_metabib_field_entry(bigint, text);
11917 DROP FUNCTION biblio.extract_metabib_field_entry(bigint);
11918
11919 DROP TYPE metabib.field_entry_template;
11920 CREATE TYPE metabib.field_entry_template AS (
11921         field_class     TEXT,
11922         field           INT,
11923         facet_field     BOOL,
11924         search_field    BOOL,
11925         browse_field   BOOL,
11926         source          BIGINT,
11927         value           TEXT
11928 );
11929
11930
11931 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
11932 DECLARE
11933     bib     biblio.record_entry%ROWTYPE;
11934     idx     config.metabib_field%ROWTYPE;
11935     xfrm        config.xml_transform%ROWTYPE;
11936     prev_xfrm   TEXT;
11937     transformed_xml TEXT;
11938     xml_node    TEXT;
11939     xml_node_list   TEXT[];
11940     facet_text  TEXT;
11941     browse_text TEXT;
11942     raw_text    TEXT;
11943     curr_text   TEXT;
11944     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
11945     output_row  metabib.field_entry_template%ROWTYPE;
11946 BEGIN
11947
11948     -- Get the record
11949     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
11950
11951     -- Loop over the indexing entries
11952     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
11953
11954         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
11955
11956         -- See if we can skip the XSLT ... it's expensive
11957         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
11958             -- Can't skip the transform
11959             IF xfrm.xslt <> '---' THEN
11960                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
11961             ELSE
11962                 transformed_xml := bib.marc;
11963             END IF;
11964
11965             prev_xfrm := xfrm.name;
11966         END IF;
11967
11968         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
11969
11970         raw_text := NULL;
11971         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
11972             CONTINUE WHEN xml_node !~ E'^\\s*<';
11973
11974             curr_text := ARRAY_TO_STRING(
11975                 oils_xpath( '//text()',
11976                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
11977                         REGEXP_REPLACE( -- This escapes embeded <s
11978                             xml_node,
11979                             $re$(>[^<]+)(<)([^>]+<)$re$,
11980                             E'\\1&lt;\\3',
11981                             'g'
11982                         ),
11983                         '&(?!amp;)',
11984                         '&amp;',
11985                         'g'
11986                     )
11987                 ),
11988                 ' '
11989             );
11990
11991             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
11992
11993             IF raw_text IS NOT NULL THEN
11994                 raw_text := raw_text || joiner;
11995             END IF;
11996
11997             raw_text := COALESCE(raw_text,'') || curr_text;
11998
11999             -- autosuggest/metabib.browse_entry
12000             IF idx.browse_field THEN
12001
12002                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
12003                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
12004                 ELSE
12005                     browse_text := curr_text;
12006                 END IF;
12007
12008                 output_row.field_class = idx.field_class;
12009                 output_row.field = idx.id;
12010                 output_row.source = rid;
12011                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
12012
12013                 output_row.browse_field = TRUE;
12014                 RETURN NEXT output_row;
12015                 output_row.browse_field = FALSE;
12016             END IF;
12017
12018             -- insert raw node text for faceting
12019             IF idx.facet_field THEN
12020
12021                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
12022                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
12023                 ELSE
12024                     facet_text := curr_text;
12025                 END IF;
12026
12027                 output_row.field_class = idx.field_class;
12028                 output_row.field = -1 * idx.id;
12029                 output_row.source = rid;
12030                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
12031
12032                 output_row.facet_field = TRUE;
12033                 RETURN NEXT output_row;
12034                 output_row.facet_field = FALSE;
12035             END IF;
12036
12037         END LOOP;
12038
12039         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
12040
12041         -- insert combined node text for searching
12042         IF idx.search_field THEN
12043             output_row.field_class = idx.field_class;
12044             output_row.field = idx.id;
12045             output_row.source = rid;
12046             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
12047
12048             output_row.search_field = TRUE;
12049             RETURN NEXT output_row;
12050         END IF;
12051
12052     END LOOP;
12053
12054 END;
12055 $func$ LANGUAGE PLPGSQL;
12056
12057 -- default to a space joiner
12058 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
12059     SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
12060     $func$ LANGUAGE SQL;
12061
12062
12063 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
12064 DECLARE
12065     fclass          RECORD;
12066     ind_data        metabib.field_entry_template%ROWTYPE;
12067     mbe_row         metabib.browse_entry%ROWTYPE;
12068     mbe_id          BIGINT;
12069 BEGIN
12070     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
12071     IF NOT FOUND THEN
12072         FOR fclass IN SELECT * FROM config.metabib_class LOOP
12073             -- RAISE NOTICE 'Emptying out %', fclass.name;
12074             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
12075         END LOOP;
12076         DELETE FROM metabib.facet_entry WHERE source = bib_id;
12077         DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
12078     END IF;
12079
12080     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
12081         IF ind_data.field < 0 THEN
12082             ind_data.field = -1 * ind_data.field;
12083         END IF;
12084
12085         IF ind_data.facet_field THEN
12086             INSERT INTO metabib.facet_entry (field, source, value)
12087                 VALUES (ind_data.field, ind_data.source, ind_data.value);
12088         END IF;
12089
12090         IF ind_data.browse_field THEN
12091             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
12092             IF FOUND THEN
12093                 mbe_id := mbe_row.id;
12094             ELSE
12095                 INSERT INTO metabib.browse_entry (value) VALUES
12096                     (metabib.browse_normalize(ind_data.value, ind_data.field));
12097                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
12098             END IF;
12099
12100             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
12101                 VALUES (mbe_id, ind_data.field, ind_data.source);
12102         END IF;
12103
12104         IF ind_data.search_field THEN
12105             EXECUTE $$
12106                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
12107                     VALUES ($$ ||
12108                         quote_literal(ind_data.field) || $$, $$ ||
12109                         quote_literal(ind_data.source) || $$, $$ ||
12110                         quote_literal(ind_data.value) ||
12111                     $$);$$;
12112         END IF;
12113
12114     END LOOP;
12115
12116     RETURN;
12117 END;
12118 $func$ LANGUAGE PLPGSQL;
12119
12120 -- This mimics a specific part of QueryParser, turning the first part of a
12121 -- classed search (search_class) into a set of classes and possibly fields.
12122 -- search_class might look like "author" or "title|proper" or "ti|uniform"
12123 -- or "au" or "au|corporate|personal" or anything like that, where the first
12124 -- element of the list you get by separating on the "|" character is either
12125 -- a registered class (config.metabib_class) or an alias
12126 -- (config.metabib_search_alias), and the rest of any such elements are
12127 -- fields (config.metabib_field).
12128 CREATE OR REPLACE
12129     FUNCTION metabib.search_class_to_registered_components(search_class TEXT)
12130     RETURNS SETOF RECORD AS $func$
12131 DECLARE
12132     search_parts        TEXT[];
12133     field_name          TEXT;
12134     search_part_count   INTEGER;
12135     rec                 RECORD;
12136     registered_class    config.metabib_class%ROWTYPE;
12137     registered_alias    config.metabib_search_alias%ROWTYPE;
12138     registered_field    config.metabib_field%ROWTYPE;
12139 BEGIN
12140     search_parts := REGEXP_SPLIT_TO_ARRAY(search_class, E'\\|');
12141
12142     search_part_count := ARRAY_LENGTH(search_parts, 1);
12143     IF search_part_count = 0 THEN
12144         RETURN;
12145     ELSE
12146         SELECT INTO registered_class
12147             * FROM config.metabib_class WHERE name = search_parts[1];
12148         IF FOUND THEN
12149             IF search_part_count < 2 THEN   -- all fields
12150                 rec := (registered_class.name, NULL::INTEGER);
12151                 RETURN NEXT rec;
12152                 RETURN; -- done
12153             END IF;
12154             FOR field_name IN SELECT *
12155                 FROM UNNEST(search_parts[2:search_part_count]) LOOP
12156                 SELECT INTO registered_field
12157                     * FROM config.metabib_field
12158                     WHERE name = field_name AND
12159                         field_class = registered_class.name;
12160                 IF FOUND THEN
12161                     rec := (registered_class.name, registered_field.id);
12162                     RETURN NEXT rec;
12163                 END IF;
12164             END LOOP;
12165         ELSE
12166             -- maybe we have an alias?
12167             SELECT INTO registered_alias
12168                 * FROM config.metabib_search_alias WHERE alias=search_parts[1];
12169             IF NOT FOUND THEN
12170                 RETURN;
12171             ELSE
12172                 IF search_part_count < 2 THEN   -- return w/e the alias says
12173                     rec := (
12174                         registered_alias.field_class, registered_alias.field
12175                     );
12176                     RETURN NEXT rec;
12177                     RETURN; -- done
12178                 ELSE
12179                     FOR field_name IN SELECT *
12180                         FROM UNNEST(search_parts[2:search_part_count]) LOOP
12181                         SELECT INTO registered_field
12182                             * FROM config.metabib_field
12183                             WHERE name = field_name AND
12184                                 field_class = registered_alias.field_class;
12185                         IF FOUND THEN
12186                             rec := (
12187                                 registered_alias.field_class,
12188                                 registered_field.id
12189                             );
12190                             RETURN NEXT rec;
12191                         END IF;
12192                     END LOOP;
12193                 END IF;
12194             END IF;
12195         END IF;
12196     END IF;
12197 END;
12198 $func$ LANGUAGE PLPGSQL;
12199
12200
12201 CREATE OR REPLACE
12202     FUNCTION metabib.suggest_browse_entries(
12203         query_text      TEXT,   -- 'foo' or 'foo & ba:*',ready for to_tsquery()
12204         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
12205         headline_opts   TEXT,   -- markup options for ts_headline()
12206         visibility_org  INTEGER,-- null if you don't want opac visibility test
12207         query_limit     INTEGER,-- use in LIMIT clause of interal query
12208         normalization   INTEGER -- argument to TS_RANK_CD()
12209     ) RETURNS TABLE (
12210         value                   TEXT,   -- plain
12211         field                   INTEGER,
12212         bouyant_and_class_match BOOL,
12213         field_match             BOOL,
12214         field_weight            INTEGER,
12215         rank                    REAL,
12216         bouyant                 BOOL,
12217         match                   TEXT    -- marked up
12218     ) AS $func$
12219 DECLARE
12220     query                   TSQUERY;
12221     opac_visibility_join    TEXT;
12222     search_class_join       TEXT;
12223     r_fields                RECORD;
12224 BEGIN
12225     query := TO_TSQUERY('keyword', query_text);
12226
12227     IF visibility_org IS NOT NULL THEN
12228         opac_visibility_join := '
12229     JOIN asset.opac_visible_copies aovc ON (
12230         aovc.record = mbedm.source AND
12231         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
12232     )';
12233     ELSE
12234         opac_visibility_join := '';
12235     END IF;
12236
12237     -- The following determines whether we only provide suggestsons matching
12238     -- the user's selected search_class, or whether we show other suggestions
12239     -- too. The reason for MIN() is that for search_classes like
12240     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
12241     -- implication is that if title as a class doesn't have restrict,
12242     -- nor does the proper field, but the uniform field does, you're going
12243     -- to get 'false' for your overall evaluation of 'should we restrict?'
12244     -- To invert that, change from MIN() to MAX().
12245
12246     SELECT
12247         INTO r_fields
12248             MIN(cmc.restrict::INT) AS restrict_class,
12249             MIN(cmf.restrict::INT) AS restrict_field
12250         FROM metabib.search_class_to_registered_components(search_class)
12251             AS _registered (field_class TEXT, field INT)
12252         JOIN
12253             config.metabib_class cmc ON (cmc.name = _registered.field_class)
12254         LEFT JOIN
12255             config.metabib_field cmf ON (cmf.id = _registered.field);
12256
12257     -- evaluate 'should we restrict?'
12258     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
12259         search_class_join := '
12260     JOIN
12261         metabib.search_class_to_registered_components($2)
12262         AS _registered (field_class TEXT, field INT) ON (
12263             (_registered.field IS NULL AND
12264                 _registered.field_class = cmf.field_class) OR
12265             (_registered.field = cmf.id)
12266         )
12267     ';
12268     ELSE
12269         search_class_join := '
12270     LEFT JOIN
12271         metabib.search_class_to_registered_components($2)
12272         AS _registered (field_class TEXT, field INT) ON (
12273             _registered.field_class = cmc.name
12274         )
12275     ';
12276     END IF;
12277
12278     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $1, $3) FROM (SELECT DISTINCT
12279         mbe.value,
12280         cmf.id,
12281         cmc.bouyant AND _registered.field_class IS NOT NULL,
12282         _registered.field = cmf.id,
12283         cmf.weight,
12284         TS_RANK_CD(mbe.index_vector, $1, $6),
12285         cmc.bouyant
12286     FROM metabib.browse_entry_def_map mbedm
12287     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
12288     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
12289     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
12290     '  || search_class_join || opac_visibility_join ||
12291     ' WHERE $1 @@ mbe.index_vector
12292     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12293     LIMIT $5) x
12294     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12295     '   -- sic, repeat the order by clause in the outer select too
12296     USING
12297         query, search_class, headline_opts,
12298         visibility_org, query_limit, normalization
12299         ;
12300
12301     -- sort order:
12302     --  bouyant AND chosen class = match class
12303     --  chosen field = match field
12304     --  field weight
12305     --  rank
12306     --  bouyancy
12307     --  value itself
12308
12309 END;
12310 $func$ LANGUAGE PLPGSQL;
12311
12312 -- The advantage of this over the stock regexp_split_to_array() is that it
12313 -- won't degrade unicode strings.
12314 CREATE OR REPLACE FUNCTION evergreen.regexp_split_to_array(TEXT, TEXT)
12315 RETURNS TEXT[] AS $$
12316     return encode_array_literal([split $_[1], $_[0]]);
12317 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
12318
12319
12320 -- Adds some logic for browse_entry to split on non-word chars for index_vector, post-normalize
12321 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
12322 DECLARE
12323     normalizer      RECORD;
12324     value           TEXT := '';
12325 BEGIN
12326
12327     value := NEW.value;
12328
12329     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12330         FOR normalizer IN
12331             SELECT  n.func AS func,
12332                     n.param_count AS param_count,
12333                     m.params AS params
12334               FROM  config.index_normalizer n
12335                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12336               WHERE field = NEW.field AND m.pos < 0
12337               ORDER BY m.pos LOOP
12338                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12339                     quote_literal( value ) ||
12340                     CASE
12341                         WHEN normalizer.param_count > 0
12342                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12343                             ELSE ''
12344                         END ||
12345                     ')' INTO value;
12346
12347         END LOOP;
12348
12349         NEW.value := value;
12350     END IF;
12351
12352     IF NEW.index_vector = ''::tsvector THEN
12353         RETURN NEW;
12354     END IF;
12355
12356     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12357         FOR normalizer IN
12358             SELECT  n.func AS func,
12359                     n.param_count AS param_count,
12360                     m.params AS params
12361               FROM  config.index_normalizer n
12362                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12363               WHERE field = NEW.field AND m.pos >= 0
12364               ORDER BY m.pos LOOP
12365                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12366                     quote_literal( value ) ||
12367                     CASE
12368                         WHEN normalizer.param_count > 0
12369                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12370                             ELSE ''
12371                         END ||
12372                     ')' INTO value;
12373
12374         END LOOP;
12375     END IF;
12376
12377     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
12378         value :=  ARRAY_TO_STRING(
12379             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12380         );
12381     END IF;
12382
12383     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
12384
12385     RETURN NEW;
12386 END;
12387 $$ LANGUAGE PLPGSQL;
12388
12389 -- Evergreen DB patch 0677.schema.circ_limits.sql
12390 --
12391 -- FIXME: insert description of change, if needed
12392 --
12393
12394
12395 -- check whether patch can be applied
12396 SELECT evergreen.upgrade_deps_block_check('0677', :eg_version);
12397
12398 -- FIXME: add/check SQL statements to perform the upgrade
12399 -- Limit groups for circ counting
12400 CREATE TABLE config.circ_limit_group (
12401     id          SERIAL  PRIMARY KEY,
12402     name        TEXT    UNIQUE NOT NULL,
12403     description TEXT
12404 );
12405
12406 -- Limit sets
12407 CREATE TABLE config.circ_limit_set (
12408     id          SERIAL  PRIMARY KEY,
12409     name        TEXT    UNIQUE NOT NULL,
12410     owning_lib  INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
12411     items_out   INT     NOT NULL, -- Total current active circulations must be less than this. 0 means skip counting (always pass)
12412     depth       INT     NOT NULL DEFAULT 0, -- Depth count starts at
12413     global      BOOL    NOT NULL DEFAULT FALSE, -- If enabled, include everything below depth, otherwise ancestors/descendants only
12414     description TEXT
12415 );
12416
12417 -- Linkage between matchpoints and limit sets
12418 CREATE TABLE config.circ_matrix_limit_set_map (
12419     id          SERIAL  PRIMARY KEY,
12420     matchpoint  INT     NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12421     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12422     fallthrough BOOL    NOT NULL DEFAULT FALSE, -- If true fallthrough will grab this rule as it goes along
12423     active      BOOL    NOT NULL DEFAULT TRUE,
12424     CONSTRAINT circ_limit_set_once_per_matchpoint UNIQUE (matchpoint, limit_set)
12425 );
12426
12427 -- Linkage between limit sets and circ mods
12428 CREATE TABLE config.circ_limit_set_circ_mod_map (
12429     id          SERIAL  PRIMARY KEY,
12430     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12431     circ_mod    TEXT    NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
12432     CONSTRAINT cm_once_per_set UNIQUE (limit_set, circ_mod)
12433 );
12434
12435 -- Linkage between limit sets and limit groups
12436 CREATE TABLE config.circ_limit_set_group_map (
12437     id          SERIAL  PRIMARY KEY,
12438     limit_set    INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12439     limit_group INT     NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12440     check_only  BOOL    NOT NULL DEFAULT FALSE, -- If true, don't accumulate this limit_group for storing with the circulation
12441     CONSTRAINT clg_once_per_set UNIQUE (limit_set, limit_group)
12442 );
12443
12444 -- Linkage between limit groups and circulations
12445 CREATE TABLE action.circulation_limit_group_map (
12446     circ        BIGINT      NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12447     limit_group INT         NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12448     PRIMARY KEY (circ, limit_group)
12449 );
12450
12451 -- Function for populating the circ/limit group mappings
12452 CREATE OR REPLACE FUNCTION action.link_circ_limit_groups ( BIGINT, INT[] ) RETURNS VOID AS $func$
12453     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));
12454 $func$ LANGUAGE SQL;
12455
12456 DROP TYPE IF EXISTS action.circ_matrix_test_result CASCADE;
12457 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[] );
12458
12459 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$
12460 DECLARE
12461     user_object             actor.usr%ROWTYPE;
12462     standing_penalty        config.standing_penalty%ROWTYPE;
12463     item_object             asset.copy%ROWTYPE;
12464     item_status_object      config.copy_status%ROWTYPE;
12465     item_location_object    asset.copy_location%ROWTYPE;
12466     result                  action.circ_matrix_test_result;
12467     circ_test               action.found_circ_matrix_matchpoint;
12468     circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
12469     circ_limit_set          config.circ_limit_set%ROWTYPE;
12470     hold_ratio              action.hold_stats%ROWTYPE;
12471     penalty_type            TEXT;
12472     items_out               INT;
12473     context_org_list        INT[];
12474     done                    BOOL := FALSE;
12475 BEGIN
12476     -- Assume success unless we hit a failure condition
12477     result.success := TRUE;
12478
12479     -- Need user info to look up matchpoints
12480     SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
12481
12482     -- (Insta)Fail if we couldn't find the user
12483     IF user_object.id IS NULL THEN
12484         result.fail_part := 'no_user';
12485         result.success := FALSE;
12486         done := TRUE;
12487         RETURN NEXT result;
12488         RETURN;
12489     END IF;
12490
12491     -- Need item info to look up matchpoints
12492     SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
12493
12494     -- (Insta)Fail if we couldn't find the item 
12495     IF item_object.id IS NULL THEN
12496         result.fail_part := 'no_item';
12497         result.success := FALSE;
12498         done := TRUE;
12499         RETURN NEXT result;
12500         RETURN;
12501     END IF;
12502
12503     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
12504
12505     circ_matchpoint             := circ_test.matchpoint;
12506     result.matchpoint           := circ_matchpoint.id;
12507     result.circulate            := circ_matchpoint.circulate;
12508     result.duration_rule        := circ_matchpoint.duration_rule;
12509     result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
12510     result.max_fine_rule        := circ_matchpoint.max_fine_rule;
12511     result.hard_due_date        := circ_matchpoint.hard_due_date;
12512     result.renewals             := circ_matchpoint.renewals;
12513     result.grace_period         := circ_matchpoint.grace_period;
12514     result.buildrows            := circ_test.buildrows;
12515
12516     -- (Insta)Fail if we couldn't find a matchpoint
12517     IF circ_test.success = false THEN
12518         result.fail_part := 'no_matchpoint';
12519         result.success := FALSE;
12520         done := TRUE;
12521         RETURN NEXT result;
12522         RETURN;
12523     END IF;
12524
12525     -- All failures before this point are non-recoverable
12526     -- Below this point are possibly overridable failures
12527
12528     -- Fail if the user is barred
12529     IF user_object.barred IS TRUE THEN
12530         result.fail_part := 'actor.usr.barred';
12531         result.success := FALSE;
12532         done := TRUE;
12533         RETURN NEXT result;
12534     END IF;
12535
12536     -- Fail if the item can't circulate
12537     IF item_object.circulate IS FALSE THEN
12538         result.fail_part := 'asset.copy.circulate';
12539         result.success := FALSE;
12540         done := TRUE;
12541         RETURN NEXT result;
12542     END IF;
12543
12544     -- Fail if the item isn't in a circulateable status on a non-renewal
12545     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
12546         result.fail_part := 'asset.copy.status';
12547         result.success := FALSE;
12548         done := TRUE;
12549         RETURN NEXT result;
12550     -- Alternately, fail if the item isn't checked out on a renewal
12551     ELSIF renewal AND item_object.status <> 1 THEN
12552         result.fail_part := 'asset.copy.status';
12553         result.success := FALSE;
12554         done := TRUE;
12555         RETURN NEXT result;
12556     END IF;
12557
12558     -- Fail if the item can't circulate because of the shelving location
12559     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
12560     IF item_location_object.circulate IS FALSE THEN
12561         result.fail_part := 'asset.copy_location.circulate';
12562         result.success := FALSE;
12563         done := TRUE;
12564         RETURN NEXT result;
12565     END IF;
12566
12567     -- Use Circ OU for penalties and such
12568     SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
12569
12570     IF renewal THEN
12571         penalty_type = '%RENEW%';
12572     ELSE
12573         penalty_type = '%CIRC%';
12574     END IF;
12575
12576     FOR standing_penalty IN
12577         SELECT  DISTINCT csp.*
12578           FROM  actor.usr_standing_penalty usp
12579                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
12580           WHERE usr = match_user
12581                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
12582                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
12583                 AND csp.block_list LIKE penalty_type LOOP
12584
12585         result.fail_part := standing_penalty.name;
12586         result.success := FALSE;
12587         done := TRUE;
12588         RETURN NEXT result;
12589     END LOOP;
12590
12591     -- Fail if the test is set to hard non-circulating
12592     IF circ_matchpoint.circulate IS FALSE THEN
12593         result.fail_part := 'config.circ_matrix_test.circulate';
12594         result.success := FALSE;
12595         done := TRUE;
12596         RETURN NEXT result;
12597     END IF;
12598
12599     -- Fail if the total copy-hold ratio is too low
12600     IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
12601         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
12602         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
12603             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
12604             result.success := FALSE;
12605             done := TRUE;
12606             RETURN NEXT result;
12607         END IF;
12608     END IF;
12609
12610     -- Fail if the available copy-hold ratio is too low
12611     IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
12612         IF hold_ratio.hold_count IS NULL THEN
12613             SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
12614         END IF;
12615         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
12616             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
12617             result.success := FALSE;
12618             done := TRUE;
12619             RETURN NEXT result;
12620         END IF;
12621     END IF;
12622
12623     -- Fail if the user has too many items out by defined limit sets
12624     FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
12625       JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
12626       WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
12627         ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
12628         ) LOOP
12629             IF circ_limit_set.items_out > 0 AND NOT renewal THEN
12630                 SELECT INTO context_org_list ARRAY_AGG(aou.id)
12631                   FROM actor.org_unit_full_path( circ_ou ) aou
12632                     JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
12633                   WHERE aout.depth >= circ_limit_set.depth;
12634                 IF circ_limit_set.global THEN
12635                     WITH RECURSIVE descendant_depth AS (
12636                         SELECT  ou.id,
12637                             ou.parent_ou
12638                         FROM  actor.org_unit ou
12639                         WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
12640                             UNION
12641                         SELECT  ou.id,
12642                             ou.parent_ou
12643                         FROM  actor.org_unit ou
12644                             JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
12645                     ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
12646                 END IF;
12647                 SELECT INTO items_out COUNT(DISTINCT circ.id)
12648                   FROM action.circulation circ
12649                     JOIN asset.copy copy ON (copy.id = circ.target_copy)
12650                     LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
12651                   WHERE circ.usr = match_user
12652                     AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
12653                     AND circ.checkin_time IS NULL
12654                     AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
12655                     AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
12656                         OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
12657                     );
12658                 IF items_out >= circ_limit_set.items_out THEN
12659                     result.fail_part := 'config.circ_matrix_circ_mod_test';
12660                     result.success := FALSE;
12661                     done := TRUE;
12662                     RETURN NEXT result;
12663                 END IF;
12664             END IF;
12665             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;
12666     END LOOP;
12667
12668     -- If we passed everything, return the successful matchpoint
12669     IF NOT done THEN
12670         RETURN NEXT result;
12671     END IF;
12672
12673     RETURN;
12674 END;
12675 $func$ LANGUAGE plpgsql;
12676
12677 -- We need to re-create these, as they got dropped with the type above.
12678 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
12679     SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
12680 $func$ LANGUAGE SQL;
12681
12682 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
12683     SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
12684 $func$ LANGUAGE SQL;
12685
12686 -- Temp function for migrating circ mod limits.
12687 CREATE OR REPLACE FUNCTION evergreen.temp_migrate_circ_mod_limits() RETURNS VOID AS $func$
12688 DECLARE
12689     circ_mod_group config.circ_matrix_circ_mod_test%ROWTYPE;
12690     current_set INT;
12691     circ_mod_count INT;
12692 BEGIN
12693     FOR circ_mod_group IN SELECT * FROM config.circ_matrix_circ_mod_test LOOP
12694         INSERT INTO config.circ_limit_set(name, owning_lib, items_out, depth, global, description)
12695             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'
12696                 FROM config.circ_matrix_matchpoint WHERE id = circ_mod_group.matchpoint
12697             RETURNING id INTO current_set;
12698         INSERT INTO config.circ_matrix_limit_set_map(matchpoint, limit_set, fallthrough, active) VALUES (circ_mod_group.matchpoint, current_set, false, true);
12699         INSERT INTO config.circ_limit_set_circ_mod_map(limit_set, circ_mod)
12700             SELECT current_set, circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = circ_mod_group.id;
12701         SELECT INTO circ_mod_count count(id) FROM config.circ_limit_set_circ_mod_map WHERE limit_set = current_set;
12702         RAISE NOTICE 'Created limit set with id % and % circ modifiers attached to matchpoint %', current_set, circ_mod_count, circ_mod_group.matchpoint;
12703     END LOOP;
12704 END;
12705 $func$ LANGUAGE plpgsql;
12706
12707 -- Run the temp function
12708 SELECT * FROM evergreen.temp_migrate_circ_mod_limits();
12709
12710 -- Drop the temp function
12711 DROP FUNCTION evergreen.temp_migrate_circ_mod_limits();
12712
12713 --Drop the old tables
12714 --Not sure we want to do this. Keeping them may help "something went wrong" correction.
12715 --DROP TABLE IF EXISTS config.circ_matrix_circ_mod_test_map, config.circ_matrix_circ_mod_test;
12716
12717
12718 -- Evergreen DB patch 0678.data.vandelay-default-merge-profiles.sql
12719
12720 -- check whether patch can be applied
12721 SELECT evergreen.upgrade_deps_block_check('0678', :eg_version);
12722
12723 INSERT INTO vandelay.merge_profile (owner, name, replace_spec) 
12724     VALUES (1, 'Match-Only Merge', '901c');
12725
12726 INSERT INTO vandelay.merge_profile (owner, name, preserve_spec) 
12727     VALUES (1, 'Full Overlay', '901c');
12728
12729 SELECT evergreen.upgrade_deps_block_check('0679', :eg_version);
12730
12731 -- Address typo in column name
12732 ALTER TABLE config.metabib_class ADD COLUMN buoyant BOOL DEFAULT FALSE NOT NULL;
12733 UPDATE config.metabib_class SET buoyant = bouyant;
12734 ALTER TABLE config.metabib_class DROP COLUMN bouyant;
12735
12736 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
12737 DECLARE
12738     normalizer      RECORD;
12739     value           TEXT := '';
12740 BEGIN
12741
12742     value := NEW.value;
12743
12744     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12745         FOR normalizer IN
12746             SELECT  n.func AS func,
12747                     n.param_count AS param_count,
12748                     m.params AS params
12749               FROM  config.index_normalizer n
12750                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12751               WHERE field = NEW.field AND m.pos < 0
12752               ORDER BY m.pos LOOP
12753                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12754                     quote_literal( value ) ||
12755                     CASE
12756                         WHEN normalizer.param_count > 0
12757                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12758                             ELSE ''
12759                         END ||
12760                     ')' INTO value;
12761
12762         END LOOP;
12763
12764         NEW.value := value;
12765     END IF;
12766
12767     IF NEW.index_vector = ''::tsvector THEN
12768         RETURN NEW;
12769     END IF;
12770
12771     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12772         FOR normalizer IN
12773             SELECT  n.func AS func,
12774                     n.param_count AS param_count,
12775                     m.params AS params
12776               FROM  config.index_normalizer n
12777                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12778               WHERE field = NEW.field AND m.pos >= 0
12779               ORDER BY m.pos LOOP
12780                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12781                     quote_literal( value ) ||
12782                     CASE
12783                         WHEN normalizer.param_count > 0
12784                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12785                             ELSE ''
12786                         END ||
12787                     ')' INTO value;
12788
12789         END LOOP;
12790     END IF;
12791
12792     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
12793         value :=  ARRAY_TO_STRING(
12794             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12795         );
12796         value := public.search_normalize(value);
12797     END IF;
12798
12799     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
12800
12801     RETURN NEW;
12802 END;
12803 $$ LANGUAGE PLPGSQL;
12804
12805 -- Given a string such as a user might type into a search box, prepare
12806 -- two changed variants for TO_TSQUERY(). See
12807 -- http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
12808 -- The first variant is normalized to match indexed documents regardless
12809 -- of diacritics.  The second variant keeps its diacritics for proper
12810 -- highlighting via TS_HEADLINE().
12811 CREATE OR REPLACE
12812     FUNCTION metabib.autosuggest_prepare_tsquery(orig TEXT) RETURNS TEXT[] AS
12813 $$
12814 DECLARE
12815     orig_ended_in_space     BOOLEAN;
12816     result                  RECORD;
12817     plain                   TEXT;
12818     normalized              TEXT;
12819 BEGIN
12820     orig_ended_in_space := orig ~ E'\\s$';
12821
12822     orig := ARRAY_TO_STRING(
12823         evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
12824     );
12825
12826     normalized := public.search_normalize(orig); -- also trim()s
12827     plain := trim(orig);
12828
12829     IF NOT orig_ended_in_space THEN
12830         plain := plain || ':*';
12831         normalized := normalized || ':*';
12832     END IF;
12833
12834     plain := ARRAY_TO_STRING(
12835         evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
12836     );
12837     normalized := ARRAY_TO_STRING(
12838         evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
12839     );
12840
12841     RETURN ARRAY[normalized, plain];
12842 END;
12843 $$ LANGUAGE PLPGSQL;
12844
12845
12846 -- Definition of OUT parameters changes, so must drop first
12847 DROP FUNCTION IF EXISTS metabib.suggest_browse_entries (TEXT, TEXT, TEXT, INTEGER, INTEGER, INTEGER);
12848
12849 CREATE OR REPLACE
12850     FUNCTION metabib.suggest_browse_entries(
12851         raw_query_text  TEXT,   -- actually typed by humans at the UI level
12852         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
12853         headline_opts   TEXT,   -- markup options for ts_headline()
12854         visibility_org  INTEGER,-- null if you don't want opac visibility test
12855         query_limit     INTEGER,-- use in LIMIT clause of interal query
12856         normalization   INTEGER -- argument to TS_RANK_CD()
12857     ) RETURNS TABLE (
12858         value                   TEXT,   -- plain
12859         field                   INTEGER,
12860         buoyant_and_class_match BOOL,
12861         field_match             BOOL,
12862         field_weight            INTEGER,
12863         rank                    REAL,
12864         buoyant                 BOOL,
12865         match                   TEXT    -- marked up
12866     ) AS $func$
12867 DECLARE
12868     prepared_query_texts    TEXT[];
12869     query                   TSQUERY;
12870     plain_query             TSQUERY;
12871     opac_visibility_join    TEXT;
12872     search_class_join       TEXT;
12873     r_fields                RECORD;
12874 BEGIN
12875     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
12876
12877     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
12878     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
12879
12880     IF visibility_org IS NOT NULL THEN
12881         opac_visibility_join := '
12882     JOIN asset.opac_visible_copies aovc ON (
12883         aovc.record = mbedm.source AND
12884         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
12885     )';
12886     ELSE
12887         opac_visibility_join := '';
12888     END IF;
12889
12890     -- The following determines whether we only provide suggestsons matching
12891     -- the user's selected search_class, or whether we show other suggestions
12892     -- too. The reason for MIN() is that for search_classes like
12893     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
12894     -- implication is that if title as a class doesn't have restrict,
12895     -- nor does the proper field, but the uniform field does, you're going
12896     -- to get 'false' for your overall evaluation of 'should we restrict?'
12897     -- To invert that, change from MIN() to MAX().
12898
12899     SELECT
12900         INTO r_fields
12901             MIN(cmc.restrict::INT) AS restrict_class,
12902             MIN(cmf.restrict::INT) AS restrict_field
12903         FROM metabib.search_class_to_registered_components(search_class)
12904             AS _registered (field_class TEXT, field INT)
12905         JOIN
12906             config.metabib_class cmc ON (cmc.name = _registered.field_class)
12907         LEFT JOIN
12908             config.metabib_field cmf ON (cmf.id = _registered.field);
12909
12910     -- evaluate 'should we restrict?'
12911     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
12912         search_class_join := '
12913     JOIN
12914         metabib.search_class_to_registered_components($2)
12915         AS _registered (field_class TEXT, field INT) ON (
12916             (_registered.field IS NULL AND
12917                 _registered.field_class = cmf.field_class) OR
12918             (_registered.field = cmf.id)
12919         )
12920     ';
12921     ELSE
12922         search_class_join := '
12923     LEFT JOIN
12924         metabib.search_class_to_registered_components($2)
12925         AS _registered (field_class TEXT, field INT) ON (
12926             _registered.field_class = cmc.name
12927         )
12928     ';
12929     END IF;
12930
12931     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $7, $3) FROM (SELECT DISTINCT
12932         mbe.value,
12933         cmf.id,
12934         cmc.buoyant AND _registered.field_class IS NOT NULL,
12935         _registered.field = cmf.id,
12936         cmf.weight,
12937         TS_RANK_CD(mbe.index_vector, $1, $6),
12938         cmc.buoyant
12939     FROM metabib.browse_entry_def_map mbedm
12940     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
12941     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
12942     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
12943     '  || search_class_join || opac_visibility_join ||
12944     ' WHERE $1 @@ mbe.index_vector
12945     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12946     LIMIT $5) x
12947     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12948     '   -- sic, repeat the order by clause in the outer select too
12949     USING
12950         query, search_class, headline_opts,
12951         visibility_org, query_limit, normalization, plain_query
12952         ;
12953
12954     -- sort order:
12955     --  buoyant AND chosen class = match class
12956     --  chosen field = match field
12957     --  field weight
12958     --  rank
12959     --  buoyancy
12960     --  value itself
12961
12962 END;
12963 $func$ LANGUAGE PLPGSQL;
12964
12965
12966 \qecho 
12967 \qecho The following takes about a minute per 100,000 rows in
12968 \qecho metabib.browse_entry on my development system, which is only a VM with
12969 \qecho 4 GB of memory and 2 cores.
12970 \qecho 
12971 \qecho The following is a very loose estimate of how long the next UPDATE
12972 \qecho statement would take to finish on MY machine, based on YOUR number
12973 \qecho of rows in metabib.browse_entry:
12974 \qecho 
12975
12976 SELECT (COUNT(id) / 100000.0) * INTERVAL '1 minute'
12977     AS "approximate duration of following UPDATE statement"
12978     FROM metabib.browse_entry;
12979
12980 UPDATE metabib.browse_entry SET index_vector = TO_TSVECTOR(
12981     'keyword',
12982     public.search_normalize(
12983         ARRAY_TO_STRING(
12984             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12985         )
12986     )
12987 );
12988
12989
12990 SELECT evergreen.upgrade_deps_block_check('0680', :eg_version);
12991
12992 -- Not much use in having identifier-class fields be suggestions. Credit for the idea goes to Ben Shum.
12993 UPDATE config.metabib_field SET browse_field = FALSE WHERE id < 100 AND field_class = 'identifier';
12994
12995
12996 ---------------------------------------------------------------------------
12997 -- The rest of this was tested on Evergreen Indiana's dev server, which has
12998 -- a large data set  of 2.6M bibs, and was instrumental in sussing out the
12999 -- needed adjustments.  Thanks, EG-IN!
13000 ---------------------------------------------------------------------------
13001
13002 -- GIN indexes are /much/ better for prefix matching, which is important for browse and autosuggest
13003 --Commented out the creation earlier, so we don't need to drop it here.
13004 --DROP INDEX metabib.metabib_browse_entry_index_vector_idx;
13005 CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIN (index_vector);
13006
13007
13008 -- We need thes to make the autosuggest limiting joins fast
13009 CREATE INDEX browse_entry_def_map_def_idx ON metabib.browse_entry_def_map (def);
13010 CREATE INDEX browse_entry_def_map_entry_idx ON metabib.browse_entry_def_map (entry);
13011 CREATE INDEX browse_entry_def_map_source_idx ON metabib.browse_entry_def_map (source);
13012
13013 -- In practice this will always be ~1 row, and the default of 1000 causes terrible plans
13014 ALTER FUNCTION metabib.search_class_to_registered_components(text) ROWS 1;
13015
13016 -- Reworking of the generated query to act in a sane manner in the face of large datasets
13017 CREATE OR REPLACE
13018     FUNCTION metabib.suggest_browse_entries(
13019         raw_query_text  TEXT,   -- actually typed by humans at the UI level
13020         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
13021         headline_opts   TEXT,   -- markup options for ts_headline()
13022         visibility_org  INTEGER,-- null if you don't want opac visibility test
13023         query_limit     INTEGER,-- use in LIMIT clause of interal query
13024         normalization   INTEGER -- argument to TS_RANK_CD()
13025     ) RETURNS TABLE (
13026         value                   TEXT,   -- plain
13027         field                   INTEGER,
13028         buoyant_and_class_match BOOL,
13029         field_match             BOOL,
13030         field_weight            INTEGER,
13031         rank                    REAL,
13032         buoyant                 BOOL,
13033         match                   TEXT    -- marked up
13034     ) AS $func$
13035 DECLARE
13036     prepared_query_texts    TEXT[];
13037     query                   TSQUERY;
13038     plain_query             TSQUERY;
13039     opac_visibility_join    TEXT;
13040     search_class_join       TEXT;
13041     r_fields                RECORD;
13042 BEGIN
13043     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
13044
13045     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
13046     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
13047
13048     IF visibility_org IS NOT NULL THEN
13049         opac_visibility_join := '
13050     JOIN asset.opac_visible_copies aovc ON (
13051         aovc.record = x.source AND
13052         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
13053     )';
13054     ELSE
13055         opac_visibility_join := '';
13056     END IF;
13057
13058     -- The following determines whether we only provide suggestsons matching
13059     -- the user's selected search_class, or whether we show other suggestions
13060     -- too. The reason for MIN() is that for search_classes like
13061     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
13062     -- implication is that if title as a class doesn't have restrict,
13063     -- nor does the proper field, but the uniform field does, you're going
13064     -- to get 'false' for your overall evaluation of 'should we restrict?'
13065     -- To invert that, change from MIN() to MAX().
13066
13067     SELECT
13068         INTO r_fields
13069             MIN(cmc.restrict::INT) AS restrict_class,
13070             MIN(cmf.restrict::INT) AS restrict_field
13071         FROM metabib.search_class_to_registered_components(search_class)
13072             AS _registered (field_class TEXT, field INT)
13073         JOIN
13074             config.metabib_class cmc ON (cmc.name = _registered.field_class)
13075         LEFT JOIN
13076             config.metabib_field cmf ON (cmf.id = _registered.field);
13077
13078     -- evaluate 'should we restrict?'
13079     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
13080         search_class_join := '
13081     JOIN
13082         metabib.search_class_to_registered_components($2)
13083         AS _registered (field_class TEXT, field INT) ON (
13084             (_registered.field IS NULL AND
13085                 _registered.field_class = cmf.field_class) OR
13086             (_registered.field = cmf.id)
13087         )
13088     ';
13089     ELSE
13090         search_class_join := '
13091     LEFT JOIN
13092         metabib.search_class_to_registered_components($2)
13093         AS _registered (field_class TEXT, field INT) ON (
13094             _registered.field_class = cmc.name
13095         )
13096     ';
13097     END IF;
13098
13099     RETURN QUERY EXECUTE '
13100 SELECT  DISTINCT
13101         x.value,
13102         x.id,
13103         x.push,
13104         x.restrict,
13105         x.weight,
13106         x.ts_rank_cd,
13107         x.buoyant,
13108         TS_HEADLINE(value, $7, $3)
13109   FROM  (SELECT DISTINCT
13110                 mbe.value,
13111                 cmf.id,
13112                 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
13113                 _registered.field = cmf.id AS restrict,
13114                 cmf.weight,
13115                 TS_RANK_CD(mbe.index_vector, $1, $6),
13116                 cmc.buoyant,
13117                 mbedm.source
13118           FROM  metabib.browse_entry_def_map mbedm
13119
13120                 -- Start with a pre-limited set of 10k possible suggestions. More than that is not going to be useful anyway
13121                 JOIN (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) mbe ON (mbe.id = mbedm.entry)
13122
13123                 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
13124                 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
13125                 '  || search_class_join || '
13126           ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
13127           LIMIT 1000) AS x -- This outer limit makes testing for opac visibility usably fast
13128         ' || opac_visibility_join || '
13129   ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
13130   LIMIT $5
13131 '   -- sic, repeat the order by clause in the outer select too
13132     USING
13133         query, search_class, headline_opts,
13134         visibility_org, query_limit, normalization, plain_query
13135         ;
13136
13137     -- sort order:
13138     --  buoyant AND chosen class = match class
13139     --  chosen field = match field
13140     --  field weight
13141     --  rank
13142     --  buoyancy
13143     --  value itself
13144
13145 END;
13146 $func$ LANGUAGE PLPGSQL;
13147
13148
13149 -- Evergreen DB patch 0681.schema.user-activity.sql
13150 --
13151
13152 -- check whether patch can be applied
13153 SELECT evergreen.upgrade_deps_block_check('0681', :eg_version);
13154
13155 -- SCHEMA --
13156
13157 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
13158
13159 CREATE TABLE config.usr_activity_type (
13160     id          SERIAL                      PRIMARY KEY, 
13161     ewho        TEXT,
13162     ewhat       TEXT,
13163     ehow        TEXT,
13164     label       TEXT                        NOT NULL, -- i18n
13165     egroup      config.usr_activity_group   NOT NULL,
13166     enabled     BOOL                        NOT NULL DEFAULT TRUE,
13167     transient   BOOL                        NOT NULL DEFAULT FALSE,
13168     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
13169 );
13170
13171 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
13172     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
13173
13174 CREATE TABLE actor.usr_activity (
13175     id          BIGSERIAL   PRIMARY KEY,
13176     usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
13177     etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
13178     event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
13179 );
13180
13181 -- remove transient activity entries on insert of new entries
13182 CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
13183 BEGIN
13184     DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
13185         WHERE atype.transient AND 
13186             NEW.etype = atype.id AND
13187             act.etype = atype.id AND
13188             act.usr = NEW.usr;
13189     RETURN NEW;
13190 END;
13191 $$ LANGUAGE PLPGSQL;
13192
13193 CREATE TRIGGER remove_transient_usr_activity
13194     BEFORE INSERT ON actor.usr_activity
13195     FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
13196
13197 -- given a set of activity criteria, find the most approprate activity type
13198 CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
13199         ewho TEXT, 
13200         ewhat TEXT, 
13201         ehow TEXT
13202     ) RETURNS SETOF config.usr_activity_type AS $$
13203 SELECT * FROM config.usr_activity_type 
13204     WHERE 
13205         enabled AND 
13206         (ewho  IS NULL OR ewho  = $1) AND
13207         (ewhat IS NULL OR ewhat = $2) AND
13208         (ehow  IS NULL OR ehow  = $3) 
13209     ORDER BY 
13210         -- BOOL comparisons sort false to true
13211         COALESCE(ewho, '')  != COALESCE($1, ''),
13212         COALESCE(ewhat,'')  != COALESCE($2, ''),
13213         COALESCE(ehow, '')  != COALESCE($3, '') 
13214     LIMIT 1;
13215 $$ LANGUAGE SQL;
13216
13217 -- given a set of activity criteria, finds the best
13218 -- activity type and inserts the activity entry
13219 CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
13220         usr INT,
13221         ewho TEXT, 
13222         ewhat TEXT, 
13223         ehow TEXT
13224     ) RETURNS SETOF actor.usr_activity AS $$
13225 DECLARE
13226     new_row actor.usr_activity%ROWTYPE;
13227 BEGIN
13228     SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
13229     IF FOUND THEN
13230         new_row.usr := usr;
13231         INSERT INTO actor.usr_activity (usr, etype) 
13232             VALUES (usr, new_row.etype)
13233             RETURNING * INTO new_row;
13234         RETURN NEXT new_row;
13235     END IF;
13236 END;
13237 $$ LANGUAGE plpgsql;
13238
13239 -- SEED DATA --
13240
13241 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
13242
13243      -- authen/authz actions
13244      -- note: "opensrf" is the default ingress/ehow
13245      (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
13246     ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
13247     ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
13248     ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
13249     ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
13250     ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
13251     ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
13252     ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
13253
13254     ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
13255     ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
13256     ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
13257     ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
13258     ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
13259     ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
13260     ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
13261
13262      -- authen/authz actions w/ known uses of "who"
13263     ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
13264     ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
13265     ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
13266     ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
13267     ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
13268     ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
13269     ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
13270
13271     ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
13272     ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
13273     -- ...
13274     ;
13275
13276 -- reserve the first 1000 slots
13277 SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
13278
13279 INSERT INTO config.org_unit_setting_type 
13280     (name, label, description, grp, datatype) 
13281     VALUES (
13282         'circ.patron.usr_activity_retrieve.max',
13283          oils_i18n_gettext(
13284             'circ.patron.usr_activity_retrieve.max',
13285             'Max user activity entries to retrieve (staff client)',
13286             'coust', 
13287             'label'
13288         ),
13289         oils_i18n_gettext(
13290             'circ.patron.usr_activity_retrieve.max',
13291             '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.',
13292             'coust', 
13293             'description'
13294         ),
13295         'gui',
13296         'integer'
13297     );
13298
13299
13300 SELECT evergreen.upgrade_deps_block_check('0682', :eg_version);
13301
13302 CREATE TABLE asset.copy_location_group (
13303     id              SERIAL  PRIMARY KEY,
13304     name            TEXT    NOT NULL, -- i18n
13305     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13306     pos             INT     NOT NULL DEFAULT 0,
13307     top             BOOL    NOT NULL DEFAULT FALSE,
13308     opac_visible    BOOL    NOT NULL DEFAULT TRUE,
13309     CONSTRAINT lgroup_once_per_owner UNIQUE (owner,name)
13310 );
13311
13312 CREATE TABLE asset.copy_location_group_map (
13313     id       SERIAL PRIMARY KEY,
13314     location    INT     NOT NULL REFERENCES asset.copy_location (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13315     lgroup      INT     NOT NULL REFERENCES asset.copy_location_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13316     CONSTRAINT  lgroup_once_per_group UNIQUE (lgroup,location)
13317 );
13318
13319 -- check whether patch can be applied
13320 SELECT evergreen.upgrade_deps_block_check('0683', :eg_version);
13321
13322 INSERT INTO action_trigger.event_params (event_def, param, value)
13323     VALUES (5, 'check_email_notify', 1);
13324 INSERT INTO action_trigger.event_params (event_def, param, value)
13325     VALUES (7, 'check_email_notify', 1);
13326 INSERT INTO action_trigger.event_params (event_def, param, value)
13327     VALUES (9, 'check_email_notify', 1);
13328 INSERT INTO action_trigger.validator (module,description) VALUES
13329     ('HoldNotifyCheck',
13330     oils_i18n_gettext(
13331         'HoldNotifyCheck',
13332         'Check Hold notification flag(s)',
13333         'atval',
13334         'description'
13335     ));
13336 UPDATE action_trigger.event_definition SET validator = 'HoldNotifyCheck' WHERE id = 9;
13337
13338 -- NOT COVERED: Adding check_sms_notify to the proper trigger. It doesn't have a static id.
13339
13340 -- check whether patch can be applied
13341 SELECT evergreen.upgrade_deps_block_check('0684', :eg_version);
13342
13343 -- schema --
13344
13345 -- Replace the constraints with more flexible ENUM's
13346 ALTER TABLE vandelay.queue DROP CONSTRAINT queue_queue_type_check;
13347 ALTER TABLE vandelay.bib_queue DROP CONSTRAINT bib_queue_queue_type_check;
13348 ALTER TABLE vandelay.authority_queue DROP CONSTRAINT authority_queue_queue_type_check;
13349
13350 CREATE TYPE vandelay.bib_queue_queue_type AS ENUM ('bib', 'acq');
13351 CREATE TYPE vandelay.authority_queue_queue_type AS ENUM ('authority');
13352
13353 -- dropped column is also implemented by the child tables
13354 ALTER TABLE vandelay.queue DROP COLUMN queue_type; 
13355
13356 -- to recover after using the undo sql from below
13357 -- alter table vandelay.bib_queue  add column queue_type text default 'bib' not null;
13358 -- alter table vandelay.authority_queue  add column queue_type text default 'authority' not null;
13359
13360 -- modify the child tables to use the ENUMs
13361 ALTER TABLE vandelay.bib_queue 
13362     ALTER COLUMN queue_type DROP DEFAULT,
13363     ALTER COLUMN queue_type TYPE vandelay.bib_queue_queue_type 
13364         USING (queue_type::vandelay.bib_queue_queue_type),
13365     ALTER COLUMN queue_type SET DEFAULT 'bib';
13366
13367 ALTER TABLE vandelay.authority_queue 
13368     ALTER COLUMN queue_type DROP DEFAULT,
13369     ALTER COLUMN queue_type TYPE vandelay.authority_queue_queue_type 
13370         USING (queue_type::vandelay.authority_queue_queue_type),
13371     ALTER COLUMN queue_type SET DEFAULT 'authority';
13372
13373 -- give lineitems a pointer to their vandelay queued_record
13374
13375 ALTER TABLE acq.lineitem ADD COLUMN queued_record BIGINT
13376     REFERENCES vandelay.queued_bib_record (id) 
13377     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
13378
13379 ALTER TABLE acq.acq_lineitem_history ADD COLUMN queued_record BIGINT
13380     REFERENCES vandelay.queued_bib_record (id) 
13381     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
13382
13383 -- seed data --
13384
13385 INSERT INTO permission.perm_list ( id, code, description ) 
13386     VALUES ( 
13387         521, 
13388         'IMPORT_ACQ_LINEITEM_BIB_RECORD_UPLOAD', 
13389         oils_i18n_gettext( 
13390             521,
13391             'Allows a user to create new bibs directly from an ACQ MARC file upload', 
13392             'ppl', 
13393             'description' 
13394         )
13395     );
13396
13397
13398 INSERT INTO vandelay.import_error ( code, description ) 
13399     VALUES ( 
13400         'import.record.perm_failure', 
13401         oils_i18n_gettext(
13402             'import.record.perm_failure', 
13403             'Perm failure creating a record', 'vie', 'description') 
13404     );
13405
13406
13407
13408
13409 -- Evergreen DB patch 0685.data.bluray_vr_format.sql
13410 --
13411 -- FIXME: insert description of change, if needed
13412 --
13413
13414
13415 -- check whether patch can be applied
13416 SELECT evergreen.upgrade_deps_block_check('0685', :eg_version);
13417
13418 -- FIXME: add/check SQL statements to perform the upgrade
13419 DO $FUNC$
13420 DECLARE
13421     same_marc BOOL;
13422 BEGIN
13423     -- Check if it is already there
13424     PERFORM * FROM config.marc21_physical_characteristic_value_map v
13425         JOIN config.marc21_physical_characteristic_subfield_map s ON v.ptype_subfield = s.id
13426         WHERE s.ptype_key = 'v' AND s.subfield = 'e' AND s.start_pos = '4' AND s.length = '1'
13427             AND v.value = 's';
13428
13429     -- If it is, bail.
13430     IF FOUND THEN
13431         RETURN;
13432     END IF;
13433
13434     -- Otherwise, insert it
13435     INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label)
13436     SELECT 's',id,'Blu-ray'
13437         FROM config.marc21_physical_characteristic_subfield_map
13438         WHERE ptype_key = 'v' AND subfield = 'e' AND start_pos = '4' AND length = '1';
13439
13440     -- And reingest the blue-ray items so that things see the new value
13441     SELECT INTO same_marc enabled FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc';
13442     UPDATE config.internal_flag SET enabled = true WHERE name = 'ingest.reingest.force_on_same_marc';
13443     UPDATE biblio.record_entry SET marc=marc WHERE id IN (SELECT record
13444         FROM
13445             metabib.full_rec a JOIN metabib.full_rec b USING (record)
13446         WHERE
13447             a.tag = 'LDR' AND a.value LIKE '______g%'
13448         AND b.tag = '007' AND b.value LIKE 'v___s%');
13449     UPDATE config.internal_flag SET enabled = same_marc WHERE name = 'ingest.reingest.force_on_same_marc';
13450 END;
13451 $FUNC$;
13452
13453
13454 -- Evergreen DB patch 0686.schema.auditor_boost.sql
13455 --
13456 -- FIXME: insert description of change, if needed
13457 --
13458 -- check whether patch can be applied
13459 SELECT evergreen.upgrade_deps_block_check('0686', :eg_version);
13460
13461 -- FIXME: add/check SQL statements to perform the upgrade
13462 -- These three functions are for capturing, getting, and clearing user and workstation information
13463
13464 -- Set the User AND workstation in one call. Tis faster. And less calls.
13465 -- First argument is user, second is workstation
13466 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
13467     $_SHARED{"eg_audit_user"} = $_[0];
13468     $_SHARED{"eg_audit_ws"} = $_[1];
13469 $$ LANGUAGE plperl;
13470
13471 -- Get the User AND workstation in one call. Less calls, useful for joins ;)
13472 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
13473     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
13474 $$ LANGUAGE plperl;
13475
13476 -- Clear the audit info, for whatever reason
13477 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
13478     delete($_SHARED{"eg_audit_user"});
13479     delete($_SHARED{"eg_audit_ws"});
13480 $$ LANGUAGE plperl;
13481
13482 CREATE OR REPLACE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13483 BEGIN
13484     EXECUTE $$
13485         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
13486             audit_id    BIGINT                          PRIMARY KEY,
13487             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
13488             audit_action        TEXT                            NOT NULL,
13489             audit_user  INT,
13490             audit_ws    INT,
13491             LIKE $$ || sch || $$.$$ || tbl || $$
13492         );
13493     $$;
13494         RETURN TRUE;
13495 END;
13496 $creator$ LANGUAGE 'plpgsql';
13497
13498 CREATE OR REPLACE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13499 DECLARE
13500     column_list TEXT[];
13501 BEGIN
13502     SELECT INTO column_list array_agg(a.attname)
13503         FROM pg_catalog.pg_attribute a
13504             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
13505             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13506         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
13507
13508     EXECUTE $$
13509         CREATE OR REPLACE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
13510         RETURNS TRIGGER AS $func$
13511         BEGIN
13512             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, $$
13513             || array_to_string(column_list, ', ') || $$ )
13514                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
13515                     now(),
13516                     SUBSTR(TG_OP,1,1),
13517                     eg_user,
13518                     eg_ws,
13519                     OLD.$$ || array_to_string(column_list, ', OLD.') || $$
13520                 FROM auditor.get_audit_info();
13521             RETURN NULL;
13522         END;
13523         $func$ LANGUAGE 'plpgsql';
13524     $$;
13525     RETURN TRUE;
13526 END;
13527 $creator$ LANGUAGE 'plpgsql';
13528
13529 CREATE OR REPLACE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13530 DECLARE
13531     column_list TEXT[];
13532 BEGIN
13533     SELECT INTO column_list array_agg(a.attname)
13534         FROM pg_catalog.pg_attribute a
13535             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
13536             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13537         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
13538
13539     EXECUTE $$
13540         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
13541             SELECT -1 AS audit_id,
13542                    now() AS audit_time,
13543                    '-' AS audit_action,
13544                    -1 AS audit_user,
13545                    -1 AS audit_ws,
13546                    $$ || array_to_string(column_list, ', ') || $$
13547               FROM $$ || sch || $$.$$ || tbl || $$
13548                 UNION ALL
13549             SELECT audit_id, audit_time, audit_action, audit_user, audit_ws,
13550             $$ || array_to_string(column_list, ', ') || $$
13551               FROM auditor.$$ || sch || $$_$$ || tbl || $$_history;
13552     $$;
13553     RETURN TRUE;
13554 END;
13555 $creator$ LANGUAGE 'plpgsql';
13556
13557 -- Corrects all column discrepencies between audit table and core table:
13558 -- Adds missing columns
13559 -- Removes leftover columns
13560 -- Updates types
13561 -- Also, ensures all core auditor columns exist.
13562 CREATE OR REPLACE FUNCTION auditor.fix_columns() RETURNS VOID AS $BODY$
13563 DECLARE
13564     current_table TEXT = ''; -- Storage for post-loop main table name
13565     current_audit_table TEXT = ''; -- Storage for post-loop audit table name
13566     query TEXT = ''; -- Storage for built query
13567     cr RECORD; -- column record object
13568     alter_t BOOL = false; -- Has the alter table command been appended yet
13569     auditor_cores TEXT[] = ARRAY[]::TEXT[]; -- Core auditor function list (filled inside of loop)
13570     core_column TEXT; -- The current core column we are adding
13571 BEGIN
13572     FOR cr IN
13573         WITH audit_tables AS ( -- Basic grab of auditor tables. Anything in the auditor namespace, basically. With oids.
13574             SELECT c.oid AS audit_oid, c.relname AS audit_table
13575             FROM pg_catalog.pg_class c
13576             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13577             WHERE relkind='r' AND nspname = 'auditor'
13578         ),
13579         table_set AS ( -- Union of auditor tables with their "main" tables. With oids.
13580             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
13581             FROM pg_catalog.pg_class c
13582             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13583             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
13584             WHERE relkind = 'r'
13585         ),
13586         column_lists AS ( -- All columns associated with the auditor or main table, grouped by the main table's oid.
13587             SELECT DISTINCT ON (main_oid, attname) t.main_oid, a.attname
13588             FROM table_set t
13589             JOIN pg_catalog.pg_attribute a ON a.attrelid IN (t.main_oid, t.audit_oid)
13590             WHERE attnum > 0 AND NOT attisdropped
13591         ),
13592         column_defs AS ( -- The motherload, every audit table and main table plus column names and defs.
13593             SELECT audit_table,
13594                    main_namespace,
13595                    main_table,
13596                    a.attname AS main_column, -- These two will be null for columns that have since been deleted, or for auditor core columns
13597                    pg_catalog.format_type(a.atttypid, a.atttypmod) AS main_column_def,
13598                    b.attname AS audit_column, -- These two will be null for columns that have since been added
13599                    pg_catalog.format_type(b.atttypid, b.atttypmod) AS audit_column_def
13600             FROM table_set t
13601             JOIN column_lists c USING (main_oid)
13602             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
13603             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
13604         )
13605         -- Nice sorted output from the above
13606         SELECT * FROM column_defs WHERE main_column_def IS DISTINCT FROM audit_column_def ORDER BY main_namespace, main_table, main_column, audit_column
13607     LOOP
13608         IF current_table <> (cr.main_namespace || '.' || cr.main_table) THEN -- New table?
13609             FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Update missing core auditor columns
13610                 IF NOT alter_t THEN -- Add ALTER TABLE if we haven't already
13611                     query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13612                     alter_t:=TRUE;
13613                 ELSE
13614                     query:=query || $$,$$;
13615                 END IF;
13616                 -- 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.
13617                 query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
13618             END LOOP;
13619             IF alter_t THEN -- Open alter table = needs a semicolon
13620                 query:=query || $$; $$;
13621                 alter_t:=FALSE;
13622                 IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
13623                     -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
13624                     -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
13625                     EXECUTE query;
13626                     EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
13627                         $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
13628                     EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
13629                         $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
13630                     query:='';
13631                 END IF;
13632             END IF;
13633             -- New table means we reset the list of needed auditor core columns
13634             auditor_cores = ARRAY['audit_id bigint', 'audit_time timestamp with time zone', 'audit_action text', 'audit_user integer', 'audit_ws integer'];
13635             -- And store some values for use later, because we can't rely on cr in all places.
13636             current_table:=cr.main_namespace || '.' || cr.main_table;
13637             current_audit_table:=cr.audit_table;
13638         END IF;
13639         IF cr.main_column IS NULL AND cr.audit_column LIKE 'audit_%' THEN -- Core auditor column?
13640             -- Remove core from list of cores
13641             SELECT INTO auditor_cores array_agg(core) FROM unnest(auditor_cores) AS core WHERE core != (cr.audit_column || ' ' || cr.audit_column_def);
13642         ELSIF cr.main_column IS NULL THEN -- Main column doesn't exist, and it isn't an auditor column. Needs dropping from the auditor.
13643             IF NOT alter_t THEN
13644                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13645                 alter_t:=TRUE;
13646             ELSE
13647                 query:=query || $$,$$;
13648             END IF;
13649             query:=query || $$ DROP COLUMN $$ || cr.audit_column;
13650         ELSIF cr.audit_column IS NULL AND cr.main_column IS NOT NULL THEN -- New column auditor doesn't have. Add it.
13651             IF NOT alter_t THEN
13652                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13653                 alter_t:=TRUE;
13654             ELSE
13655                 query:=query || $$,$$;
13656             END IF;
13657             query:=query || $$ ADD COLUMN $$ || cr.main_column || $$ $$ || cr.main_column_def;
13658         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.
13659             IF NOT alter_t THEN
13660                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13661                 alter_t:=TRUE;
13662             ELSE
13663                 query:=query || $$,$$;
13664             END IF;
13665             query:=query || $$ ALTER COLUMN $$ || cr.audit_column || $$ TYPE $$ || cr.main_column_def;
13666         END IF;
13667     END LOOP;
13668     FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Repeat this outside of the loop to catch the last table
13669         IF NOT alter_t THEN
13670             query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13671             alter_t:=TRUE;
13672         ELSE
13673             query:=query || $$,$$;
13674         END IF;
13675         -- 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.
13676         query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
13677     END LOOP;
13678     IF alter_t THEN -- Open alter table = needs a semicolon
13679         query:=query || $$;$$;
13680         IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
13681             -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
13682             -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
13683             EXECUTE query;
13684             EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
13685                 $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
13686             EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
13687                 $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
13688             query:='';
13689         END IF;
13690     END IF;
13691     EXECUTE query;
13692 END;
13693 $BODY$ LANGUAGE plpgsql;
13694
13695 -- Update it all routine
13696 CREATE OR REPLACE FUNCTION auditor.update_auditors() RETURNS boolean AS $BODY$
13697 DECLARE
13698     auditor_name TEXT;
13699     table_schema TEXT;
13700     table_name TEXT;
13701 BEGIN
13702     -- Drop Lifecycle view(s) before potential column changes
13703     FOR auditor_name IN
13704         SELECT c.relname
13705             FROM pg_catalog.pg_class c
13706                 JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13707             WHERE relkind = 'v' AND n.nspname = 'auditor' LOOP
13708         EXECUTE $$ DROP VIEW auditor.$$ || auditor_name || $$;$$;
13709     END LOOP;
13710     -- Fix all column discrepencies
13711     PERFORM auditor.fix_columns();
13712     -- Re-create trigger functions and lifecycle views
13713     FOR table_schema, table_name IN
13714         WITH audit_tables AS (
13715             SELECT c.oid AS audit_oid, c.relname AS audit_table
13716             FROM pg_catalog.pg_class c
13717             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13718             WHERE relkind='r' AND nspname = 'auditor'
13719         ),
13720         table_set AS (
13721             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
13722             FROM pg_catalog.pg_class c
13723             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13724             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
13725             WHERE relkind = 'r'
13726         )
13727         SELECT main_namespace, main_table FROM table_set LOOP
13728         
13729         PERFORM auditor.create_auditor_func(table_schema, table_name);
13730         PERFORM auditor.create_auditor_lifecycle(table_schema, table_name);
13731     END LOOP;
13732     RETURN TRUE;
13733 END;
13734 $BODY$ LANGUAGE plpgsql;
13735
13736 -- Go ahead and update them all now
13737 SELECT auditor.update_auditors();
13738
13739
13740 -- Evergreen DB patch 0687.schema.enhance_reingest.sql
13741 --
13742 -- FIXME: insert description of change, if needed
13743 --
13744
13745
13746 -- check whether patch can be applied
13747 SELECT evergreen.upgrade_deps_block_check('0687', :eg_version);
13748
13749 -- FIXME: add/check SQL statements to perform the upgrade
13750 -- New function def
13751 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$
13752 DECLARE
13753     fclass          RECORD;
13754     ind_data        metabib.field_entry_template%ROWTYPE;
13755     mbe_row         metabib.browse_entry%ROWTYPE;
13756     mbe_id          BIGINT;
13757 BEGIN
13758     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
13759     IF NOT FOUND THEN
13760         IF NOT skip_search THEN
13761             FOR fclass IN SELECT * FROM config.metabib_class LOOP
13762                 -- RAISE NOTICE 'Emptying out %', fclass.name;
13763                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
13764             END LOOP;
13765         END IF;
13766         IF NOT skip_facet THEN
13767             DELETE FROM metabib.facet_entry WHERE source = bib_id;
13768         END IF;
13769         IF NOT skip_browse THEN
13770             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
13771         END IF;
13772     END IF;
13773
13774     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
13775         IF ind_data.field < 0 THEN
13776             ind_data.field = -1 * ind_data.field;
13777         END IF;
13778
13779         IF ind_data.facet_field AND NOT skip_facet THEN
13780             INSERT INTO metabib.facet_entry (field, source, value)
13781                 VALUES (ind_data.field, ind_data.source, ind_data.value);
13782         END IF;
13783
13784         IF ind_data.browse_field AND NOT skip_browse THEN
13785             -- A caveat about this SELECT: this should take care of replacing
13786             -- old mbe rows when data changes, but not if normalization (by
13787             -- which I mean specifically the output of
13788             -- evergreen.oils_tsearch2()) changes.  It may or may not be
13789             -- expensive to add a comparison of index_vector to index_vector
13790             -- to the WHERE clause below.
13791             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
13792             IF FOUND THEN
13793                 mbe_id := mbe_row.id;
13794             ELSE
13795                 INSERT INTO metabib.browse_entry (value) VALUES
13796                     (metabib.browse_normalize(ind_data.value, ind_data.field));
13797                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
13798             END IF;
13799
13800             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
13801                 VALUES (mbe_id, ind_data.field, ind_data.source);
13802         END IF;
13803
13804         IF ind_data.search_field AND NOT skip_search THEN
13805             EXECUTE $$
13806                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
13807                     VALUES ($$ ||
13808                         quote_literal(ind_data.field) || $$, $$ ||
13809                         quote_literal(ind_data.source) || $$, $$ ||
13810                         quote_literal(ind_data.value) ||
13811                     $$);$$;
13812         END IF;
13813
13814     END LOOP;
13815
13816     RETURN;
13817 END;
13818 $func$ LANGUAGE PLPGSQL;
13819
13820 -- Delete old one
13821 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
13822
13823 -- Evergreen DB patch 0688.data.circ_history_export_csv.sql
13824 --
13825 -- FIXME: insert description of change, if needed
13826 --
13827
13828 -- check whether patch can be applied
13829 SELECT evergreen.upgrade_deps_block_check('0688', :eg_version);
13830
13831 INSERT INTO action_trigger.hook (key, core_type, description, passive)
13832 VALUES (
13833     'circ.format.history.csv',
13834     'circ',
13835     oils_i18n_gettext(
13836         'circ.format.history.csv',
13837         'Produce CSV of circulation history',
13838         'ath',
13839         'description'
13840     ),
13841     FALSE
13842 );
13843
13844 INSERT INTO action_trigger.event_definition (
13845     active, owner, name, hook, reactor, validator, group_field, template) 
13846 VALUES (
13847     TRUE, 1, 'Circ History CSV', 'circ.format.history.csv', 'ProcessTemplate', 'NOOP_True', 'usr',
13848 $$
13849 Title,Author,Call Number,Barcode,Format
13850 [%-
13851 FOR circ IN target;
13852     bibxml = helpers.unapi_bre(circ.target_copy.call_number.record, {flesh => '{mra}'});
13853     title = "";
13854     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
13855         title = title _ part.textContent;
13856     END;
13857     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
13858     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value') %]
13859
13860     [%- helpers.csv_datum(title) -%],
13861     [%- helpers.csv_datum(author) -%],
13862     [%- helpers.csv_datum(circ.target_copy.call_number.label) -%],
13863     [%- helpers.csv_datum(circ.target_copy.barcode) -%],
13864     [%- helpers.csv_datum(item_type) %]
13865 [%- END -%]
13866 $$
13867 );
13868
13869 INSERT INTO action_trigger.environment (event_def, path)
13870     VALUES (
13871         currval('action_trigger.event_definition_id_seq'),
13872         'target_copy.call_number'
13873     );
13874
13875
13876 -- Evergreen DB patch 0689.data.record_print_format_update.sql
13877 --
13878 -- Updates print and email templates for bib record actions
13879 --
13880
13881 -- check whether patch can be applied
13882 SELECT evergreen.upgrade_deps_block_check('0689', :eg_version);
13883
13884 UPDATE action_trigger.event_definition SET template = $$
13885 <div>
13886     <style> li { padding: 8px; margin 5px; }</style>
13887     <ol>
13888     [% FOR cbreb IN target %]
13889     [% FOR item IN cbreb.items;
13890         bre_id = item.target_biblio_record_entry;
13891
13892         bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
13893         FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
13894             title = title _ part.textContent;
13895         END;
13896
13897         author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
13898         item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
13899         publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
13900         pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
13901         isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
13902         issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
13903         upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
13904         %]
13905
13906         <li>
13907             Bib ID# [% bre_id %]<br/>
13908             [% IF isbn %]ISBN: [% isbn %]<br/>[% END %]
13909             [% IF issn %]ISSN: [% issn %]<br/>[% END %]
13910             [% IF upc  %]UPC:  [% upc %]<br/>[% END %]
13911             Title: [% title %]<br />
13912             Author: [% author %]<br />
13913             Publication Info: [% publisher %] [% pubdate %]<br/>
13914             Item Type: [% item_type %]
13915         </li>
13916     [% END %]
13917     [% END %]
13918     </ol>
13919 </div>
13920 $$ 
13921 WHERE hook = 'biblio.format.record_entry.print' AND id < 100; -- sample data
13922
13923
13924 UPDATE action_trigger.event_definition SET delay = '00:00:00', template = $$
13925 [%- SET user = target.0.owner -%]
13926 To: [%- params.recipient_email || user.email %]
13927 From: [%- params.sender_email || default_sender %]
13928 Subject: Bibliographic Records
13929
13930 [% FOR cbreb IN target %]
13931 [% FOR item IN cbreb.items;
13932     bre_id = item.target_biblio_record_entry;
13933
13934     bibxml = helpers.unapi_bre(bre_id, {flesh => '{mra}'});
13935     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
13936         title = title _ part.textContent;
13937     END;
13938
13939     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
13940     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
13941     publisher = bibxml.findnodes('//*[@tag="260"]/*[@code="b"]').textContent;
13942     pubdate = bibxml.findnodes('//*[@tag="260"]/*[@code="c"]').textContent;
13943     isbn = bibxml.findnodes('//*[@tag="020"]/*[@code="a"]').textContent;
13944     issn = bibxml.findnodes('//*[@tag="022"]/*[@code="a"]').textContent;
13945     upc = bibxml.findnodes('//*[@tag="024"]/*[@code="a"]').textContent;
13946 %]
13947
13948 [% loop.count %]/[% loop.size %].  Bib ID# [% bre_id %] 
13949 [% IF isbn %]ISBN: [% isbn _ "\n" %][% END -%]
13950 [% IF issn %]ISSN: [% issn _ "\n" %][% END -%]
13951 [% IF upc  %]UPC:  [% upc _ "\n" %] [% END -%]
13952 Title: [% title %]
13953 Author: [% author %]
13954 Publication Info: [% publisher %] [% pubdate %]
13955 Item Type: [% item_type %]
13956
13957 [% END %]
13958 [% END %]
13959 $$ 
13960 WHERE hook = 'biblio.format.record_entry.email' AND id < 100; -- sample data
13961
13962 -- remove a swath of unused environment entries
13963
13964 DELETE FROM action_trigger.environment env 
13965     USING action_trigger.event_definition def 
13966     WHERE env.event_def = def.id AND 
13967         env.path != 'items' AND 
13968         def.hook = 'biblio.format.record_entry.print' AND 
13969         def.id < 100; -- sample data
13970
13971 DELETE FROM action_trigger.environment env 
13972     USING action_trigger.event_definition def 
13973     WHERE env.event_def = def.id AND 
13974         env.path != 'items' AND 
13975         env.path != 'owner' AND 
13976         def.hook = 'biblio.format.record_entry.email' AND 
13977         def.id < 100; -- sample data
13978
13979 -- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
13980 --
13981 -- Rewrite the in-database unapi functions to include per-object limits and
13982 -- offsets, such as a maximum number of copies and call numbers for given
13983 -- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
13984 -- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
13985 -- call number).
13986 --
13987 -- Add some notion of "preferred library" that will provide copy counts
13988 -- and optionally affect the sorting of returned copies.
13989 --
13990 -- Sort copies by availability, preferring the most available copies.
13991 --
13992 -- Return located URIs.
13993 --
13994 --
13995
13996 -- check whether patch can be applied
13997 SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
13998
13999 -- The simplest way to apply all of these changes is just to replace the unapi
14000 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
14001 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
14002 -- if it does, then the attempt to create the unapi schema in the following
14003 -- transaction will also fail. Not graceful, but safe!
14004 DROP SCHEMA IF EXISTS unapi CASCADE;
14005
14006 CREATE SCHEMA unapi;
14007
14008 CREATE OR REPLACE FUNCTION evergreen.org_top()
14009 RETURNS SETOF actor.org_unit AS $$
14010     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
14011 $$ LANGUAGE SQL STABLE
14012 ROWS 1;
14013
14014 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
14015 RETURNS anyarray AS $$
14016     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
14017 $$ LANGUAGE SQL STABLE;
14018
14019 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
14020 RETURNS INTEGER AS $$
14021     WITH search_libs AS (
14022         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
14023     )
14024     SELECT COALESCE(
14025         (SELECT -10000 FROM actor.org_unit
14026          WHERE $1 = $3 AND id = $3 AND $2 IN (
14027                 SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
14028              )
14029         ),
14030         (SELECT distance FROM search_libs WHERE id = $1),
14031         10000
14032     );
14033 $$ LANGUAGE SQL STABLE;
14034
14035 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
14036 RETURNS INTEGER AS $$
14037     WITH totally_available AS (
14038         SELECT id, 0 AS avail_rank
14039         FROM config.copy_status
14040         WHERE opac_visible IS TRUE
14041             AND copy_active IS TRUE
14042             AND id != 1 -- "Checked out"
14043     ), almost_available AS (
14044         SELECT id, 10 AS avail_rank
14045         FROM config.copy_status
14046         WHERE holdable IS TRUE
14047             AND opac_visible IS TRUE
14048             AND copy_active IS FALSE
14049             OR id = 1 -- "Checked out"
14050     )
14051     SELECT COALESCE(
14052         (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
14053         (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
14054         100
14055     );
14056 $$ LANGUAGE SQL STABLE;
14057
14058 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
14059     bibid BIGINT, 
14060     ouid INT,
14061     depth INT DEFAULT NULL,
14062     slimit HSTORE DEFAULT NULL,
14063     soffset HSTORE DEFAULT NULL,
14064     pref_lib INT DEFAULT NULL
14065 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
14066     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
14067         SELECT acn.id, aou.name, acn.label_sortkey,
14068             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
14069             RANK() OVER w
14070         FROM asset.call_number acn
14071             JOIN asset.copy acp ON (acn.id = acp.call_number)
14072             JOIN actor.org_unit_descendants( $2, COALESCE(
14073                 $3, (
14074                     SELECT depth
14075                     FROM actor.org_unit_type aout
14076                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
14077                     WHERE ou.id = $2
14078                 ), $6)
14079             ) AS aou ON (acp.circ_lib = aou.id)
14080         WHERE acn.record = $1
14081             AND acn.deleted IS FALSE
14082             AND acp.deleted IS FALSE
14083         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
14084         WINDOW w AS (
14085             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
14086         )
14087     ) AS ua
14088     GROUP BY ua.id, ua.name, ua.label_sortkey
14089     ORDER BY rank, ua.name, ua.label_sortkey
14090     LIMIT ($4 -> 'acn')::INT
14091     OFFSET ($5 -> 'acn')::INT;
14092 $$
14093 LANGUAGE SQL STABLE;
14094
14095 CREATE OR REPLACE FUNCTION evergreen.located_uris (
14096     bibid BIGINT, 
14097     ouid INT,
14098     pref_lib INT DEFAULT NULL
14099 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
14100     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
14101       FROM asset.call_number acn
14102            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
14103            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
14104            INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
14105       WHERE acn.record = $1
14106           AND acn.deleted IS FALSE
14107           AND auri.active IS TRUE
14108     UNION
14109     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
14110       FROM asset.call_number acn
14111            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
14112            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
14113            INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
14114       WHERE acn.record = $1
14115           AND acn.deleted IS FALSE
14116           AND auri.active IS TRUE;
14117 $$
14118 LANGUAGE SQL STABLE;
14119
14120 CREATE TABLE unapi.bre_output_layout (
14121     name                TEXT    PRIMARY KEY,
14122     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
14123     mime_type           TEXT    NOT NULL,
14124     feed_top            TEXT    NOT NULL,
14125     holdings_element    TEXT,
14126     title_element       TEXT,
14127     description_element TEXT,
14128     creator_element     TEXT,
14129     update_ts_element   TEXT
14130 );
14131
14132 INSERT INTO unapi.bre_output_layout
14133     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
14134         VALUES
14135     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
14136     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
14137     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
14138 ;
14139
14140 -- Dummy functions, so we can create the real ones out of order
14141 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;
14142 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;
14143 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;
14144 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;
14145 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;
14146 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;
14147 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;
14148 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;
14149 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;
14150 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;
14151 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;
14152 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;
14153 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;
14154 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;
14155 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;
14156 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;
14157 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;
14158 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;
14159 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;
14160 CREATE OR REPLACE FUNCTION unapi.bre (
14161     obj_id BIGINT,
14162     format TEXT,
14163     ename TEXT,
14164     includes TEXT[],
14165     org TEXT,
14166     depth INT DEFAULT NULL,
14167     slimit HSTORE DEFAULT NULL,
14168     soffset HSTORE DEFAULT NULL,
14169     include_xmlns BOOL DEFAULT TRUE,
14170     pref_lib INT DEFAULT NULL
14171 )
14172 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
14173 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;
14174 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;
14175 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;
14176
14177 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
14178     bid BIGINT,
14179     ouid INT,
14180     org TEXT,
14181     depth INT DEFAULT NULL,
14182     includes TEXT[] DEFAULT NULL::TEXT[],
14183     slimit HSTORE DEFAULT NULL,
14184     soffset HSTORE DEFAULT NULL,
14185     include_xmlns BOOL DEFAULT TRUE,
14186     pref_lib INT DEFAULT NULL
14187 )
14188 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
14189
14190 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;
14191
14192 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$
14193 DECLARE
14194     key     TEXT;
14195     output  XML;
14196 BEGIN
14197     key :=
14198         'id'        || COALESCE(obj_id::TEXT,'') ||
14199         'format'    || COALESCE(format::TEXT,'') ||
14200         'ename'     || COALESCE(ename::TEXT,'') ||
14201         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
14202         'org'       || COALESCE(org::TEXT,'') ||
14203         'depth'     || COALESCE(depth::TEXT,'') ||
14204         'slimit'    || COALESCE(slimit::TEXT,'') ||
14205         'soffset'   || COALESCE(soffset::TEXT,'') ||
14206         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
14207     -- RAISE NOTICE 'memoize key: %', key;
14208
14209     key := MD5(key);
14210     -- RAISE NOTICE 'memoize hash: %', key;
14211
14212     -- XXX cache logic ... memcached? table?
14213
14214     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;
14215     RETURN output;
14216 END;
14217 $F$ LANGUAGE PLPGSQL STABLE;
14218
14219 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$
14220 DECLARE
14221     layout          unapi.bre_output_layout%ROWTYPE;
14222     transform       config.xml_transform%ROWTYPE;
14223     item_format     TEXT;
14224     tmp_xml         TEXT;
14225     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
14226     ouid            INT;
14227     element_list    TEXT[];
14228 BEGIN
14229
14230     IF org = '-' OR org IS NULL THEN
14231         SELECT shortname INTO org FROM evergreen.org_top();
14232     END IF;
14233
14234     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
14235     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
14236
14237     IF layout.name IS NULL THEN
14238         RETURN NULL::XML;
14239     END IF;
14240
14241     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
14242     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
14243
14244     -- Gather the bib xml
14245     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
14246
14247     IF layout.title_element IS NOT NULL THEN
14248         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
14249     END IF;
14250
14251     IF layout.description_element IS NOT NULL THEN
14252         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
14253     END IF;
14254
14255     IF layout.creator_element IS NOT NULL THEN
14256         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
14257     END IF;
14258
14259     IF layout.update_ts_element IS NOT NULL THEN
14260         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;
14261     END IF;
14262
14263     IF unapi_url IS NOT NULL THEN
14264         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;
14265     END IF;
14266
14267     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
14268
14269     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
14270     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
14271         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
14272     END LOOP;
14273
14274     RETURN tmp_xml::XML;
14275 END;
14276 $F$ LANGUAGE PLPGSQL STABLE;
14277
14278 CREATE OR REPLACE FUNCTION unapi.bre (
14279     obj_id BIGINT,
14280     format TEXT,
14281     ename TEXT,
14282     includes TEXT[],
14283     org TEXT,
14284     depth INT DEFAULT NULL,
14285     slimit HSTORE DEFAULT NULL,
14286     soffset HSTORE DEFAULT NULL,
14287     include_xmlns BOOL DEFAULT TRUE,
14288     pref_lib INT DEFAULT NULL
14289 )
14290 RETURNS XML AS $F$
14291 DECLARE
14292     me      biblio.record_entry%ROWTYPE;
14293     layout  unapi.bre_output_layout%ROWTYPE;
14294     xfrm    config.xml_transform%ROWTYPE;
14295     ouid    INT;
14296     tmp_xml TEXT;
14297     top_el  TEXT;
14298     output  XML;
14299     hxml    XML;
14300     axml    XML;
14301 BEGIN
14302
14303     IF org = '-' OR org IS NULL THEN
14304         SELECT shortname INTO org FROM evergreen.org_top();
14305     END IF;
14306
14307     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
14308
14309     IF ouid IS NULL THEN
14310         RETURN NULL::XML;
14311     END IF;
14312
14313     IF format = 'holdings_xml' THEN -- the special case
14314         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
14315         RETURN output;
14316     END IF;
14317
14318     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
14319
14320     IF layout.name IS NULL THEN
14321         RETURN NULL::XML;
14322     END IF;
14323
14324     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
14325
14326     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
14327
14328     -- grab SVF if we need them
14329     IF ('mra' = ANY (includes)) THEN 
14330         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
14331     ELSE
14332         axml := NULL::XML;
14333     END IF;
14334
14335     -- grab holdings if we need them
14336     IF ('holdings_xml' = ANY (includes)) THEN 
14337         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
14338     ELSE
14339         hxml := NULL::XML;
14340     END IF;
14341
14342
14343     -- generate our item node
14344
14345
14346     IF format = 'marcxml' THEN
14347         tmp_xml := me.marc;
14348         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
14349            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
14350         END IF; 
14351     ELSE
14352         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
14353     END IF;
14354
14355     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
14356
14357     IF axml IS NOT NULL THEN 
14358         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
14359     END IF;
14360
14361     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
14362         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
14363     END IF;
14364
14365     IF ('bre.unapi' = ANY (includes)) THEN 
14366         output := REGEXP_REPLACE(
14367             tmp_xml,
14368             '</' || top_el || '>(.*?)',
14369             XMLELEMENT(
14370                 name abbr,
14371                 XMLATTRIBUTES(
14372                     'http://www.w3.org/1999/xhtml' AS xmlns,
14373                     'unapi-id' AS class,
14374                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
14375                 )
14376             )::TEXT || '</' || top_el || E'>\\1'
14377         );
14378     ELSE
14379         output := tmp_xml;
14380     END IF;
14381
14382     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
14383     RETURN output;
14384 END;
14385 $F$ LANGUAGE PLPGSQL STABLE;
14386
14387 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
14388     bid BIGINT,
14389     ouid INT,
14390     org TEXT,
14391     depth INT DEFAULT NULL,
14392     includes TEXT[] DEFAULT NULL::TEXT[],
14393     slimit HSTORE DEFAULT NULL,
14394     soffset HSTORE DEFAULT NULL,
14395     include_xmlns BOOL DEFAULT TRUE,
14396     pref_lib INT DEFAULT NULL
14397 )
14398 RETURNS XML AS $F$
14399      SELECT  XMLELEMENT(
14400                  name holdings,
14401                  XMLATTRIBUTES(
14402                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14403                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
14404                  ),
14405                  XMLELEMENT(
14406                      name counts,
14407                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
14408                          SELECT  XMLELEMENT(
14409                                      name count,
14410                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14411                                  )::text
14412                            FROM  asset.opac_ou_record_copy_count($2,  $1)
14413                                      UNION
14414                          SELECT  XMLELEMENT(
14415                                      name count,
14416                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14417                                  )::text
14418                            FROM  asset.staff_ou_record_copy_count($2, $1)
14419                                      UNION
14420                          SELECT  XMLELEMENT(
14421                                      name count,
14422                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14423                                  )::text
14424                            FROM  asset.opac_ou_record_copy_count($9,  $1)
14425                                      ORDER BY 1
14426                      )x)
14427                  ),
14428                  CASE 
14429                      WHEN ('bmp' = ANY ($5)) THEN
14430                         XMLELEMENT(
14431                             name monograph_parts,
14432                             (SELECT XMLAGG(bmp) FROM (
14433                                 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)
14434                                   FROM  biblio.monograph_part
14435                                   WHERE record = $1
14436                             )x)
14437                         )
14438                      ELSE NULL
14439                  END,
14440                  XMLELEMENT(
14441                      name volumes,
14442                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
14443                         -- Physical copies
14444                         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
14445                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
14446                         UNION ALL
14447                         -- Located URIs
14448                         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
14449                         FROM evergreen.located_uris($1, $2, $9) AS uris
14450                      )x)
14451                  ),
14452                  CASE WHEN ('ssub' = ANY ($5)) THEN 
14453                      XMLELEMENT(
14454                          name subscriptions,
14455                          (SELECT XMLAGG(ssub) FROM (
14456                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
14457                               FROM  serial.subscription
14458                               WHERE record_entry = $1
14459                         )x)
14460                      )
14461                  ELSE NULL END,
14462                  CASE WHEN ('acp' = ANY ($5)) THEN 
14463                      XMLELEMENT(
14464                          name foreign_copies,
14465                          (SELECT XMLAGG(acp) FROM (
14466                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
14467                               FROM  biblio.peer_bib_copy_map p
14468                                     JOIN asset.copy c ON (p.target_copy = c.id)
14469                               WHERE NOT c.deleted AND p.peer_record = $1
14470                             LIMIT ($6 -> 'acp')::INT
14471                             OFFSET ($7 -> 'acp')::INT
14472                         )x)
14473                      )
14474                  ELSE NULL END
14475              );
14476 $F$ LANGUAGE SQL STABLE;
14477
14478 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$
14479         SELECT  XMLELEMENT(
14480                     name subscription,
14481                     XMLATTRIBUTES(
14482                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14483                         'tag:open-ils.org:U2@ssub/' || id AS id,
14484                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
14485                         start_date AS start, end_date AS end, expected_date_offset
14486                     ),
14487                     CASE 
14488                         WHEN ('sdist' = ANY ($4)) THEN
14489                             XMLELEMENT( name distributions,
14490                                 (SELECT XMLAGG(sdist) FROM (
14491                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
14492                                       FROM  serial.distribution
14493                                       WHERE subscription = ssub.id
14494                                 )x)
14495                             )
14496                         ELSE NULL
14497                     END
14498                 )
14499           FROM  serial.subscription ssub
14500           WHERE id = $1
14501           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
14502 $F$ LANGUAGE SQL STABLE;
14503
14504 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$
14505         SELECT  XMLELEMENT(
14506                     name distribution,
14507                     XMLATTRIBUTES(
14508                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14509                         'tag:open-ils.org:U2@sdist/' || id AS id,
14510                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
14511                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
14512                         unit_label_prefix, label, unit_label_suffix, summary_method
14513                     ),
14514                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
14515                     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,
14516                     CASE 
14517                         WHEN ('sstr' = ANY ($4)) THEN
14518                             XMLELEMENT( name streams,
14519                                 (SELECT XMLAGG(sstr) FROM (
14520                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14521                                       FROM  serial.stream
14522                                       WHERE distribution = sdist.id
14523                                 )x)
14524                             )
14525                         ELSE NULL
14526                     END,
14527                     XMLELEMENT( name summaries,
14528                         CASE 
14529                             WHEN ('sbsum' = ANY ($4)) THEN
14530                                 (SELECT XMLAGG(sbsum) FROM (
14531                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14532                                       FROM  serial.basic_summary
14533                                       WHERE distribution = sdist.id
14534                                 )x)
14535                             ELSE NULL
14536                         END,
14537                         CASE 
14538                             WHEN ('sisum' = ANY ($4)) THEN
14539                                 (SELECT XMLAGG(sisum) FROM (
14540                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14541                                       FROM  serial.index_summary
14542                                       WHERE distribution = sdist.id
14543                                 )x)
14544                             ELSE NULL
14545                         END,
14546                         CASE 
14547                             WHEN ('sssum' = ANY ($4)) THEN
14548                                 (SELECT XMLAGG(sssum) FROM (
14549                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14550                                       FROM  serial.supplement_summary
14551                                       WHERE distribution = sdist.id
14552                                 )x)
14553                             ELSE NULL
14554                         END
14555                     )
14556                 )
14557           FROM  serial.distribution sdist
14558           WHERE id = $1
14559           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
14560 $F$ LANGUAGE SQL STABLE;
14561
14562 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$
14563     SELECT  XMLELEMENT(
14564                 name stream,
14565                 XMLATTRIBUTES(
14566                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14567                     'tag:open-ils.org:U2@sstr/' || id AS id,
14568                     routing_label
14569                 ),
14570                 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,
14571                 CASE 
14572                     WHEN ('sitem' = ANY ($4)) THEN
14573                         XMLELEMENT( name items,
14574                             (SELECT XMLAGG(sitem) FROM (
14575                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
14576                                   FROM  serial.item
14577                                   WHERE stream = sstr.id
14578                             )x)
14579                         )
14580                     ELSE NULL
14581                 END
14582             )
14583       FROM  serial.stream sstr
14584       WHERE id = $1
14585       GROUP BY id, routing_label, distribution;
14586 $F$ LANGUAGE SQL STABLE;
14587
14588 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$
14589     SELECT  XMLELEMENT(
14590                 name issuance,
14591                 XMLATTRIBUTES(
14592                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14593                     'tag:open-ils.org:U2@siss/' || id AS id,
14594                     create_date, edit_date, label, date_published,
14595                     holding_code, holding_type, holding_link_id
14596                 ),
14597                 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,
14598                 CASE 
14599                     WHEN ('sitem' = ANY ($4)) THEN
14600                         XMLELEMENT( name items,
14601                             (SELECT XMLAGG(sitem) FROM (
14602                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
14603                                   FROM  serial.item
14604                                   WHERE issuance = sstr.id
14605                             )x)
14606                         )
14607                     ELSE NULL
14608                 END
14609             )
14610       FROM  serial.issuance sstr
14611       WHERE id = $1
14612       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
14613 $F$ LANGUAGE SQL STABLE;
14614
14615 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$
14616         SELECT  XMLELEMENT(
14617                     name serial_item,
14618                     XMLATTRIBUTES(
14619                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14620                         'tag:open-ils.org:U2@sitem/' || id AS id,
14621                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
14622                         date_expected, date_received
14623                     ),
14624                     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,
14625                     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,
14626                     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,
14627                     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
14628 --                    XMLELEMENT( name notes,
14629 --                        CASE 
14630 --                            WHEN ('acpn' = ANY ($4)) THEN
14631 --                                (SELECT XMLAGG(acpn) FROM (
14632 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
14633 --                                      FROM  asset.copy_note
14634 --                                      WHERE owning_copy = cp.id AND pub
14635 --                                )x)
14636 --                            ELSE NULL
14637 --                        END
14638 --                    )
14639                 )
14640           FROM  serial.item sitem
14641           WHERE id = $1;
14642 $F$ LANGUAGE SQL STABLE;
14643
14644
14645 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$
14646     SELECT  XMLELEMENT(
14647                 name serial_summary,
14648                 XMLATTRIBUTES(
14649                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14650                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14651                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
14652                 ),
14653                 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
14654             )
14655       FROM  serial.supplement_summary ssum
14656       WHERE id = $1
14657       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14658 $F$ LANGUAGE SQL STABLE;
14659
14660 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$
14661     SELECT  XMLELEMENT(
14662                 name serial_summary,
14663                 XMLATTRIBUTES(
14664                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14665                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14666                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
14667                 ),
14668                 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
14669             )
14670       FROM  serial.basic_summary ssum
14671       WHERE id = $1
14672       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14673 $F$ LANGUAGE SQL STABLE;
14674
14675 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$
14676     SELECT  XMLELEMENT(
14677                 name serial_summary,
14678                 XMLATTRIBUTES(
14679                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14680                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14681                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
14682                 ),
14683                 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
14684             )
14685       FROM  serial.index_summary ssum
14686       WHERE id = $1
14687       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14688 $F$ LANGUAGE SQL STABLE;
14689
14690
14691 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$
14692 DECLARE
14693     output XML;
14694 BEGIN
14695     IF ename = 'circlib' THEN
14696         SELECT  XMLELEMENT(
14697                     name circlib,
14698                     XMLATTRIBUTES(
14699                         'http://open-ils.org/spec/actors/v1' AS xmlns,
14700                         id AS ident
14701                     ),
14702                     name
14703                 ) INTO output
14704           FROM  actor.org_unit aou
14705           WHERE id = obj_id;
14706     ELSE
14707         EXECUTE $$SELECT  XMLELEMENT(
14708                     name $$ || ename || $$,
14709                     XMLATTRIBUTES(
14710                         'http://open-ils.org/spec/actors/v1' AS xmlns,
14711                         'tag:open-ils.org:U2@aou/' || id AS id,
14712                         shortname, name, opac_visible
14713                     )
14714                 )
14715           FROM  actor.org_unit aou
14716          WHERE id = $1 $$ INTO output USING obj_id;
14717     END IF;
14718
14719     RETURN output;
14720
14721 END;
14722 $F$ LANGUAGE PLPGSQL STABLE;
14723
14724 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$
14725     SELECT  XMLELEMENT(
14726                 name location,
14727                 XMLATTRIBUTES(
14728                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14729                     id AS ident,
14730                     holdable,
14731                     opac_visible,
14732                     label_prefix AS prefix,
14733                     label_suffix AS suffix
14734                 ),
14735                 name
14736             )
14737       FROM  asset.copy_location
14738       WHERE id = $1;
14739 $F$ LANGUAGE SQL STABLE;
14740
14741 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$
14742     SELECT  XMLELEMENT(
14743                 name status,
14744                 XMLATTRIBUTES(
14745                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14746                     id AS ident,
14747                     holdable,
14748                     opac_visible
14749                 ),
14750                 name
14751             )
14752       FROM  config.copy_status
14753       WHERE id = $1;
14754 $F$ LANGUAGE SQL STABLE;
14755
14756 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$
14757         SELECT  XMLELEMENT(
14758                     name copy_note,
14759                     XMLATTRIBUTES(
14760                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14761                         create_date AS date,
14762                         title
14763                     ),
14764                     value
14765                 )
14766           FROM  asset.copy_note
14767           WHERE id = $1;
14768 $F$ LANGUAGE SQL STABLE;
14769
14770 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$
14771         SELECT  XMLELEMENT(
14772                     name statcat,
14773                     XMLATTRIBUTES(
14774                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14775                         sc.name,
14776                         sc.opac_visible
14777                     ),
14778                     asce.value
14779                 )
14780           FROM  asset.stat_cat_entry asce
14781                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
14782           WHERE asce.id = $1;
14783 $F$ LANGUAGE SQL STABLE;
14784
14785 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$
14786         SELECT  XMLELEMENT(
14787                     name monograph_part,
14788                     XMLATTRIBUTES(
14789                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14790                         'tag:open-ils.org:U2@bmp/' || id AS id,
14791                         id AS ident,
14792                         label,
14793                         label_sortkey,
14794                         'tag:open-ils.org:U2@bre/' || record AS record
14795                     ),
14796                     CASE 
14797                         WHEN ('acp' = ANY ($4)) THEN
14798                             XMLELEMENT( name copies,
14799                                 (SELECT XMLAGG(acp) FROM (
14800                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
14801                                       FROM  asset.copy cp
14802                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
14803                                       WHERE cpm.part = $1
14804                                           AND cp.deleted IS FALSE
14805                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
14806                                       LIMIT ($7 -> 'acp')::INT
14807                                       OFFSET ($8 -> 'acp')::INT
14808
14809                                 )x)
14810                             )
14811                         ELSE NULL
14812                     END,
14813                     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
14814                 )
14815           FROM  biblio.monograph_part
14816           WHERE id = $1
14817           GROUP BY id, label, label_sortkey, record;
14818 $F$ LANGUAGE SQL STABLE;
14819
14820 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$
14821         SELECT  XMLELEMENT(
14822                     name copy,
14823                     XMLATTRIBUTES(
14824                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14825                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
14826                         create_date, edit_date, copy_number, circulate, deposit,
14827                         ref, holdable, deleted, deposit_amount, price, barcode,
14828                         circ_modifier, circ_as_type, opac_visible, age_protect
14829                     ),
14830                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
14831                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
14832                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
14833                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
14834                     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,
14835                     CASE 
14836                         WHEN ('acpn' = ANY ($4)) THEN
14837                             XMLELEMENT( name copy_notes,
14838                                 (SELECT XMLAGG(acpn) FROM (
14839                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14840                                       FROM  asset.copy_note
14841                                       WHERE owning_copy = cp.id AND pub
14842                                 )x)
14843                             )
14844                         ELSE NULL
14845                     END,
14846                     CASE 
14847                         WHEN ('ascecm' = ANY ($4)) THEN
14848                             XMLELEMENT( name statcats,
14849                                 (SELECT XMLAGG(ascecm) FROM (
14850                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14851                                       FROM  asset.stat_cat_entry_copy_map
14852                                       WHERE owning_copy = cp.id
14853                                 )x)
14854                             )
14855                         ELSE NULL
14856                     END,
14857                     CASE
14858                         WHEN ('bre' = ANY ($4)) THEN
14859                             XMLELEMENT( name foreign_records,
14860                                 (SELECT XMLAGG(bre) FROM (
14861                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
14862                                       FROM  biblio.peer_bib_copy_map
14863                                       WHERE target_copy = cp.id
14864                                 )x)
14865
14866                             )
14867                         ELSE NULL
14868                     END,
14869                     CASE 
14870                         WHEN ('bmp' = ANY ($4)) THEN
14871                             XMLELEMENT( name monograph_parts,
14872                                 (SELECT XMLAGG(bmp) FROM (
14873                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14874                                       FROM  asset.copy_part_map
14875                                       WHERE target_copy = cp.id
14876                                 )x)
14877                             )
14878                         ELSE NULL
14879                     END,
14880                     CASE 
14881                         WHEN ('circ' = ANY ($4)) THEN
14882                             XMLELEMENT( name current_circulation,
14883                                 (SELECT XMLAGG(circ) FROM (
14884                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
14885                                       FROM  action.circulation
14886                                       WHERE target_copy = cp.id
14887                                             AND checkin_time IS NULL
14888                                 )x)
14889                             )
14890                         ELSE NULL
14891                     END
14892                 )
14893           FROM  asset.copy cp
14894           WHERE id = $1
14895               AND cp.deleted IS FALSE
14896           GROUP BY id, status, location, circ_lib, call_number, create_date,
14897               edit_date, copy_number, circulate, deposit, ref, holdable,
14898               deleted, deposit_amount, price, barcode, circ_modifier,
14899               circ_as_type, opac_visible, age_protect;
14900 $F$ LANGUAGE SQL STABLE;
14901
14902 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$
14903         SELECT  XMLELEMENT(
14904                     name serial_unit,
14905                     XMLATTRIBUTES(
14906                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14907                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
14908                         create_date, edit_date, copy_number, circulate, deposit,
14909                         ref, holdable, deleted, deposit_amount, price, barcode,
14910                         circ_modifier, circ_as_type, opac_visible, age_protect,
14911                         status_changed_time, floating, mint_condition,
14912                         detailed_contents, sort_key, summary_contents, cost 
14913                     ),
14914                     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),
14915                     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),
14916                     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),
14917                     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),
14918                     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,
14919                     XMLELEMENT( name copy_notes,
14920                         CASE 
14921                             WHEN ('acpn' = ANY ($4)) THEN
14922                                 (SELECT XMLAGG(acpn) FROM (
14923                                     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)
14924                                       FROM  asset.copy_note
14925                                       WHERE owning_copy = cp.id AND pub
14926                                 )x)
14927                             ELSE NULL
14928                         END
14929                     ),
14930                     XMLELEMENT( name statcats,
14931                         CASE 
14932                             WHEN ('ascecm' = ANY ($4)) THEN
14933                                 (SELECT XMLAGG(ascecm) FROM (
14934                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14935                                       FROM  asset.stat_cat_entry_copy_map
14936                                       WHERE owning_copy = cp.id
14937                                 )x)
14938                             ELSE NULL
14939                         END
14940                     ),
14941                     XMLELEMENT( name foreign_records,
14942                         CASE
14943                             WHEN ('bre' = ANY ($4)) THEN
14944                                 (SELECT XMLAGG(bre) FROM (
14945                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
14946                                       FROM  biblio.peer_bib_copy_map
14947                                       WHERE target_copy = cp.id
14948                                 )x)
14949                             ELSE NULL
14950                         END
14951                     ),
14952                     CASE 
14953                         WHEN ('bmp' = ANY ($4)) THEN
14954                             XMLELEMENT( name monograph_parts,
14955                                 (SELECT XMLAGG(bmp) FROM (
14956                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14957                                       FROM  asset.copy_part_map
14958                                       WHERE target_copy = cp.id
14959                                 )x)
14960                             )
14961                         ELSE NULL
14962                     END,
14963                     CASE 
14964                         WHEN ('circ' = ANY ($4)) THEN
14965                             XMLELEMENT( name current_circulation,
14966                                 (SELECT XMLAGG(circ) FROM (
14967                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
14968                                       FROM  action.circulation
14969                                       WHERE target_copy = cp.id
14970                                             AND checkin_time IS NULL
14971                                 )x)
14972                             )
14973                         ELSE NULL
14974                     END
14975                 )
14976           FROM  serial.unit cp
14977           WHERE id = $1
14978               AND cp.deleted IS FALSE
14979           GROUP BY id, status, location, circ_lib, call_number, create_date,
14980               edit_date, copy_number, circulate, floating, mint_condition,
14981               deposit, ref, holdable, deleted, deposit_amount, price,
14982               barcode, circ_modifier, circ_as_type, opac_visible,
14983               status_changed_time, detailed_contents, sort_key,
14984               summary_contents, cost, age_protect;
14985 $F$ LANGUAGE SQL STABLE;
14986
14987 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$
14988         SELECT  XMLELEMENT(
14989                     name volume,
14990                     XMLATTRIBUTES(
14991                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14992                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
14993                         acn.id AS vol_id, o.shortname AS lib,
14994                         o.opac_visible AS opac_visible,
14995                         deleted, label, label_sortkey, label_class, record
14996                     ),
14997                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
14998                     CASE 
14999                         WHEN ('acp' = ANY ($4)) THEN
15000                             CASE WHEN $6 IS NOT NULL THEN
15001                                 XMLELEMENT( name copies,
15002                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
15003                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
15004                                             evergreen.rank_cp_status(cp.status) AS rank_avail
15005                                           FROM  asset.copy cp
15006                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
15007                                           WHERE cp.call_number = acn.id
15008                                               AND cp.deleted IS FALSE
15009                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
15010                                           LIMIT ($7 -> 'acp')::INT
15011                                           OFFSET ($8 -> 'acp')::INT
15012                                     )x)
15013                                 )
15014                             ELSE
15015                                 XMLELEMENT( name copies,
15016                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
15017                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
15018                                             evergreen.rank_cp_status(cp.status) AS rank_avail
15019                                           FROM  asset.copy cp
15020                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
15021                                           WHERE cp.call_number = acn.id
15022                                               AND cp.deleted IS FALSE
15023                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
15024                                           LIMIT ($7 -> 'acp')::INT
15025                                           OFFSET ($8 -> 'acp')::INT
15026                                     )x)
15027                                 )
15028                             END
15029                         ELSE NULL
15030                     END,
15031                     XMLELEMENT(
15032                         name uris,
15033                         (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)
15034                     ),
15035                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
15036                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
15037                     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
15038                 ) AS x
15039           FROM  asset.call_number acn
15040                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
15041           WHERE acn.id = $1
15042               AND acn.deleted IS FALSE
15043           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
15044 $F$ LANGUAGE SQL STABLE;
15045
15046 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$
15047         SELECT  XMLELEMENT(
15048                     name call_number_prefix,
15049                     XMLATTRIBUTES(
15050                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
15051                         id AS ident,
15052                         label,
15053                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
15054                         label_sortkey
15055                     )
15056                 )
15057           FROM  asset.call_number_prefix
15058           WHERE id = $1;
15059 $F$ LANGUAGE SQL STABLE;
15060
15061 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$
15062         SELECT  XMLELEMENT(
15063                     name call_number_suffix,
15064                     XMLATTRIBUTES(
15065                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
15066                         id AS ident,
15067                         label,
15068                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
15069                         label_sortkey
15070                     )
15071                 )
15072           FROM  asset.call_number_suffix
15073           WHERE id = $1;
15074 $F$ LANGUAGE SQL STABLE;
15075
15076 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$
15077         SELECT  XMLELEMENT(
15078                     name uri,
15079                     XMLATTRIBUTES(
15080                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
15081                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
15082                         use_restriction,
15083                         href,
15084                         label
15085                     ),
15086                     CASE 
15087                         WHEN ('acn' = ANY ($4)) THEN
15088                             XMLELEMENT( name copies,
15089                                 (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)
15090                             )
15091                         ELSE NULL
15092                     END
15093                 ) AS x
15094           FROM  asset.uri uri
15095           WHERE uri.id = $1
15096           GROUP BY uri.id, use_restriction, href, label;
15097 $F$ LANGUAGE SQL STABLE;
15098
15099 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$
15100         SELECT  XMLELEMENT(
15101                     name attributes,
15102                     XMLATTRIBUTES(
15103                         CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
15104                         'tag:open-ils.org:U2@mra/' || mra.id AS id,
15105                         'tag:open-ils.org:U2@bre/' || mra.id AS record
15106                     ),
15107                     (SELECT XMLAGG(foo.y)
15108                       FROM (SELECT XMLELEMENT(
15109                                 name field,
15110                                 XMLATTRIBUTES(
15111                                     key AS name,
15112                                     cvm.value AS "coded-value",
15113                                     rad.filter,
15114                                     rad.sorter
15115                                 ),
15116                                 x.value
15117                             )
15118                            FROM EACH(mra.attrs) AS x
15119                                 JOIN config.record_attr_definition rad ON (x.key = rad.name)
15120                                 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
15121                         )foo(y)
15122                     )
15123                 )
15124           FROM  metabib.record_attr mra
15125           WHERE mra.id = $1;
15126 $F$ LANGUAGE SQL STABLE;
15127
15128 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$
15129     SELECT XMLELEMENT(
15130         name circ,
15131         XMLATTRIBUTES(
15132             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
15133             'tag:open-ils.org:U2@circ/' || id AS id,
15134             xact_start,
15135             due_date
15136         ),
15137         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,
15138         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
15139     )
15140     FROM action.circulation
15141     WHERE id = $1;
15142 $F$ LANGUAGE SQL STABLE;
15143
15144 /*
15145
15146  -- Some test queries
15147
15148 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
15149 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
15150 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
15151
15152 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>');
15153
15154 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>');
15155 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>');
15156 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>');
15157 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>');
15158
15159 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
15160 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
15161 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
15162 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
15163 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
15164
15165 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
15166 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
15167
15168 */
15169
15170
15171 SELECT evergreen.upgrade_deps_block_check('0691', :eg_version);
15172
15173 CREATE INDEX poi_po_idx ON acq.po_item (purchase_order);
15174
15175 CREATE INDEX ie_inv_idx on acq.invoice_entry (invoice);
15176 CREATE INDEX ie_po_idx on acq.invoice_entry (purchase_order);
15177 CREATE INDEX ie_li_idx on acq.invoice_entry (lineitem);
15178
15179 CREATE INDEX ii_inv_idx on acq.invoice_item (invoice);
15180 CREATE INDEX ii_po_idx on acq.invoice_item (purchase_order);
15181 CREATE INDEX ii_poi_idx on acq.invoice_item (po_item);
15182
15183
15184 SELECT evergreen.upgrade_deps_block_check('0692', :eg_version);
15185
15186 INSERT INTO config.org_unit_setting_type
15187     (name, label, description, grp, datatype)
15188     VALUES (
15189         'circ.fines.charge_when_closed',
15190          oils_i18n_gettext(
15191             'circ.fines.charge_when_closed',
15192             'Charge fines on overdue circulations when closed',
15193             'coust',
15194             'label'
15195         ),
15196         oils_i18n_gettext(
15197             'circ.fines.charge_when_closed',
15198             '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.',
15199             'coust',
15200             'description'
15201         ),
15202         'circ',
15203         'bool'
15204     );
15205
15206 SELECT evergreen.upgrade_deps_block_check('0694', :eg_version);
15207
15208 INSERT into config.org_unit_setting_type
15209 ( name, grp, label, description, datatype, fm_class ) VALUES
15210
15211 ( 'ui.patron.edit.au.prefix.require', 'gui',
15212     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
15213         'Require prefix field on patron registration',
15214         'coust', 'label'),
15215     oils_i18n_gettext('ui.patron.edit.au.prefix.require',
15216         'The prefix field will be required on the patron registration screen.',
15217         'coust', 'description'),
15218     'bool', null)
15219         
15220 ,( 'ui.patron.edit.au.prefix.show', 'gui',
15221     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
15222         'Show prefix field on patron registration',
15223         'coust', 'label'),
15224     oils_i18n_gettext('ui.patron.edit.au.prefix.show',
15225         '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.',
15226         'coust', 'description'),
15227     'bool', null)
15228
15229 ,( 'ui.patron.edit.au.prefix.suggest', 'gui',
15230     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
15231         'Suggest prefix field on patron registration',
15232         'coust', 'label'),
15233     oils_i18n_gettext('ui.patron.edit.au.prefix.suggest',
15234         '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.',
15235         'coust', 'description'),
15236     'bool', null)
15237 ;               
15238
15239
15240 -- Evergreen DB patch 0695.schema.custom_toolbars.sql
15241 --
15242 -- FIXME: insert description of change, if needed
15243 --
15244
15245 -- check whether patch can be applied
15246 SELECT evergreen.upgrade_deps_block_check('0695', :eg_version);
15247
15248 CREATE TABLE actor.toolbar (
15249     id          BIGSERIAL   PRIMARY KEY,
15250     ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
15251     org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
15252     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
15253     label       TEXT        NOT NULL,
15254     layout      TEXT        NOT NULL,
15255     CONSTRAINT only_one_type CHECK (
15256         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
15257         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
15258         (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
15259     ),
15260     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
15261 );
15262 CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
15263 CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
15264 CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
15265
15266 -- this one unrelated to toolbars but is a gap in the upgrade scripts
15267 INSERT INTO permission.perm_list ( id, code, description )
15268     SELECT
15269         522,
15270         'IMPORT_AUTHORITY_MARC',
15271         oils_i18n_gettext(
15272             522,
15273             'Allows a user to create new authority records',
15274             'ppl',
15275             'description'
15276         )
15277     WHERE NOT EXISTS (
15278         SELECT 1
15279         FROM permission.perm_list
15280         WHERE
15281             id = 522
15282     );
15283
15284 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
15285     523,
15286     'ADMIN_TOOLBAR',
15287     oils_i18n_gettext(
15288         523,
15289         'Allows a user to create, edit, and delete custom toolbars',
15290         'ppl',
15291         'description'
15292     )
15293 );
15294
15295 -- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
15296 -- 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';
15297
15298 INSERT INTO actor.toolbar(org,label,layout) VALUES
15299     ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
15300     ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
15301
15302 -- 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 ;
15303
15304 -- Evergreen DB patch 0696.no_plperl.sql
15305 --
15306 -- FIXME: insert description of change, if needed
15307 --
15308
15309 -- check whether patch can be applied
15310 SELECT evergreen.upgrade_deps_block_check('0696', :eg_version);
15311
15312 -- Re-create these as plperlu instead of plperl
15313 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
15314     $_SHARED{"eg_audit_user"} = $_[0];
15315     $_SHARED{"eg_audit_ws"} = $_[1];
15316 $$ LANGUAGE plperlu;
15317
15318 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
15319     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
15320 $$ LANGUAGE plperlu;
15321
15322 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
15323     delete($_SHARED{"eg_audit_user"});
15324     delete($_SHARED{"eg_audit_ws"});
15325 $$ LANGUAGE plperlu;
15326
15327 -- And remove the language so that we don't use it later.
15328 DROP LANGUAGE plperl;
15329
15330 -- Evergreen DB patch 0697.data.place_currently_unfillable_hold.sql
15331 --
15332 -- FIXME: insert description of change, if needed
15333 --
15334
15335 -- check whether patch can be applied
15336 SELECT evergreen.upgrade_deps_block_check('0697', :eg_version);
15337
15338 -- FIXME: add/check SQL statements to perform the upgrade
15339 INSERT INTO permission.perm_list ( id, code, description ) VALUES
15340  ( 524, 'PLACE_UNFILLABLE_HOLD', oils_i18n_gettext( 524,
15341     'Allows a user to place a hold that cannot currently be filled.', 'ppl', 'description' ));
15342
15343 -- Evergreen DB patch 0698.hold_default_pickup.sql
15344 --
15345 -- FIXME: insert description of change, if needed
15346 --
15347
15348 -- check whether patch can be applied
15349 SELECT evergreen.upgrade_deps_block_check('0698', :eg_version);
15350
15351 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
15352     VALUES ('opac.default_pickup_location', TRUE, 'Default Hold Pickup Location', 'Default location for holds pickup', 'integer');
15353
15354 SELECT evergreen.upgrade_deps_block_check('0699', :eg_version);
15355
15356 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, grp )
15357     VALUES (
15358         'ui.hide_copy_editor_fields',
15359         oils_i18n_gettext(
15360             'ui.hide_copy_editor_fields',
15361             'GUI: Hide these fields within the Item Attribute Editor',
15362             'coust',
15363             'label'
15364         ),
15365         oils_i18n_gettext(
15366             'ui.hide_copy_editor_fields',
15367             'This setting may be best maintained with the dedicated configuration'
15368             || ' interface within the Item Attribute Editor.  However, here it'
15369             || ' shows up as comma separated list of field identifiers to hide.',
15370             'coust',
15371             'description'
15372         ),
15373         'array',
15374         'gui'
15375     );
15376
15377
15378 SELECT evergreen.upgrade_deps_block_check('0700', :eg_version);
15379 SELECT evergreen.upgrade_deps_block_check('0706', :eg_version);
15380
15381 -- This throws away data, but only data that causes breakage anyway.
15382 UPDATE serial.issuance SET holding_code = NULL WHERE NOT is_json(holding_code);
15383
15384 -- If we don't do this, we have unprocessed triggers and we can't alter the table
15385 SET CONSTRAINTS serial.issuance_caption_and_pattern_fkey IMMEDIATE;
15386
15387 ALTER TABLE serial.issuance ADD CHECK (holding_code IS NULL OR is_json(holding_code));
15388
15389 INSERT INTO config.internal_flag (name, value, enabled) VALUES (
15390     'serial.rematerialize_on_same_holding_code', NULL, FALSE
15391 );
15392
15393 INSERT INTO config.org_unit_setting_type (
15394     name, label, grp, description, datatype
15395 ) VALUES (
15396     'serial.default_display_grouping',
15397     'Default display grouping for serials distributions presented in the OPAC.',
15398     'serial',
15399     'Default display grouping for serials distributions presented in the OPAC. This can be "enum" or "chron".',
15400     'string'
15401 );
15402
15403 ALTER TABLE serial.distribution
15404     ADD COLUMN display_grouping TEXT NOT NULL DEFAULT 'chron'
15405         CHECK (display_grouping IN ('enum', 'chron'));
15406
15407 -- why didn't we just make one summary table in the first place?
15408 CREATE VIEW serial.any_summary AS
15409     SELECT
15410         'basic' AS summary_type, id, distribution,
15411         generated_coverage, textual_holdings, show_generated
15412     FROM serial.basic_summary
15413     UNION
15414     SELECT
15415         'index' AS summary_type, id, distribution,
15416         generated_coverage, textual_holdings, show_generated
15417     FROM serial.index_summary
15418     UNION
15419     SELECT
15420         'supplement' AS summary_type, id, distribution,
15421         generated_coverage, textual_holdings, show_generated
15422     FROM serial.supplement_summary ;
15423
15424
15425 -- Given the IDs of two rows in actor.org_unit, *the second being an ancestor
15426 -- of the first*, return in array form the path from the ancestor to the
15427 -- descendant, with each point in the path being an org_unit ID.  This is
15428 -- useful for sorting org_units by their position in a depth-first (display
15429 -- order) representation of the tree.
15430 --
15431 -- This breaks with the precedent set by actor.org_unit_full_path() and others,
15432 -- and gets the parameters "backwards," but otherwise this function would
15433 -- not be very usable within json_query.
15434 CREATE OR REPLACE FUNCTION actor.org_unit_simple_path(INT, INT)
15435 RETURNS INT[] AS $$
15436     WITH RECURSIVE descendant_depth(id, path) AS (
15437         SELECT  aou.id,
15438                 ARRAY[aou.id]
15439           FROM  actor.org_unit aou
15440                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
15441           WHERE aou.id = $2
15442             UNION ALL
15443         SELECT  aou.id,
15444                 dd.path || ARRAY[aou.id]
15445           FROM  actor.org_unit aou
15446                 JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
15447                 JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
15448     ) SELECT dd.path
15449         FROM actor.org_unit aou
15450         JOIN descendant_depth dd USING (id)
15451         WHERE aou.id = $1 ORDER BY dd.path;
15452 $$ LANGUAGE SQL STABLE;
15453
15454 CREATE TABLE serial.materialized_holding_code (
15455     id BIGSERIAL PRIMARY KEY,
15456     issuance INTEGER NOT NULL REFERENCES serial.issuance (id) ON DELETE CASCADE,
15457     subfield CHAR,
15458     value TEXT
15459 );
15460
15461 CREATE OR REPLACE FUNCTION serial.materialize_holding_code() RETURNS TRIGGER
15462 AS $func$ 
15463 use strict;
15464
15465 use MARC::Field;
15466 use JSON::XS;
15467
15468 if (not defined $_TD->{new}{holding_code}) {
15469     elog(WARNING, 'NULL in "holding_code" column of serial.issuance allowed for now, but may not be useful');
15470     return;
15471 }
15472
15473 # Do nothing if holding_code has not changed...
15474
15475 if ($_TD->{new}{holding_code} eq $_TD->{old}{holding_code}) {
15476     # ... unless the following internal flag is set.
15477
15478     my $flag_rv = spi_exec_query(q{
15479         SELECT * FROM config.internal_flag
15480         WHERE name = 'serial.rematerialize_on_same_holding_code' AND enabled
15481     }, 1);
15482     return unless $flag_rv->{processed};
15483 }
15484
15485
15486 my $holding_code = (new JSON::XS)->decode($_TD->{new}{holding_code});
15487
15488 my $field = new MARC::Field('999', @$holding_code); # tag doesnt matter
15489
15490 my $dstmt = spi_prepare(
15491     'DELETE FROM serial.materialized_holding_code WHERE issuance = $1',
15492     'INT'
15493 );
15494 spi_exec_prepared($dstmt, $_TD->{new}{id});
15495
15496 my $istmt = spi_prepare(
15497     q{
15498         INSERT INTO serial.materialized_holding_code (
15499             issuance, subfield, value
15500         ) VALUES ($1, $2, $3)
15501     }, qw{INT CHAR TEXT}
15502 );
15503
15504 foreach ($field->subfields) {
15505     spi_exec_prepared(
15506         $istmt,
15507         $_TD->{new}{id},
15508         $_->[0],
15509         $_->[1]
15510     );
15511 }
15512
15513 return;
15514
15515 $func$ LANGUAGE 'plperlu';
15516
15517 CREATE INDEX assist_holdings_display
15518     ON serial.materialized_holding_code (issuance, subfield);
15519
15520 CREATE TRIGGER materialize_holding_code
15521     AFTER INSERT OR UPDATE ON serial.issuance
15522     FOR EACH ROW EXECUTE PROCEDURE serial.materialize_holding_code() ;
15523
15524 -- starting here, we materialize all existing holding codes.
15525
15526 UPDATE config.internal_flag
15527     SET enabled = TRUE
15528     WHERE name = 'serial.rematerialize_on_same_holding_code';
15529
15530 UPDATE serial.issuance SET holding_code = holding_code;
15531
15532 UPDATE config.internal_flag
15533     SET enabled = FALSE
15534     WHERE name = 'serial.rematerialize_on_same_holding_code';
15535
15536 -- finish holding code materialization process
15537
15538 -- fix up missing holding_code fields from serial.issuance
15539 UPDATE serial.issuance siss
15540     SET holding_type = scap.type
15541     FROM serial.caption_and_pattern scap
15542     WHERE scap.id = siss.caption_and_pattern AND siss.holding_type IS NULL;
15543
15544
15545 -- Evergreen DB patch 0701.schema.patron_stat_category_enhancements.sql
15546 --
15547 -- Enables users to set patron statistical categories as required,
15548 -- whether or not users can input free text for the category value.
15549 -- Enables administrators to set an entry as the default for any
15550 -- given patron statistical category and org unit.
15551 --
15552
15553 -- check whether patch can be applied
15554 SELECT evergreen.upgrade_deps_block_check('0701', :eg_version);
15555
15556 -- New table
15557
15558 CREATE TABLE actor.stat_cat_entry_default (
15559     id              SERIAL  PRIMARY KEY,
15560     stat_cat_entry  INT     NOT NULL REFERENCES actor.stat_cat_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
15561     stat_cat        INT     NOT NULL REFERENCES actor.stat_cat (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
15562     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
15563     CONSTRAINT sced_once_per_owner UNIQUE (stat_cat,owner)
15564 );
15565
15566 COMMENT ON TABLE actor.stat_cat_entry_default IS $$
15567 User Statistical Category Default Entry
15568
15569 A library may choose one of the stat_cat entries to be the
15570 default entry.
15571 $$;
15572
15573 -- Add columns to existing tables
15574
15575 -- Patron stat cat required column
15576 ALTER TABLE actor.stat_cat
15577     ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
15578
15579 -- Patron stat cat allow_freetext column
15580 ALTER TABLE actor.stat_cat
15581     ADD COLUMN allow_freetext BOOL NOT NULL DEFAULT TRUE;
15582
15583 -- Add permissions
15584
15585 INSERT INTO permission.perm_list ( id, code, description ) VALUES
15586     ( 525, 'CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 525, 
15587         'User may set a default entry in a patron statistical category', 'ppl', 'description' )),
15588     ( 526, 'UPDATE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 526, 
15589         'User may reset a default entry in a patron statistical category', 'ppl', 'description' )),
15590     ( 527, 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT', oils_i18n_gettext( 527, 
15591         'User may unset a default entry in a patron statistical category', 'ppl', 'description' ));
15592
15593 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
15594     SELECT
15595         pgt.id, perm.id, aout.depth, TRUE
15596     FROM
15597         permission.grp_tree pgt,
15598         permission.perm_list perm,
15599         actor.org_unit_type aout
15600     WHERE
15601         pgt.name = 'Circulation Administrator' AND
15602         aout.name = 'System' AND
15603         perm.code IN ('CREATE_PATRON_STAT_CAT_ENTRY_DEFAULT', 'DELETE_PATRON_STAT_CAT_ENTRY_DEFAULT');
15604
15605
15606 SELECT evergreen.upgrade_deps_block_check('0702', :eg_version);
15607
15608 INSERT INTO config.global_flag (name, enabled, label) 
15609     VALUES (
15610         'opac.org_unit.non_inheritied_visibility',
15611         FALSE,
15612         oils_i18n_gettext(
15613             'opac.org_unit.non_inheritied_visibility',
15614             'Org Units Do Not Inherit Visibility',
15615             'cgf',
15616             'label'
15617         )
15618     );
15619
15620 CREATE TYPE actor.org_unit_custom_tree_purpose AS ENUM ('opac');
15621
15622 CREATE TABLE actor.org_unit_custom_tree (
15623     id              SERIAL  PRIMARY KEY,
15624     active          BOOLEAN DEFAULT FALSE,
15625     purpose         actor.org_unit_custom_tree_purpose NOT NULL DEFAULT 'opac' UNIQUE
15626 );
15627
15628 CREATE TABLE actor.org_unit_custom_tree_node (
15629     id              SERIAL  PRIMARY KEY,
15630     tree            INTEGER REFERENCES actor.org_unit_custom_tree (id) DEFERRABLE INITIALLY DEFERRED,
15631         org_unit        INTEGER NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
15632         parent_node     INTEGER REFERENCES actor.org_unit_custom_tree_node (id) DEFERRABLE INITIALLY DEFERRED,
15633     sibling_order   INTEGER NOT NULL DEFAULT 0,
15634     CONSTRAINT aouctn_once_per_org UNIQUE (tree, org_unit)
15635 );
15636     
15637
15638 /* UNDO
15639 BEGIN;
15640 DELETE FROM config.global_flag WHERE name = 'opac.org_unit.non_inheritied_visibility';
15641 DROP TABLE actor.org_unit_custom_tree_node;
15642 DROP TABLE actor.org_unit_custom_tree;
15643 DROP TYPE actor.org_unit_custom_tree_purpose;
15644 COMMIT;
15645 */
15646
15647 COMMIT;
15648
15649 -- This is split out because it was backported to 2.1, but may not exist before upgrades
15650 -- It can safely fail
15651 -- Also, lets say that. <_<
15652 \qecho
15653 \qecho *************************************************************************
15654 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
15655 \qecho We are about to apply a patch that may not be needed. It can fail safely.
15656 \qecho !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
15657 \qecho *************************************************************************
15658 \qecho
15659
15660 -- Evergreen DB patch 0693.schema.do_not_despace_issns.sql
15661 --
15662 -- FIXME: insert description of change, if needed
15663 --
15664 BEGIN;
15665
15666
15667 -- check whether patch can be applied
15668 SELECT evergreen.upgrade_deps_block_check('0693', :eg_version);
15669
15670 -- FIXME: add/check SQL statements to perform the upgrade
15671 -- Delete the index normalizer that was meant to remove spaces from ISSNs
15672 -- but ended up breaking records with multiple ISSNs
15673 DELETE FROM config.metabib_field_index_norm_map WHERE id IN (
15674     SELECT map.id FROM config.metabib_field_index_norm_map map
15675         INNER JOIN config.metabib_field cmf ON cmf.id = map.field
15676         INNER JOIN config.index_normalizer cin ON cin.id = map.norm
15677     WHERE cin.func = 'replace'
15678         AND cmf.field_class = 'identifier'
15679         AND cmf.name = 'issn'
15680         AND map.params = $$[" ",""]$$
15681 );
15682
15683 -- Reindex records that have more than just a single ISSN
15684 -- to ensure that spaces are maintained
15685 SELECT metabib.reingest_metabib_field_entries(source)
15686   FROM metabib.identifier_field_entry mife
15687     INNER JOIN config.metabib_field cmf ON cmf.id = mife.field
15688   WHERE cmf.field_class = 'identifier'
15689     AND cmf.name = 'issn'
15690     AND char_length(value) > 9
15691 ;
15692
15693
15694 COMMIT;
15695
15696 -- Evergreen DB patch 0705.data.custom-org-tree-perms.sql
15697 --
15698 BEGIN;
15699
15700 -- check whether patch can be applied
15701 SELECT evergreen.upgrade_deps_block_check('0705', :eg_version);
15702
15703 INSERT INTO permission.perm_list (id, code, description)
15704     VALUES (
15705         528,
15706         'ADMIN_ORG_UNIT_CUSTOM_TREE',
15707         oils_i18n_gettext(
15708             528,
15709             'User may update custom org unit trees',
15710             'ppl',
15711             'description'
15712         )
15713     );
15714
15715 COMMIT;
15716
15717
15718 -- Evergreen DB patch 0707.schema.acq-vandelay-integration.sql
15719 BEGIN;
15720
15721 SELECT evergreen.upgrade_deps_block_check('0707', :eg_version);
15722
15723 -- seed data --
15724
15725 INSERT INTO permission.perm_list ( id, code, description )
15726     VALUES (
15727         529,
15728         'ADMIN_IMPORT_MATCH_SET',
15729         oils_i18n_gettext(
15730             529,
15731             'Allows a user to create/retrieve/update/delete vandelay match sets',
15732             'ppl',
15733             'description'
15734         )
15735     ), (
15736         530,
15737         'VIEW_IMPORT_MATCH_SET',
15738         oils_i18n_gettext(
15739             530,
15740             'Allows a user to view vandelay match sets',
15741             'ppl',
15742             'description'
15743         )
15744     );
15745
15746 COMMIT;