]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/2.1-2.2-upgrade-db.sql
8c79061aa627dd14c67ccd0d97ab2b86c7e42125
[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 -- DROP objects that might have existed from a prior run of 0526
3 -- Yes this is ironic.
4 DROP TABLE IF EXISTS config.db_patch_dependencies;
5 ALTER TABLE config.upgrade_log DROP COLUMN applied_to;
6 DROP FUNCTION evergreen.upgrade_list_applied_deprecates(TEXT);
7 DROP FUNCTION evergreen.upgrade_list_applied_supersedes(TEXT);
8
9 BEGIN;
10 INSERT INTO config.upgrade_log (version) VALUES ('2.2-alpha3');
11
12 INSERT INTO config.upgrade_log (version) VALUES ('0526'); --miker
13
14 CREATE TABLE config.db_patch_dependencies (
15   db_patch      TEXT PRIMARY KEY,
16   supersedes    TEXT[],
17   deprecates    TEXT[]
18 );
19
20 CREATE OR REPLACE FUNCTION evergreen.array_overlap_check (/* field */) RETURNS TRIGGER AS $$
21 DECLARE
22     fld     TEXT;
23     cnt     INT;
24 BEGIN
25     fld := TG_ARGV[1];
26     EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
27     IF cnt > 0 THEN
28         RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
29     END IF;
30     RETURN NEW;
31 END;
32 $$ LANGUAGE PLPGSQL;
33
34 CREATE TRIGGER no_overlapping_sups
35     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
36     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('supersedes');
37
38 CREATE TRIGGER no_overlapping_deps
39     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
40     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
41
42 ALTER TABLE config.upgrade_log
43     ADD COLUMN applied_to TEXT;
44
45 -- Provide a named type for patching functions
46 CREATE TYPE evergreen.patch AS (patch TEXT);
47
48 -- List applied db patches that are deprecated by (and block the application of) my_db_patch
49 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
50     SELECT  DISTINCT l.version
51       FROM  config.upgrade_log l
52             JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.deprecates)
53       WHERE d.db_patch = $1
54 $$ LANGUAGE SQL;
55
56 -- List applied db patches that are superseded by (and block the application of) my_db_patch
57 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF evergreen.patch AS $$
58     SELECT  DISTINCT l.version
59       FROM  config.upgrade_log l
60             JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.supersedes)
61       WHERE d.db_patch = $1
62 $$ LANGUAGE SQL;
63
64 -- List applied db patches that deprecates (and block the application of) my_db_patch
65 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS TEXT AS $$
66     SELECT  db_patch
67       FROM  config.db_patch_dependencies
68       WHERE ARRAY[$1]::TEXT[] && deprecates
69 $$ LANGUAGE SQL;
70
71 -- List applied db patches that supersedes (and block the application of) my_db_patch
72 CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS TEXT AS $$
73     SELECT  db_patch
74       FROM  config.db_patch_dependencies
75       WHERE ARRAY[$1]::TEXT[] && supersedes
76 $$ LANGUAGE SQL;
77
78 -- Make sure that no deprecated or superseded db patches are currently applied
79 CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
80     SELECT  COUNT(*) = 0
81       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
82                 UNION
83              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
84                 UNION
85              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
86                 UNION
87              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
88 $$ LANGUAGE SQL;
89
90 -- Raise an exception if there are, in fact, dep/sup confilct
91 CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
92 DECLARE 
93     deprecates TEXT;
94     supersedes TEXT;
95 BEGIN
96     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
97         SELECT  STRING_AGG(patch, ', ') INTO deprecates FROM evergreen.upgrade_list_applied_deprecates(my_db_patch);
98         SELECT  STRING_AGG(patch, ', ') INTO supersedes FROM evergreen.upgrade_list_applied_supersedes(my_db_patch);
99         RAISE EXCEPTION '
100 Upgrade script % can not be applied:
101   applied deprecated scripts %
102   applied superseded scripts %
103   deprecated by %
104   superseded by %',
105             my_db_patch,
106             ARRAY_AGG(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
107             ARRAY_AGG(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
108             evergreen.upgrade_list_applied_deprecated(my_db_patch),
109             evergreen.upgrade_list_applied_superseded(my_db_patch);
110     END IF;
111
112     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
113     RETURN TRUE;
114 END;
115 $$ LANGUAGE PLPGSQL;
116
117 -- Evergreen DB patch 0536.schema.lazy_circ-barcode_lookup.sql
118 --
119 -- FIXME: insert description of change, if needed
120 --
121
122 -- check whether patch can be applied
123 INSERT INTO config.upgrade_log (version) VALUES ('0536');
124
125 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');
126
127 CREATE TABLE config.barcode_completion (
128     id          SERIAL  PRIMARY KEY,
129     active      BOOL    NOT NULL DEFAULT true,
130     org_unit    INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
131     prefix      TEXT,
132     suffix      TEXT,
133     length      INT     NOT NULL DEFAULT 0,
134     padding     TEXT,
135     padding_end BOOL    NOT NULL DEFAULT false,
136     asset       BOOL    NOT NULL DEFAULT true,
137     actor       BOOL    NOT NULL DEFAULT true
138 );
139
140 CREATE TYPE evergreen.barcode_set AS (type TEXT, id BIGINT, barcode TEXT);
141
142 CREATE OR REPLACE FUNCTION evergreen.get_barcodes(select_ou INT, type TEXT, in_barcode TEXT) RETURNS SETOF evergreen.barcode_set AS $$
143 DECLARE
144     cur_barcode TEXT;
145     barcode_len INT;
146     completion_len  INT;
147     asset_barcodes  TEXT[];
148     actor_barcodes  TEXT[];
149     do_asset    BOOL = false;
150     do_serial   BOOL = false;
151     do_booking  BOOL = false;
152     do_actor    BOOL = false;
153     completion_set  config.barcode_completion%ROWTYPE;
154 BEGIN
155
156     IF position('asset' in type) > 0 THEN
157         do_asset = true;
158     END IF;
159     IF position('serial' in type) > 0 THEN
160         do_serial = true;
161     END IF;
162     IF position('booking' in type) > 0 THEN
163         do_booking = true;
164     END IF;
165     IF do_asset OR do_serial OR do_booking THEN
166         asset_barcodes = asset_barcodes || in_barcode;
167     END IF;
168     IF position('actor' in type) > 0 THEN
169         do_actor = true;
170         actor_barcodes = actor_barcodes || in_barcode;
171     END IF;
172
173     barcode_len := length(in_barcode);
174
175     FOR completion_set IN
176       SELECT * FROM config.barcode_completion
177         WHERE active
178         AND org_unit IN (SELECT aou.id FROM actor.org_unit_ancestors(select_ou) aou)
179         LOOP
180         IF completion_set.prefix IS NULL THEN
181             completion_set.prefix := '';
182         END IF;
183         IF completion_set.suffix IS NULL THEN
184             completion_set.suffix := '';
185         END IF;
186         IF completion_set.length = 0 OR completion_set.padding IS NULL OR length(completion_set.padding) = 0 THEN
187             cur_barcode = completion_set.prefix || in_barcode || completion_set.suffix;
188         ELSE
189             completion_len = completion_set.length - length(completion_set.prefix) - length(completion_set.suffix);
190             IF completion_len >= barcode_len THEN
191                 IF completion_set.padding_end THEN
192                     cur_barcode = rpad(in_barcode, completion_len, completion_set.padding);
193                 ELSE
194                     cur_barcode = lpad(in_barcode, completion_len, completion_set.padding);
195                 END IF;
196                 cur_barcode = completion_set.prefix || cur_barcode || completion_set.suffix;
197             END IF;
198         END IF;
199         IF completion_set.actor THEN
200             actor_barcodes = actor_barcodes || cur_barcode;
201         END IF;
202         IF completion_set.asset THEN
203             asset_barcodes = asset_barcodes || cur_barcode;
204         END IF;
205     END LOOP;
206
207     IF do_asset AND do_serial THEN
208         RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM ONLY asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
209         RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
210     ELSIF do_asset THEN
211         RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
212     ELSIF do_serial THEN
213         RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
214     END IF;
215     IF do_booking THEN
216         RETURN QUERY SELECT 'booking'::TEXT, id::BIGINT, barcode FROM booking.resource WHERE barcode = ANY(asset_barcodes);
217     END IF;
218     IF do_actor THEN
219         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;
220     END IF;
221     RETURN;
222 END;
223 $$ LANGUAGE plpgsql;
224
225 COMMENT ON FUNCTION evergreen.get_barcodes(INT, TEXT, TEXT) IS $$
226 Given user input, find an appropriate barcode in the proper class.
227
228 Will add prefix/suffix information to do so, and return all results.
229 $$;
230
231
232
233 INSERT INTO config.upgrade_log (version) VALUES ('0537'); --miker
234
235 DROP FUNCTION evergreen.upgrade_deps_block_check(text,text);
236 DROP FUNCTION evergreen.upgrade_verify_no_dep_conflicts(text);
237 DROP FUNCTION evergreen.upgrade_list_applied_deprecated(text);
238 DROP FUNCTION evergreen.upgrade_list_applied_superseded(text);
239
240 -- List applied db patches that deprecates (and block the application of) my_db_patch
241 CREATE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
242     SELECT  db_patch
243       FROM  config.db_patch_dependencies
244       WHERE ARRAY[$1]::TEXT[] && deprecates
245 $$ LANGUAGE SQL;
246
247 -- List applied db patches that supersedes (and block the application of) my_db_patch
248 CREATE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
249     SELECT  db_patch
250       FROM  config.db_patch_dependencies
251       WHERE ARRAY[$1]::TEXT[] && supersedes
252 $$ LANGUAGE SQL;
253
254 -- Make sure that no deprecated or superseded db patches are currently applied
255 CREATE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
256     SELECT  COUNT(*) = 0
257       FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
258                 UNION
259              SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
260                 UNION
261              SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
262                 UNION
263              SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
264 $$ LANGUAGE SQL;
265
266 -- Raise an exception if there are, in fact, dep/sup confilct
267 CREATE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
268 BEGIN
269     IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
270         RAISE EXCEPTION '
271 Upgrade script % can not be applied:
272   applied deprecated scripts %
273   applied superseded scripts %
274   deprecated by %
275   superseded by %',
276             my_db_patch,
277             ARRAY_ACCUM(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
278             ARRAY_ACCUM(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
279             evergreen.upgrade_list_applied_deprecated(my_db_patch),
280             evergreen.upgrade_list_applied_superseded(my_db_patch);
281     END IF;
282
283     INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
284     RETURN TRUE;
285 END;
286 $$ LANGUAGE PLPGSQL;
287
288
289 INSERT INTO config.upgrade_log (version) VALUES ('0544');
290
291 INSERT INTO config.usr_setting_type 
292 ( name, opac_visible, label, description, datatype) VALUES 
293 ( 'circ.collections.exempt',
294   FALSE, 
295   oils_i18n_gettext('circ.collections.exempt', 'Collections: Exempt', 'cust', 'description'),
296   oils_i18n_gettext('circ.collections.exempt', 'User is exempt from collections tracking/processing', 'cust', 'description'),
297   'bool'
298 );
299
300
301
302 SELECT evergreen.upgrade_deps_block_check('0545', :eg_version);
303
304 INSERT INTO permission.perm_list VALUES
305  (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')),
306  (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'));
307
308 --- stock Circulation Administrator group
309
310 INSERT INTO permission.grp_perm_map ( grp, perm, depth, grantable )
311     SELECT
312         4,
313         id,
314         0,
315         't'
316     FROM permission.perm_list
317     WHERE code in ('ABORT_TRANSIT_ON_LOST', 'ABORT_TRANSIT_ON_MISSING');
318
319 -- Evergreen DB patch 0546.schema.sip_statcats.sql
320
321
322 -- check whether patch can be applied
323 SELECT evergreen.upgrade_deps_block_check('0546', :eg_version);
324
325 CREATE TABLE actor.stat_cat_sip_fields (
326     field   CHAR(2) PRIMARY KEY,
327     name    TEXT    NOT NULL,
328     one_only  BOOL    NOT NULL DEFAULT FALSE
329 );
330 COMMENT ON TABLE actor.stat_cat_sip_fields IS $$
331 Actor Statistical Category SIP Fields
332
333 Contains the list of valid SIP Field identifiers for
334 Statistical Categories.
335 $$;
336 ALTER TABLE actor.stat_cat
337     ADD COLUMN sip_field   CHAR(2) REFERENCES actor.stat_cat_sip_fields(field) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
338     ADD COLUMN sip_format  TEXT;
339
340 CREATE FUNCTION actor.stat_cat_check() RETURNS trigger AS $func$
341 DECLARE
342     sipfield actor.stat_cat_sip_fields%ROWTYPE;
343     use_count INT;
344 BEGIN
345     IF NEW.sip_field IS NOT NULL THEN
346         SELECT INTO sipfield * FROM actor.stat_cat_sip_fields WHERE field = NEW.sip_field;
347         IF sipfield.one_only THEN
348             SELECT INTO use_count count(id) FROM actor.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
349             IF use_count > 0 THEN
350                 RAISE EXCEPTION 'Sip field cannot be used twice';
351             END IF;
352         END IF;
353     END IF;
354     RETURN NEW;
355 END;
356 $func$ LANGUAGE PLPGSQL;
357
358 CREATE TRIGGER actor_stat_cat_sip_update_trigger
359     BEFORE INSERT OR UPDATE ON actor.stat_cat FOR EACH ROW
360     EXECUTE PROCEDURE actor.stat_cat_check();
361
362 CREATE TABLE asset.stat_cat_sip_fields (
363     field   CHAR(2) PRIMARY KEY,
364     name    TEXT    NOT NULL,
365     one_only BOOL    NOT NULL DEFAULT FALSE
366 );
367 COMMENT ON TABLE asset.stat_cat_sip_fields IS $$
368 Asset Statistical Category SIP Fields
369
370 Contains the list of valid SIP Field identifiers for
371 Statistical Categories.
372 $$;
373
374 ALTER TABLE asset.stat_cat
375     ADD COLUMN sip_field   CHAR(2) REFERENCES asset.stat_cat_sip_fields(field) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
376     ADD COLUMN sip_format  TEXT;
377
378 CREATE FUNCTION asset.stat_cat_check() RETURNS trigger AS $func$
379 DECLARE
380     sipfield asset.stat_cat_sip_fields%ROWTYPE;
381     use_count INT;
382 BEGIN
383     IF NEW.sip_field IS NOT NULL THEN
384         SELECT INTO sipfield * FROM asset.stat_cat_sip_fields WHERE field = NEW.sip_field;
385         IF sipfield.one_only THEN
386             SELECT INTO use_count count(id) FROM asset.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
387             IF use_count > 0 THEN
388                 RAISE EXCEPTION 'Sip field cannot be used twice';
389             END IF;
390         END IF;
391     END IF;
392     RETURN NEW;
393 END;
394 $func$ LANGUAGE PLPGSQL;
395
396 CREATE TRIGGER asset_stat_cat_sip_update_trigger
397     BEFORE INSERT OR UPDATE ON asset.stat_cat FOR EACH ROW
398     EXECUTE PROCEDURE asset.stat_cat_check();
399
400
401
402 SELECT evergreen.upgrade_deps_block_check('0548', :eg_version); -- dbwells
403
404 \qecho This redoes the original part 1 of 0547 which did not apply to rel_2_1,
405 \qecho and is being added for the sake of clarity
406
407 -- delete errant inserts from 0545 (group 4 is NOT the circulation admin group)
408 DELETE FROM permission.grp_perm_map WHERE grp = 4 AND perm IN (
409         SELECT id FROM permission.perm_list
410         WHERE code in ('ABORT_TRANSIT_ON_LOST', 'ABORT_TRANSIT_ON_MISSING')
411 );
412
413 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
414         SELECT
415                 pgt.id, perm.id, aout.depth, TRUE
416         FROM
417                 permission.grp_tree pgt,
418                 permission.perm_list perm,
419                 actor.org_unit_type aout
420         WHERE
421                 pgt.name = 'Circulation Administrator' AND
422                 aout.name = 'Consortium' AND
423                 perm.code IN (
424                         'ABORT_TRANSIT_ON_LOST',
425                         'ABORT_TRANSIT_ON_MISSING'
426                 ) AND NOT EXISTS (
427                         SELECT 1
428                         FROM permission.grp_perm_map AS map
429                         WHERE
430                                 map.grp = pgt.id
431                                 AND map.perm = perm.id
432                 );
433
434 -- Evergreen DB patch XXXX.data.transit-checkin-interval.sql
435 --
436 -- New org unit setting "circ.transit.min_checkin_interval"
437 -- New TRANSIT_CHECKIN_INTERVAL_BLOCK.override permission
438 --
439
440
441 -- check whether patch can be applied
442 SELECT evergreen.upgrade_deps_block_check('0549', :eg_version);
443
444 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
445     'circ.transit.min_checkin_interval',
446     oils_i18n_gettext( 
447         'circ.transit.min_checkin_interval', 
448         'Circ:  Minimum Transit Checkin Interval',
449         'coust',
450         'label'
451     ),
452     oils_i18n_gettext( 
453         'circ.transit.min_checkin_interval', 
454         'In-Transit items checked in this close to the transit start time will be prevented from checking in',
455         'coust',
456         'label'
457     ),
458     'interval'
459 );
460
461 INSERT INTO permission.perm_list ( id, code, description ) VALUES (  
462     509, 
463     'TRANSIT_CHECKIN_INTERVAL_BLOCK.override', 
464     oils_i18n_gettext(
465         509,
466         'Allows a user to override the TRANSIT_CHECKIN_INTERVAL_BLOCK event', 
467         'ppl', 
468         'description'
469     )
470 );
471
472 -- add the perm to the default circ admin group
473 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
474         SELECT
475                 pgt.id, perm.id, aout.depth, TRUE
476         FROM
477                 permission.grp_tree pgt,
478                 permission.perm_list perm,
479                 actor.org_unit_type aout
480         WHERE
481                 pgt.name = 'Circulation Administrator' AND
482                 aout.name = 'System' AND
483                 perm.code IN ( 'TRANSIT_CHECKIN_INTERVAL_BLOCK.override' );
484
485
486 -- check whether patch can be applied
487 SELECT evergreen.upgrade_deps_block_check('0550', :eg_version);
488
489 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
490     'org.patron_opt_boundary',
491     oils_i18n_gettext( 
492         'org.patron_opt_boundary',
493         'Circ: Patron Opt-In Boundary',
494         'coust',
495         'label'
496     ),
497     oils_i18n_gettext( 
498         'org.patron_opt_boundary',
499         'This determines at which depth above which patrons must be opted in, and below which patrons will be assumed to be opted in.',
500         'coust',
501         'label'
502     ),
503     'integer'
504 );
505
506 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
507     'org.patron_opt_default',
508     oils_i18n_gettext( 
509         'org.patron_opt_default',
510         'Circ: Patron Opt-In Default',
511         'coust',
512         'label'
513     ),
514     oils_i18n_gettext( 
515         'org.patron_opt_default',
516         'This is the default depth at which a patron is opted in; it is calculated as an org unit relative to the current workstation.',
517         'coust',
518         'label'
519     ),
520     'integer'
521 );
522
523 -- Evergreen DB patch 0562.schema.copy_active_date.sql
524 --
525 -- Active Date
526
527
528 -- check whether patch can be applied
529 SELECT evergreen.upgrade_deps_block_check('0562', :eg_version);
530
531 ALTER TABLE asset.copy
532     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
533
534 ALTER TABLE auditor.asset_copy_history
535     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
536
537 ALTER TABLE auditor.serial_unit_history
538     ADD COLUMN active_date TIMESTAMP WITH TIME ZONE;
539
540 ALTER TABLE config.copy_status
541     ADD COLUMN copy_active BOOL NOT NULL DEFAULT FALSE;
542
543 ALTER TABLE config.circ_matrix_weights
544     ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
545
546 ALTER TABLE config.hold_matrix_weights
547     ADD COLUMN item_age NUMERIC(6,2) NOT NULL DEFAULT 0.0;
548
549 -- The two defaults above were to stop erroring on NOT NULL
550 -- Remove them here
551 ALTER TABLE config.circ_matrix_weights
552     ALTER COLUMN item_age DROP DEFAULT;
553
554 ALTER TABLE config.hold_matrix_weights
555     ALTER COLUMN item_age DROP DEFAULT;
556
557 ALTER TABLE config.circ_matrix_matchpoint
558     ADD COLUMN item_age INTERVAL;
559
560 ALTER TABLE config.hold_matrix_matchpoint
561     ADD COLUMN item_age INTERVAL;
562
563 --Removed dupe asset.acp_status_changed
564
565 CREATE OR REPLACE FUNCTION asset.acp_created()
566 RETURNS TRIGGER AS $$
567 BEGIN
568     IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
569         NEW.active_date := now();
570     END IF;
571     IF NEW.status_changed_time IS NULL THEN
572         NEW.status_changed_time := now();
573     END IF;
574     RETURN NEW;
575 END;
576 $$ LANGUAGE plpgsql;
577
578 CREATE TRIGGER acp_created_trig
579     BEFORE INSERT ON asset.copy
580     FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
581
582 CREATE TRIGGER sunit_created_trig
583     BEFORE INSERT ON serial.unit
584     FOR EACH ROW EXECUTE PROCEDURE asset.acp_created();
585
586 --Removed dupe action.hold_request_permit_test
587
588 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$
589 DECLARE
590     cn_object       asset.call_number%ROWTYPE;
591     rec_descriptor  metabib.rec_descriptor%ROWTYPE;
592     cur_matchpoint  config.circ_matrix_matchpoint%ROWTYPE;
593     matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
594     weights         config.circ_matrix_weights%ROWTYPE;
595     user_age        INTERVAL;
596     my_item_age     INTERVAL;
597     denominator     NUMERIC(6,2);
598     row_list        INT[];
599     result          action.found_circ_matrix_matchpoint;
600 BEGIN
601     -- Assume failure
602     result.success = false;
603
604     -- Fetch useful data
605     SELECT INTO cn_object       * FROM asset.call_number        WHERE id = item_object.call_number;
606     SELECT INTO rec_descriptor  * FROM metabib.rec_descriptor   WHERE record = cn_object.record;
607
608     -- Pre-generate this so we only calc it once
609     IF user_object.dob IS NOT NULL THEN
610         SELECT INTO user_age age(user_object.dob);
611     END IF;
612
613     -- Ditto
614     SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
615
616     -- Grab the closest set circ weight setting.
617     SELECT INTO weights cw.*
618       FROM config.weight_assoc wa
619            JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
620            JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
621       WHERE active
622       ORDER BY d.distance
623       LIMIT 1;
624
625     -- No weights? Bad admin! Defaults to handle that anyway.
626     IF weights.id IS NULL THEN
627         weights.grp                 := 11.0;
628         weights.org_unit            := 10.0;
629         weights.circ_modifier       := 5.0;
630         weights.marc_type           := 4.0;
631         weights.marc_form           := 3.0;
632         weights.marc_bib_level      := 2.0;
633         weights.marc_vr_format      := 2.0;
634         weights.copy_circ_lib       := 8.0;
635         weights.copy_owning_lib     := 8.0;
636         weights.user_home_ou        := 8.0;
637         weights.ref_flag            := 1.0;
638         weights.juvenile_flag       := 6.0;
639         weights.is_renewal          := 7.0;
640         weights.usr_age_lower_bound := 0.0;
641         weights.usr_age_upper_bound := 0.0;
642         weights.item_age            := 0.0;
643     END IF;
644
645     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
646     -- If you break your org tree with funky parenting this may be wrong
647     -- 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
648     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
649     WITH all_distance(distance) AS (
650             SELECT depth AS distance FROM actor.org_unit_type
651         UNION
652             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
653         )
654     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
655
656     -- Loop over all the potential matchpoints
657     FOR cur_matchpoint IN
658         SELECT m.*
659           FROM  config.circ_matrix_matchpoint m
660                 /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
661                 /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
662                 LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
663                 LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
664                 LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
665           WHERE m.active
666                 -- Permission Groups
667              -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
668                 -- Org Units
669              -- AND (m.org_unit                 IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
670                 AND (m.copy_owning_lib          IS NULL OR cnoua.id IS NOT NULL)
671                 AND (m.copy_circ_lib            IS NULL OR iooua.id IS NOT NULL)
672                 AND (m.user_home_ou             IS NULL OR uhoua.id IS NOT NULL)
673                 -- Circ Type
674                 AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
675                 -- Static User Checks
676                 AND (m.juvenile_flag            IS NULL OR m.juvenile_flag = user_object.juvenile)
677                 AND (m.usr_age_lower_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
678                 AND (m.usr_age_upper_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
679                 -- Static Item Checks
680                 AND (m.circ_modifier            IS NULL OR m.circ_modifier = item_object.circ_modifier)
681                 AND (m.marc_type                IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
682                 AND (m.marc_form                IS NULL OR m.marc_form = rec_descriptor.item_form)
683                 AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
684                 AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
685                 AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
686                 AND (m.item_age                 IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
687           ORDER BY
688                 -- Permission Groups
689                 CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
690                 -- Org Units
691                 CASE WHEN ctoua.distance        IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
692                 CASE WHEN cnoua.distance        IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
693                 CASE WHEN iooua.distance        IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
694                 CASE WHEN uhoua.distance        IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
695                 -- Circ Type                    -- Note: 4^x is equiv to 2^(2*x)
696                 CASE WHEN m.is_renewal          IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
697                 -- Static User Checks
698                 CASE WHEN m.juvenile_flag       IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
699                 CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
700                 CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
701                 -- Static Item Checks
702                 CASE WHEN m.circ_modifier       IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
703                 CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
704                 CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
705                 CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
706                 CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
707                 -- Item age has a slight adjustment to weight based on value.
708                 -- This should ensure that a shorter age limit comes first when all else is equal.
709                 -- NOTE: This assumes that intervals will normally be in days.
710                 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,
711                 -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
712                 -- This prevents "we changed the table order by updating a rule, and we started getting different results"
713                 m.id LOOP
714
715         -- Record the full matching row list
716         row_list := row_list || cur_matchpoint.id;
717
718         -- No matchpoint yet?
719         IF matchpoint.id IS NULL THEN
720             -- Take the entire matchpoint as a starting point
721             matchpoint := cur_matchpoint;
722             CONTINUE; -- No need to look at this row any more.
723         END IF;
724
725         -- Incomplete matchpoint?
726         IF matchpoint.circulate IS NULL THEN
727             matchpoint.circulate := cur_matchpoint.circulate;
728         END IF;
729         IF matchpoint.duration_rule IS NULL THEN
730             matchpoint.duration_rule := cur_matchpoint.duration_rule;
731         END IF;
732         IF matchpoint.recurring_fine_rule IS NULL THEN
733             matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
734         END IF;
735         IF matchpoint.max_fine_rule IS NULL THEN
736             matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
737         END IF;
738         IF matchpoint.hard_due_date IS NULL THEN
739             matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
740         END IF;
741         IF matchpoint.total_copy_hold_ratio IS NULL THEN
742             matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
743         END IF;
744         IF matchpoint.available_copy_hold_ratio IS NULL THEN
745             matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
746         END IF;
747         IF matchpoint.renewals IS NULL THEN
748             matchpoint.renewals := cur_matchpoint.renewals;
749         END IF;
750         IF matchpoint.grace_period IS NULL THEN
751             matchpoint.grace_period := cur_matchpoint.grace_period;
752         END IF;
753     END LOOP;
754
755     -- Check required fields
756     IF matchpoint.circulate             IS NOT NULL AND
757        matchpoint.duration_rule         IS NOT NULL AND
758        matchpoint.recurring_fine_rule   IS NOT NULL AND
759        matchpoint.max_fine_rule         IS NOT NULL THEN
760         -- All there? We have a completed match.
761         result.success := true;
762     END IF;
763
764     -- Include the assembled matchpoint, even if it isn't complete
765     result.matchpoint := matchpoint;
766
767     -- Include (for debugging) the full list of matching rows
768     result.buildrows := row_list;
769
770     -- Hand the result back to caller
771     RETURN result;
772 END;
773 $func$ LANGUAGE plpgsql;
774
775 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint(pickup_ou integer, request_ou integer, match_item bigint, match_user integer, match_requestor integer)
776   RETURNS integer AS
777 $func$
778 DECLARE
779     requestor_object    actor.usr%ROWTYPE;
780     user_object         actor.usr%ROWTYPE;
781     item_object         asset.copy%ROWTYPE;
782     item_cn_object      asset.call_number%ROWTYPE;
783     my_item_age         INTERVAL;
784     rec_descriptor      metabib.rec_descriptor%ROWTYPE;
785     matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
786     weights             config.hold_matrix_weights%ROWTYPE;
787     denominator         NUMERIC(6,2);
788 BEGIN
789     SELECT INTO user_object         * FROM actor.usr                WHERE id = match_user;
790     SELECT INTO requestor_object    * FROM actor.usr                WHERE id = match_requestor;
791     SELECT INTO item_object         * FROM asset.copy               WHERE id = match_item;
792     SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
793     SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;
794
795     SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));
796
797     -- The item's owner should probably be the one determining if the item is holdable
798     -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
799     -- This flag will allow for setting it to the owning library (where the call number "lives")
800     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;
801
802     -- Grab the closest set circ weight setting.
803     IF NOT FOUND THEN
804         -- Default to circ library
805         SELECT INTO weights hw.*
806           FROM config.weight_assoc wa
807                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
808                JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
809           WHERE active
810           ORDER BY d.distance
811           LIMIT 1;
812     ELSE
813         -- Flag is set, use owning library
814         SELECT INTO weights hw.*
815           FROM config.weight_assoc wa
816                JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
817                JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) d ON (wa.org_unit = d.id)
818           WHERE active
819           ORDER BY d.distance
820           LIMIT 1;
821     END IF;
822
823     -- No weights? Bad admin! Defaults to handle that anyway.
824     IF weights.id IS NULL THEN
825         weights.user_home_ou    := 5.0;
826         weights.request_ou      := 5.0;
827         weights.pickup_ou       := 5.0;
828         weights.item_owning_ou  := 5.0;
829         weights.item_circ_ou    := 5.0;
830         weights.usr_grp         := 7.0;
831         weights.requestor_grp   := 8.0;
832         weights.circ_modifier   := 4.0;
833         weights.marc_type       := 3.0;
834         weights.marc_form       := 2.0;
835         weights.marc_bib_level  := 1.0;
836         weights.marc_vr_format  := 1.0;
837         weights.juvenile_flag   := 4.0;
838         weights.ref_flag        := 0.0;
839         weights.item_age        := 0.0;
840     END IF;
841
842     -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
843     -- If you break your org tree with funky parenting this may be wrong
844     -- 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
845     -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
846     WITH all_distance(distance) AS (
847             SELECT depth AS distance FROM actor.org_unit_type
848         UNION
849             SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
850         )
851     SELECT INTO denominator MAX(distance) + 1 FROM all_distance;
852
853     -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
854     -- This may be better implemented as part of the upgrade script?
855     -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
856     -- Then remove this flag, of course.
857     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
858
859     IF FOUND THEN
860         -- Note: This, to me, is REALLY hacky. I put it in anyway.
861         -- If you can't tell, this is a single call swap on two variables.
862         SELECT INTO user_object.profile, requestor_object.profile
863                     requestor_object.profile, user_object.profile;
864     END IF;
865
866     -- Select the winning matchpoint into the matchpoint variable for returning
867     SELECT INTO matchpoint m.*
868       FROM  config.hold_matrix_matchpoint m
869             /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
870             LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
871             LEFT JOIN actor.org_unit_ancestors_distance( pickup_ou ) puoua ON m.pickup_ou = puoua.id
872             LEFT JOIN actor.org_unit_ancestors_distance( request_ou ) rqoua ON m.request_ou = rqoua.id
873             LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
874             LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
875             LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
876       WHERE m.active
877             -- Permission Groups
878          -- AND (m.requestor_grp        IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
879             AND (m.usr_grp              IS NULL OR upgad.id IS NOT NULL)
880             -- Org Units
881             AND (m.pickup_ou            IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
882             AND (m.request_ou           IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
883             AND (m.item_owning_ou       IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
884             AND (m.item_circ_ou         IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
885             AND (m.user_home_ou         IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
886             -- Static User Checks
887             AND (m.juvenile_flag        IS NULL OR m.juvenile_flag = user_object.juvenile)
888             -- Static Item Checks
889             AND (m.circ_modifier        IS NULL OR m.circ_modifier = item_object.circ_modifier)
890             AND (m.marc_type            IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
891             AND (m.marc_form            IS NULL OR m.marc_form = rec_descriptor.item_form)
892             AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
893             AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
894             AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
895             AND (m.item_age             IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
896       ORDER BY
897             -- Permission Groups
898             CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
899             CASE WHEN upgad.distance    IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
900             -- Org Units
901             CASE WHEN puoua.distance    IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
902             CASE WHEN rqoua.distance    IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
903             CASE WHEN cnoua.distance    IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
904             CASE WHEN iooua.distance    IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
905             CASE WHEN uhoua.distance    IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
906             -- Static User Checks       -- Note: 4^x is equiv to 2^(2*x)
907             CASE WHEN m.juvenile_flag   IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
908             -- Static Item Checks
909             CASE WHEN m.circ_modifier   IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
910             CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
911             CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
912             CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
913             CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
914             -- Item age has a slight adjustment to weight based on value.
915             -- This should ensure that a shorter age limit comes first when all else is equal.
916             -- NOTE: This assumes that intervals will normally be in days.
917             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,
918             -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
919             -- This prevents "we changed the table order by updating a rule, and we started getting different results"
920             m.id;
921
922     -- Return just the ID for now
923     RETURN matchpoint.id;
924 END;
925 $func$ LANGUAGE 'plpgsql';
926
927 DROP INDEX IF EXISTS config.ccmm_once_per_paramset;
928
929 DROP INDEX IF EXISTS config.chmm_once_per_paramset;
930
931 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;
932
933 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;
934
935 UPDATE config.copy_status SET copy_active = true WHERE id IN (0, 1, 7, 8, 10, 12, 15);
936
937 INSERT into config.org_unit_setting_type
938 ( name, label, description, datatype ) VALUES
939 ( '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');
940
941 -- Assume create date when item is in status we would update active date for anyway
942 UPDATE asset.copy SET active_date = create_date WHERE status IN (SELECT id FROM config.copy_status WHERE copy_active = true);
943
944 -- Assume create date for any item with circs
945 UPDATE asset.copy SET active_date = create_date WHERE id IN (SELECT id FROM extend_reporter.full_circ_count WHERE circ_count > 0);
946
947 -- Assume create date for status change time while we are at it. Because being created WAS a change in status.
948 UPDATE asset.copy SET status_changed_time = create_date WHERE status_changed_time IS NULL;
949
950 -- Evergreen DB patch 0564.data.delete_empty_volume.sql
951 --
952 -- New org setting cat.volume.delete_on_empty
953 --
954
955 -- check whether patch can be applied
956 SELECT evergreen.upgrade_deps_block_check('0564', :eg_version);
957
958 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
959     VALUES ( 
960         'cat.volume.delete_on_empty',
961         oils_i18n_gettext('cat.volume.delete_on_empty', 'Cat: Delete volume with last copy', 'coust', 'label'),
962         oils_i18n_gettext('cat.volume.delete_on_empty', 'Automatically delete a volume when the last linked copy is deleted', 'coust', 'description'),
963         'bool'
964     );
965
966
967 -- Evergreen DB patch 0565.schema.action-trigger.event_definition.hold-cancel-no-target-notification.sql
968 --
969 -- New action trigger event definition: Hold Cancelled (No Target) Email Notification
970 --
971
972 -- check whether patch can be applied
973 SELECT evergreen.upgrade_deps_block_check('0565', :eg_version);
974
975 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template)
976     VALUES (38, FALSE, 1, 
977         'Hold Cancelled (No Target) Email Notification', 
978         'hold_request.cancel.expire_no_target', 
979         'HoldIsCancelled', 'SendEmail', '30 minutes', 'cancel_time', 'usr',
980 $$
981 [%- USE date -%]
982 [%- user = target.0.usr -%]
983 To: [%- params.recipient_email || user.email %]
984 From: [%- params.sender_email || default_sender %]
985 Subject: Hold Request Cancelled
986
987 Dear [% user.family_name %], [% user.first_given_name %]
988 The following holds were cancelled because no items were found to fullfil the hold.
989
990 [% FOR hold IN target %]
991     Title: [% hold.bib_rec.bib_record.simple_record.title %]
992     Author: [% hold.bib_rec.bib_record.simple_record.author %]
993     Library: [% hold.pickup_lib.name %]
994     Request Date: [% date.format(helpers.format_date(hold.rrequest_time), '%Y-%m-%d') %]
995 [% END %]
996
997 $$);
998
999 INSERT INTO action_trigger.environment (event_def, path) VALUES
1000     (38, 'usr'),
1001     (38, 'pickup_lib'),
1002     (38, 'bib_rec.bib_record.simple_record');
1003
1004 -- Evergreen DB patch XXXX.data.ou_setting_generate_overdue_on_lost.sql.sql
1005
1006 -- check whether patch can be applied
1007 SELECT evergreen.upgrade_deps_block_check('0567', :eg_version);
1008
1009 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1010     'circ.lost.generate_overdue_on_checkin',
1011     oils_i18n_gettext( 
1012         'circ.lost.generate_overdue_on_checkin',
1013         'Circ:  Lost Checkin Generates New Overdues',
1014         'coust',
1015         'label'
1016     ),
1017     oils_i18n_gettext( 
1018         'circ.lost.generate_overdue_on_checkin',
1019         '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',
1020         'coust',
1021         'label'
1022     ),
1023     'bool'
1024 );
1025
1026 -- Evergreen DB patch 0572.vandelay-record-matching-and-quality.sql
1027 --
1028
1029
1030 -- check whether patch can be applied
1031 SELECT evergreen.upgrade_deps_block_check('0572', :eg_version);
1032
1033 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;
1034
1035 CREATE TABLE vandelay.match_set (
1036     id      SERIAL  PRIMARY KEY,
1037     name    TEXT        NOT NULL,
1038     owner   INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE,
1039     mtype   TEXT        NOT NULL DEFAULT 'biblio', -- 'biblio','authority','mfhd'?, others?
1040     CONSTRAINT name_once_per_owner_mtype UNIQUE (name, owner, mtype)
1041 );
1042
1043 -- Table to define match points, either FF via SVF or tag+subfield
1044 CREATE TABLE vandelay.match_set_point (
1045     id          SERIAL  PRIMARY KEY,
1046     match_set   INT     REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
1047     parent      INT     REFERENCES vandelay.match_set_point (id),
1048     bool_op     TEXT    CHECK (bool_op IS NULL OR (bool_op IN ('AND','OR','NOT'))),
1049     svf         TEXT    REFERENCES config.record_attr_definition (name),
1050     tag         TEXT,
1051     subfield    TEXT,
1052     negate      BOOL    DEFAULT FALSE,
1053     quality     INT     NOT NULL DEFAULT 1, -- higher is better
1054     CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
1055     CONSTRAINT vmsp_need_a_tag_or_a_ff_or_a_bo CHECK (
1056         (tag IS NOT NULL AND svf IS NULL AND bool_op IS NULL) OR
1057         (tag IS NULL AND svf IS NOT NULL AND bool_op IS NULL) OR
1058         (tag IS NULL AND svf IS NULL AND bool_op IS NOT NULL)
1059     )
1060 );
1061
1062 CREATE TABLE vandelay.match_set_quality (
1063     id          SERIAL  PRIMARY KEY,
1064     match_set   INT     NOT NULL REFERENCES vandelay.match_set (id) ON DELETE CASCADE,
1065     svf         TEXT    REFERENCES config.record_attr_definition,
1066     tag         TEXT,
1067     subfield    TEXT,
1068     value       TEXT    NOT NULL,
1069     quality     INT     NOT NULL DEFAULT 1, -- higher is better
1070     CONSTRAINT vmsq_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
1071     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))
1072 );
1073 CREATE UNIQUE INDEX vmsq_def_once_per_set ON vandelay.match_set_quality (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(svf,''), value);
1074
1075
1076 -- ALTER TABLEs...
1077 ALTER TABLE vandelay.queue ADD COLUMN match_set INT REFERENCES vandelay.match_set (id) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
1078 ALTER TABLE vandelay.queued_record ADD COLUMN quality INT NOT NULL DEFAULT 0;
1079 ALTER TABLE vandelay.bib_attr_definition DROP COLUMN ident;
1080
1081 CREATE TABLE vandelay.import_error (
1082     code        TEXT    PRIMARY KEY,
1083     description TEXT    NOT NULL -- i18n
1084 );
1085
1086 ALTER TABLE vandelay.queued_bib_record
1087     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1088     ADD COLUMN error_detail TEXT;
1089
1090 ALTER TABLE vandelay.bib_match
1091     DROP COLUMN field_type,
1092     DROP COLUMN matched_attr,
1093     ADD COLUMN quality INT NOT NULL DEFAULT 1,
1094     ADD COLUMN match_score INT NOT NULL DEFAULT 0;
1095
1096 ALTER TABLE vandelay.import_item
1097     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1098     ADD COLUMN error_detail TEXT,
1099     ADD COLUMN imported_as BIGINT REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED,
1100     ADD COLUMN import_time TIMESTAMP WITH TIME ZONE;
1101
1102 ALTER TABLE vandelay.merge_profile ADD COLUMN lwm_ratio NUMERIC;
1103
1104 CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
1105 DECLARE
1106     ldr         TEXT;
1107     tval        TEXT;
1108     tval_rec    RECORD;
1109     bval        TEXT;
1110     bval_rec    RECORD;
1111     retval      config.marc21_rec_type_map%ROWTYPE;
1112 BEGIN
1113     ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
1114
1115     IF ldr IS NULL OR ldr = '' THEN
1116         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1117         RETURN retval;
1118     END IF;
1119
1120     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
1121     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
1122
1123
1124     tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
1125     bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
1126
1127     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
1128
1129     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
1130
1131
1132     IF retval.code IS NULL THEN
1133         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
1134     END IF;
1135
1136     RETURN retval;
1137 END;
1138 $func$ LANGUAGE PLPGSQL;
1139
1140 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
1141 DECLARE
1142     rtype       TEXT;
1143     ff_pos      RECORD;
1144     tag_data    RECORD;
1145     val         TEXT;
1146 BEGIN
1147     rtype := (vandelay.marc21_record_type( marc )).code;
1148     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
1149         IF ff_pos.tag = 'ldr' THEN
1150             val := oils_xpath_string('//*[local-name()="leader"]', marc);
1151             IF val IS NOT NULL THEN
1152                 val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
1153                 RETURN val;
1154             END IF;
1155         ELSE
1156             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1157                 val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
1158                 RETURN val;
1159             END LOOP;
1160         END IF;
1161         val := REPEAT( ff_pos.default_val, ff_pos.length );
1162         RETURN val;
1163     END LOOP;
1164
1165     RETURN NULL;
1166 END;
1167 $func$ LANGUAGE PLPGSQL;
1168
1169 CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
1170 DECLARE
1171     tag_data    TEXT;
1172     rtype       TEXT;
1173     ff_pos      RECORD;
1174     output      biblio.record_ff_map%ROWTYPE;
1175 BEGIN
1176     rtype := (vandelay.marc21_record_type( marc )).code;
1177
1178     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
1179         output.ff_name  := ff_pos.fixed_field;
1180         output.ff_value := NULL;
1181
1182         IF ff_pos.tag = 'ldr' THEN
1183             output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
1184             IF output.ff_value IS NOT NULL THEN
1185                 output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
1186                 RETURN NEXT output;
1187                 output.ff_value := NULL;
1188             END IF;
1189         ELSE
1190             FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
1191                 output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
1192                 IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
1193                 RETURN NEXT output;
1194                 output.ff_value := NULL;
1195             END LOOP;
1196         END IF;
1197     
1198     END LOOP;
1199
1200     RETURN;
1201 END;
1202 $func$ LANGUAGE PLPGSQL;
1203
1204 CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
1205 DECLARE
1206     rowid   INT := 0;
1207     _007    TEXT;
1208     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
1209     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
1210     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
1211     retval  biblio.marc21_physical_characteristics%ROWTYPE;
1212 BEGIN
1213
1214     _007 := oils_xpath_string( '//*[@tag="007"]', marc );
1215
1216     IF _007 IS NOT NULL AND _007 <> '' THEN
1217         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
1218
1219         IF ptype.ptype_key IS NOT NULL THEN
1220             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
1221                 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 );
1222
1223                 IF pval.id IS NOT NULL THEN
1224                     rowid := rowid + 1;
1225                     retval.id := rowid;
1226                     retval.ptype := ptype.ptype_key;
1227                     retval.subfield := psf.id;
1228                     retval.value := pval.id;
1229                     RETURN NEXT retval;
1230                 END IF;
1231
1232             END LOOP;
1233         END IF;
1234     END IF;
1235
1236     RETURN;
1237 END;
1238 $func$ LANGUAGE PLPGSQL;
1239
1240 CREATE TYPE vandelay.flat_marc AS ( tag CHAR(3), ind1 TEXT, ind2 TEXT, subfield TEXT, value TEXT );
1241 CREATE OR REPLACE FUNCTION vandelay.flay_marc ( TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
1242
1243 use MARC::Record;
1244 use MARC::File::XML (BinaryEncoding => 'UTF-8');
1245 use MARC::Charset;
1246 use strict;
1247
1248 MARC::Charset->assume_unicode(1);
1249
1250 my $xml = shift;
1251 my $r = MARC::Record->new_from_xml( $xml );
1252
1253 return_next( { tag => 'LDR', value => $r->leader } );
1254
1255 for my $f ( $r->fields ) {
1256     if ($f->is_control_field) {
1257         return_next({ tag => $f->tag, value => $f->data });
1258     } else {
1259         for my $s ($f->subfields) {
1260             return_next({
1261                 tag      => $f->tag,
1262                 ind1     => $f->indicator(1),
1263                 ind2     => $f->indicator(2),
1264                 subfield => $s->[0],
1265                 value    => $s->[1]
1266             });
1267
1268             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
1269                 my $trim = $f->indicator(2) || 0;
1270                 return_next({
1271                     tag      => 'tnf',
1272                     ind1     => $f->indicator(1),
1273                     ind2     => $f->indicator(2),
1274                     subfield => 'a',
1275                     value    => substr( $s->[1], $trim )
1276                 });
1277             }
1278         }
1279     }
1280 }
1281
1282 return undef;
1283
1284 $func$ LANGUAGE PLPERLU;
1285
1286 CREATE OR REPLACE FUNCTION vandelay.flatten_marc ( marc TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
1287 DECLARE
1288     output  vandelay.flat_marc%ROWTYPE;
1289     field   RECORD;
1290 BEGIN
1291     FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
1292         output.ind1 := field.ind1;
1293         output.ind2 := field.ind2;
1294         output.tag := field.tag;
1295         output.subfield := field.subfield;
1296         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
1297             output.value := naco_normalize(field.value, field.subfield);
1298         ELSE
1299             output.value := field.value;
1300         END IF;
1301
1302         CONTINUE WHEN output.value IS NULL;
1303
1304         RETURN NEXT output;
1305     END LOOP;
1306 END;
1307 $func$ LANGUAGE PLPGSQL;
1308
1309 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT, attr_defs TEXT[]) RETURNS hstore AS $_$
1310 DECLARE
1311     transformed_xml TEXT;
1312     prev_xfrm       TEXT;
1313     normalizer      RECORD;
1314     xfrm            config.xml_transform%ROWTYPE;
1315     attr_value      TEXT;
1316     new_attrs       HSTORE := ''::HSTORE;
1317     attr_def        config.record_attr_definition%ROWTYPE;
1318 BEGIN
1319
1320     FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE name IN (SELECT * FROM UNNEST(attr_defs)) ORDER BY format LOOP
1321
1322         IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
1323             SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(x.value), COALESCE(attr_def.joiner,' ')) INTO attr_value
1324               FROM  vandelay.flatten_marc(xml) AS x
1325               WHERE x.tag LIKE attr_def.tag
1326                     AND CASE
1327                         WHEN attr_def.sf_list IS NOT NULL
1328                             THEN POSITION(x.subfield IN attr_def.sf_list) > 0
1329                         ELSE TRUE
1330                         END
1331               GROUP BY x.tag
1332               ORDER BY x.tag
1333               LIMIT 1;
1334
1335         ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
1336             attr_value := vandelay.marc21_extract_fixed_field(xml, attr_def.fixed_field);
1337
1338         ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
1339
1340             SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
1341
1342             -- See if we can skip the XSLT ... it's expensive
1343             IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
1344                 -- Can't skip the transform
1345                 IF xfrm.xslt <> '---' THEN
1346                     transformed_xml := oils_xslt_process(xml,xfrm.xslt);
1347                 ELSE
1348                     transformed_xml := xml;
1349                 END IF;
1350
1351                 prev_xfrm := xfrm.name;
1352             END IF;
1353
1354             IF xfrm.name IS NULL THEN
1355                 -- just grab the marcxml (empty) transform
1356                 SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
1357                 prev_xfrm := xfrm.name;
1358             END IF;
1359
1360             attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
1361
1362         ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
1363             SELECT  m.value::TEXT INTO attr_value
1364               FROM  vandelay.marc21_physical_characteristics(xml) v
1365                     JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
1366               WHERE v.subfield = attr_def.phys_char_sf
1367               LIMIT 1; -- Just in case ...
1368
1369         END IF;
1370
1371         -- apply index normalizers to attr_value
1372         FOR normalizer IN
1373             SELECT  n.func AS func,
1374                     n.param_count AS param_count,
1375                     m.params AS params
1376               FROM  config.index_normalizer n
1377                     JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
1378               WHERE attr = attr_def.name
1379               ORDER BY m.pos LOOP
1380                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
1381                     quote_literal( attr_value ) ||
1382                     CASE
1383                         WHEN normalizer.param_count > 0
1384                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
1385                             ELSE ''
1386                         END ||
1387                     ')' INTO attr_value;
1388
1389         END LOOP;
1390
1391         -- Add the new value to the hstore
1392         new_attrs := new_attrs || hstore( attr_def.name, attr_value );
1393
1394     END LOOP;
1395
1396     RETURN new_attrs;
1397 END;
1398 $_$ LANGUAGE PLPGSQL;
1399
1400 CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT ) RETURNS hstore AS $_$
1401     SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_ACCUM(name) FROM config.record_attr_definition));
1402 $_$ LANGUAGE SQL;
1403
1404 -- Everything between this comment and the beginning of the definition of
1405 -- vandelay.match_bib_record() is strictly in service of that function.
1406 CREATE TYPE vandelay.match_set_test_result AS (record BIGINT, quality INTEGER);
1407
1408 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
1409     match_set_id INTEGER, record_xml TEXT
1410 ) RETURNS SETOF vandelay.match_set_test_result AS $$
1411 DECLARE
1412     tags_rstore HSTORE;
1413     svf_rstore  HSTORE;
1414     coal        TEXT;
1415     joins       TEXT;
1416     query_      TEXT;
1417     wq          TEXT;
1418     qvalue      INTEGER;
1419     rec         RECORD;
1420 BEGIN
1421     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
1422     svf_rstore := vandelay.extract_rec_attrs(record_xml);
1423
1424     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
1425     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
1426
1427     -- generate the where clause and return that directly (into wq), and as
1428     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
1429     wq := vandelay.get_expr_from_match_set(match_set_id);
1430
1431     query_ := 'SELECT bre.id AS record, ';
1432
1433     -- qrows table is for the quality bits we add to the SELECT clause
1434     SELECT ARRAY_TO_STRING(
1435         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
1436     ) INTO coal FROM _vandelay_tmp_qrows;
1437
1438     -- our query string so far is the SELECT clause and the inital FROM.
1439     -- no JOINs yet nor the WHERE clause
1440     query_ := query_ || coal || ' AS quality ' || E'\n' ||
1441         'FROM biblio.record_entry bre ';
1442
1443     -- jrows table is for the joins we must make (and the real text conditions)
1444     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
1445         FROM _vandelay_tmp_jrows;
1446
1447     -- add those joins and the where clause to our query.
1448     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
1449
1450     -- this will return rows of record,quality
1451     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
1452         RETURN NEXT rec;
1453     END LOOP;
1454
1455     DROP TABLE _vandelay_tmp_qrows;
1456     DROP TABLE _vandelay_tmp_jrows;
1457     RETURN;
1458 END;
1459
1460 $$ LANGUAGE PLPGSQL;
1461
1462 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
1463     record_xml TEXT
1464 ) RETURNS HSTORE AS $$
1465 BEGIN
1466     RETURN (SELECT
1467         HSTORE(
1468             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
1469             ARRAY_ACCUM(value)
1470         )
1471         FROM (
1472             SELECT tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
1473                 FROM vandelay.flatten_marc(record_xml)
1474                 GROUP BY tag, subfield ORDER BY tag, subfield
1475         ) subquery
1476     );
1477 END;
1478 $$ LANGUAGE PLPGSQL;
1479
1480 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set(
1481     match_set_id INTEGER
1482 ) RETURNS TEXT AS $$
1483 DECLARE
1484     root    vandelay.match_set_point;
1485 BEGIN
1486     SELECT * INTO root FROM vandelay.match_set_point
1487         WHERE parent IS NULL AND match_set = match_set_id;
1488
1489     RETURN vandelay.get_expr_from_match_set_point(root);
1490 END;
1491 $$  LANGUAGE PLPGSQL;
1492
1493 CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set_point(
1494     node vandelay.match_set_point
1495 ) RETURNS TEXT AS $$
1496 DECLARE
1497     q           TEXT;
1498     i           INTEGER;
1499     this_op     TEXT;
1500     children    INTEGER[];
1501     child       vandelay.match_set_point;
1502 BEGIN
1503     SELECT ARRAY_ACCUM(id) INTO children FROM vandelay.match_set_point
1504         WHERE parent = node.id;
1505
1506     IF ARRAY_LENGTH(children, 1) > 0 THEN
1507         this_op := vandelay._get_expr_render_one(node);
1508         q := '(';
1509         i := 1;
1510         WHILE children[i] IS NOT NULL LOOP
1511             SELECT * INTO child FROM vandelay.match_set_point
1512                 WHERE id = children[i];
1513             IF i > 1 THEN
1514                 q := q || ' ' || this_op || ' ';
1515             END IF;
1516             i := i + 1;
1517             q := q || vandelay.get_expr_from_match_set_point(child);
1518         END LOOP;
1519         q := q || ')';
1520         RETURN q;
1521     ELSIF node.bool_op IS NULL THEN
1522         PERFORM vandelay._get_expr_push_qrow(node);
1523         PERFORM vandelay._get_expr_push_jrow(node);
1524         RETURN vandelay._get_expr_render_one(node);
1525     ELSE
1526         RETURN '';
1527     END IF;
1528 END;
1529 $$  LANGUAGE PLPGSQL;
1530
1531 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_qrow(
1532     node vandelay.match_set_point
1533 ) RETURNS VOID AS $$
1534 DECLARE
1535 BEGIN
1536     INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
1537 END;
1538 $$ LANGUAGE PLPGSQL;
1539
1540 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
1541     node vandelay.match_set_point
1542 ) RETURNS VOID AS $$
1543 DECLARE
1544     jrow        TEXT;
1545     my_alias    TEXT;
1546     op          TEXT;
1547     tagkey      TEXT;
1548 BEGIN
1549     IF node.negate THEN
1550         op := '<>';
1551     ELSE
1552         op := '=';
1553     END IF;
1554
1555     IF node.tag IS NOT NULL THEN
1556         tagkey := node.tag;
1557         IF node.subfield IS NOT NULL THEN
1558             tagkey := tagkey || node.subfield;
1559         END IF;
1560     END IF;
1561
1562     my_alias := 'n' || node.id::TEXT;
1563
1564     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
1565         ' AS quality FROM metabib.';
1566     IF node.tag IS NOT NULL THEN
1567         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
1568             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
1569             node.tag || '''';
1570         IF node.subfield IS NOT NULL THEN
1571             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
1572                 node.subfield || '''';
1573         END IF;
1574         jrow := jrow || ' AND (' || my_alias || '.value ' || op ||
1575             ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
1576     ELSE    -- svf
1577         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
1578             my_alias || '.id = bre.id AND (' ||
1579             my_alias || '.attrs->''' || node.svf ||
1580             ''' ' || op || ' $2->''' || node.svf || '''))';
1581     END IF;
1582     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
1583 END;
1584 $$ LANGUAGE PLPGSQL;
1585
1586 CREATE OR REPLACE FUNCTION vandelay._get_expr_render_one(
1587     node vandelay.match_set_point
1588 ) RETURNS TEXT AS $$
1589 DECLARE
1590     s           TEXT;
1591 BEGIN
1592     IF node.bool_op IS NOT NULL THEN
1593         RETURN node.bool_op;
1594     ELSE
1595         RETURN '(n' || node.id::TEXT || '.id IS NOT NULL)';
1596     END IF;
1597 END;
1598 $$ LANGUAGE PLPGSQL;
1599
1600 CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
1601 DECLARE
1602     incoming_existing_id    TEXT;
1603     test_result             vandelay.match_set_test_result%ROWTYPE;
1604     tmp_rec                 BIGINT;
1605     match_set               INT;
1606 BEGIN
1607     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1608         RETURN NEW;
1609     END IF;
1610
1611     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
1612
1613     SELECT q.match_set INTO match_set FROM vandelay.bib_queue q WHERE q.id = NEW.queue;
1614
1615     IF match_set IS NOT NULL THEN
1616         NEW.quality := vandelay.measure_record_quality( NEW.marc, match_set );
1617     END IF;
1618
1619     -- Perfect matches on 901$c exit early with a match with high quality.
1620     incoming_existing_id :=
1621         oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
1622
1623     IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
1624         SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
1625         IF tmp_rec IS NOT NULL THEN
1626             INSERT INTO vandelay.bib_match (queued_record, eg_record, match_score, quality) 
1627                 SELECT
1628                     NEW.id, 
1629                     b.id,
1630                     9999,
1631                     -- note: no match_set means quality==0
1632                     vandelay.measure_record_quality( b.marc, match_set )
1633                 FROM biblio.record_entry b
1634                 WHERE id = incoming_existing_id::bigint;
1635         END IF;
1636     END IF;
1637
1638     IF match_set IS NULL THEN
1639         RETURN NEW;
1640     END IF;
1641
1642     FOR test_result IN SELECT * FROM
1643         vandelay.match_set_test_marcxml(match_set, NEW.marc) LOOP
1644
1645         INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
1646             SELECT  
1647                 NEW.id,
1648                 test_result.record,
1649                 test_result.quality,
1650                 vandelay.measure_record_quality( b.marc, match_set )
1651                 FROM  biblio.record_entry b
1652                 WHERE id = test_result.record;
1653
1654     END LOOP;
1655
1656     RETURN NEW;
1657 END;
1658 $func$ LANGUAGE PLPGSQL;
1659
1660 CREATE OR REPLACE FUNCTION vandelay.measure_record_quality ( xml TEXT, match_set_id INT ) RETURNS INT AS $_$
1661 DECLARE
1662     out_q   INT := 0;
1663     rvalue  TEXT;
1664     test    vandelay.match_set_quality%ROWTYPE;
1665 BEGIN
1666
1667     FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
1668         IF test.tag IS NOT NULL THEN
1669             FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
1670                 IF test.value = rvalue THEN
1671                     out_q := out_q + test.quality;
1672                 END IF;
1673             END LOOP;
1674         ELSE
1675             IF test.value = vandelay.extract_rec_attrs(xml, ARRAY[test.svf]) -> test.svf THEN
1676                 out_q := out_q + test.quality;
1677             END IF;
1678         END IF;
1679     END LOOP;
1680
1681     RETURN out_q;
1682 END;
1683 $_$ LANGUAGE PLPGSQL;
1684
1685
1686 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
1687 DECLARE
1688     merge_profile   vandelay.merge_profile%ROWTYPE;
1689     dyn_profile     vandelay.compile_profile%ROWTYPE;
1690     editor_string   TEXT;
1691     editor_id       INT;
1692     source_marc     TEXT;
1693     target_marc     TEXT;
1694     eg_marc         TEXT;
1695     v_marc          TEXT;
1696     replace_rule    TEXT;
1697 BEGIN
1698
1699     SELECT  q.marc INTO v_marc
1700       FROM  vandelay.queued_record q
1701             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
1702       LIMIT 1;
1703
1704     IF v_marc IS NULL THEN
1705         -- RAISE NOTICE 'no marc for vandelay or bib record';
1706         RETURN FALSE;
1707     END IF;
1708
1709     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
1710         UPDATE  vandelay.queued_bib_record
1711           SET   imported_as = eg_id,
1712                 import_time = NOW()
1713           WHERE id = import_id;
1714
1715         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
1716
1717         IF editor_string IS NOT NULL AND editor_string <> '' THEN
1718             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
1719
1720             IF editor_id IS NULL THEN
1721                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
1722             END IF;
1723
1724             IF editor_id IS NOT NULL THEN
1725                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
1726             END IF;
1727         END IF;
1728
1729         RETURN TRUE;
1730     END IF;
1731
1732     -- RAISE NOTICE 'update of biblio.record_entry failed';
1733
1734     RETURN FALSE;
1735
1736 END;
1737 $$ LANGUAGE PLPGSQL;
1738
1739
1740 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 $$
1741 DECLARE
1742     eg_id           BIGINT;
1743     lwm_ratio_value NUMERIC;
1744 BEGIN
1745
1746     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
1747
1748     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
1749
1750     IF FOUND THEN
1751         -- RAISE NOTICE 'already imported, cannot auto-overlay'
1752         RETURN FALSE;
1753     END IF;
1754
1755     SELECT  m.eg_record INTO eg_id
1756       FROM  vandelay.bib_match m
1757             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
1758             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
1759             JOIN biblio.record_entry r ON (r.id = m.eg_record)
1760       WHERE m.queued_record = import_id
1761             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
1762       ORDER BY  m.match_score DESC, -- required match score
1763                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
1764                 m.id -- when in doubt, use the first match
1765       LIMIT 1;
1766
1767     IF eg_id IS NULL THEN
1768         -- RAISE NOTICE 'incoming record is not of high enough quality';
1769         RETURN FALSE;
1770     END IF;
1771
1772     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
1773 END;
1774 $$ LANGUAGE PLPGSQL;
1775
1776 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 $$
1777 DECLARE
1778     eg_id           BIGINT;
1779     lwm_ratio_value NUMERIC;
1780 BEGIN
1781
1782     lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
1783
1784     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
1785
1786     IF FOUND THEN
1787         -- RAISE NOTICE 'already imported, cannot auto-overlay'
1788         RETURN FALSE;
1789     END IF;
1790
1791     SELECT  m.eg_record INTO eg_id
1792       FROM  vandelay.bib_match m
1793             JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
1794             JOIN vandelay.bib_queue q ON (qr.queue = q.id)
1795             JOIN biblio.record_entry r ON (r.id = m.eg_record)
1796       WHERE m.queued_record = import_id
1797             AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
1798       ORDER BY  m.match_score DESC, -- required match score
1799                 qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
1800                 m.id -- when in doubt, use the first match
1801       LIMIT 1;
1802
1803     IF eg_id IS NULL THEN
1804         -- RAISE NOTICE 'incoming record is not of high enough quality';
1805         RETURN FALSE;
1806     END IF;
1807
1808     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
1809 END;
1810 $$ LANGUAGE PLPGSQL;
1811
1812
1813 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 $$
1814 DECLARE
1815     queued_record   vandelay.queued_bib_record%ROWTYPE;
1816 BEGIN
1817
1818     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
1819
1820         IF vandelay.auto_overlay_bib_record_with_best( queued_record.id, merge_profile_id, lwm_ratio_value ) THEN
1821             RETURN NEXT queued_record.id;
1822         END IF;
1823
1824     END LOOP;
1825
1826     RETURN;
1827     
1828 END;
1829 $$ LANGUAGE PLPGSQL;
1830
1831 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( import_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
1832     SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
1833 $$ LANGUAGE SQL;
1834
1835 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_marc ( ) RETURNS TRIGGER AS $$
1836 DECLARE
1837     value   TEXT;
1838     atype   TEXT;
1839     adef    RECORD;
1840 BEGIN
1841     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1842         RETURN NEW;
1843     END IF;
1844
1845     FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
1846
1847         SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
1848         IF (value IS NOT NULL AND value <> '') THEN
1849             INSERT INTO vandelay.queued_bib_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1850         END IF;
1851
1852     END LOOP;
1853
1854     RETURN NULL;
1855 END;
1856 $$ LANGUAGE PLPGSQL;
1857
1858 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
1859 DECLARE
1860     attr_def    BIGINT;
1861     item_data   vandelay.import_item%ROWTYPE;
1862 BEGIN
1863
1864     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1865         RETURN NEW;
1866     END IF;
1867
1868     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
1869
1870     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
1871         INSERT INTO vandelay.import_item (
1872             record,
1873             definition,
1874             owning_lib,
1875             circ_lib,
1876             call_number,
1877             copy_number,
1878             status,
1879             location,
1880             circulate,
1881             deposit,
1882             deposit_amount,
1883             ref,
1884             holdable,
1885             price,
1886             barcode,
1887             circ_modifier,
1888             circ_as_type,
1889             alert_message,
1890             pub_note,
1891             priv_note,
1892             opac_visible
1893         ) VALUES (
1894             NEW.id,
1895             item_data.definition,
1896             item_data.owning_lib,
1897             item_data.circ_lib,
1898             item_data.call_number,
1899             item_data.copy_number,
1900             item_data.status,
1901             item_data.location,
1902             item_data.circulate,
1903             item_data.deposit,
1904             item_data.deposit_amount,
1905             item_data.ref,
1906             item_data.holdable,
1907             item_data.price,
1908             item_data.barcode,
1909             item_data.circ_modifier,
1910             item_data.circ_as_type,
1911             item_data.alert_message,
1912             item_data.pub_note,
1913             item_data.priv_note,
1914             item_data.opac_visible
1915         );
1916     END LOOP;
1917
1918     RETURN NULL;
1919 END;
1920 $func$ LANGUAGE PLPGSQL;
1921
1922 CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
1923 BEGIN
1924     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1925         RETURN NEW;
1926     END IF;
1927
1928     DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
1929     DELETE FROM vandelay.import_item WHERE record = OLD.id;
1930
1931     IF TG_OP = 'UPDATE' THEN
1932         RETURN NEW;
1933     END IF;
1934     RETURN OLD;
1935 END;
1936 $$ LANGUAGE PLPGSQL;
1937
1938 -- ALTER TABLEs...
1939
1940 DROP TRIGGER zz_match_bibs_trigger ON vandelay.queued_bib_record;
1941 CREATE TRIGGER zz_match_bibs_trigger
1942     BEFORE INSERT OR UPDATE ON vandelay.queued_bib_record
1943     FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
1944
1945 CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
1946 DECLARE
1947     value   TEXT;
1948     atype   TEXT;
1949     adef    RECORD;
1950 BEGIN
1951     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1952         RETURN NEW;
1953     END IF;
1954
1955     FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
1956
1957         SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
1958         IF (value IS NOT NULL AND value <> '') THEN
1959             INSERT INTO vandelay.queued_authority_record_attr (record, field, attr_value) VALUES (NEW.id, adef.id, value);
1960         END IF;
1961
1962     END LOOP;
1963
1964     RETURN NULL;
1965 END;
1966 $$ LANGUAGE PLPGSQL;
1967
1968 ALTER TABLE vandelay.authority_attr_definition DROP COLUMN ident;
1969 ALTER TABLE vandelay.queued_authority_record
1970     ADD COLUMN import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
1971     ADD COLUMN error_detail TEXT;
1972
1973 ALTER TABLE vandelay.authority_match DROP COLUMN matched_attr;
1974
1975 CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
1976 BEGIN
1977     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
1978         RETURN NEW;
1979     END IF;
1980
1981     DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
1982     IF TG_OP = 'UPDATE' THEN
1983         RETURN NEW;
1984     END IF;
1985     RETURN OLD;
1986 END;
1987 $$ LANGUAGE PLPGSQL;
1988
1989 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
1990 DECLARE
1991         auth    authority.record_entry%ROWTYPE;
1992         output  authority.full_rec%ROWTYPE;
1993         field   RECORD;
1994 BEGIN
1995         SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
1996
1997         FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
1998                 output.record := rid;
1999                 output.ind1 := field.ind1;
2000                 output.ind2 := field.ind2;
2001                 output.tag := field.tag;
2002                 output.subfield := field.subfield;
2003                 output.value := field.value;
2004
2005                 RETURN NEXT output;
2006         END LOOP;
2007 END;
2008 $func$ LANGUAGE PLPGSQL;
2009
2010 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
2011 DECLARE
2012         bib     biblio.record_entry%ROWTYPE;
2013         output  metabib.full_rec%ROWTYPE;
2014         field   RECORD;
2015 BEGIN
2016         SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
2017
2018         FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
2019                 output.record := rid;
2020                 output.ind1 := field.ind1;
2021                 output.ind2 := field.ind2;
2022                 output.tag := field.tag;
2023                 output.subfield := field.subfield;
2024                 output.value := field.value;
2025
2026                 RETURN NEXT output;
2027         END LOOP;
2028 END;
2029 $func$ LANGUAGE PLPGSQL;
2030
2031 -----------------------------------------------
2032 -- Seed data for import errors
2033 -----------------------------------------------
2034
2035 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'general.unknown', oils_i18n_gettext('general.unknown', 'Import or Overlay failed', 'vie', 'description') );
2036 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') );
2037 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') );
2038 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') );
2039 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') );
2040 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') );
2041 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') );
2042 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') );
2043 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.xml.malformed', oils_i18n_gettext('import.xml.malformed', 'Malformed record cause Import failure', 'vie', 'description') );
2044 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.xml.malformed', oils_i18n_gettext('overlay.xml.malformed', 'Malformed record cause Overlay failure', 'vie', 'description') );
2045 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.record.quality', oils_i18n_gettext('overlay.record.quality', 'New record had insufficient quality', 'vie', 'description') );
2046
2047
2048 ----------------------------------------------------------------
2049 -- Seed data for queued record/item exports
2050 ----------------------------------------------------------------
2051
2052 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
2053         'vandelay.queued_bib_record.print',
2054         'vqbr', 
2055         oils_i18n_gettext(
2056             'vandelay.queued_bib_record.print',
2057             'Print output has been requested for records in an Importer Bib Queue.',
2058             'ath',
2059             'description'
2060         ), 
2061         FALSE
2062     )
2063     ,(
2064         'vandelay.queued_bib_record.csv',
2065         'vqbr', 
2066         oils_i18n_gettext(
2067             'vandelay.queued_bib_record.csv',
2068             'CSV output has been requested for records in an Importer Bib Queue.',
2069             'ath',
2070             'description'
2071         ), 
2072         FALSE
2073     )
2074     ,(
2075         'vandelay.queued_bib_record.email',
2076         'vqbr', 
2077         oils_i18n_gettext(
2078             'vandelay.queued_bib_record.email',
2079             'An email has been requested for records in an Importer Bib Queue.',
2080             'ath',
2081             'description'
2082         ), 
2083         FALSE
2084     )
2085     ,(
2086         'vandelay.queued_auth_record.print',
2087         'vqar', 
2088         oils_i18n_gettext(
2089             'vandelay.queued_auth_record.print',
2090             'Print output has been requested for records in an Importer Authority Queue.',
2091             'ath',
2092             'description'
2093         ), 
2094         FALSE
2095     )
2096     ,(
2097         'vandelay.queued_auth_record.csv',
2098         'vqar', 
2099         oils_i18n_gettext(
2100             'vandelay.queued_auth_record.csv',
2101             'CSV output has been requested for records in an Importer Authority Queue.',
2102             'ath',
2103             'description'
2104         ), 
2105         FALSE
2106     )
2107     ,(
2108         'vandelay.queued_auth_record.email',
2109         'vqar', 
2110         oils_i18n_gettext(
2111             'vandelay.queued_auth_record.email',
2112             'An email has been requested for records in an Importer Authority Queue.',
2113             'ath',
2114             'description'
2115         ), 
2116         FALSE
2117     )
2118     ,(
2119         'vandelay.import_items.print',
2120         'vii', 
2121         oils_i18n_gettext(
2122             'vandelay.import_items.print',
2123             'Print output has been requested for Import Items from records in an Importer Bib Queue.',
2124             'ath',
2125             'description'
2126         ), 
2127         FALSE
2128     )
2129     ,(
2130         'vandelay.import_items.csv',
2131         'vii', 
2132         oils_i18n_gettext(
2133             'vandelay.import_items.csv',
2134             'CSV output has been requested for Import Items from records in an Importer Bib Queue.',
2135             'ath',
2136             'description'
2137         ), 
2138         FALSE
2139     )
2140     ,(
2141         'vandelay.import_items.email',
2142         'vii', 
2143         oils_i18n_gettext(
2144             'vandelay.import_items.email',
2145             'An email has been requested for Import Items from records in an Importer Bib Queue.',
2146             'ath',
2147             'description'
2148         ), 
2149         FALSE
2150     )
2151 ;
2152
2153 INSERT INTO action_trigger.event_definition (
2154         id,
2155         active,
2156         owner,
2157         name,
2158         hook,
2159         validator,
2160         reactor,
2161         group_field,
2162         granularity,
2163         template
2164     ) VALUES (
2165         39,
2166         TRUE,
2167         1,
2168         'Print Output for Queued Bib Records',
2169         'vandelay.queued_bib_record.print',
2170         'NOOP_True',
2171         'ProcessTemplate',
2172         'queue.owner',
2173         'print-on-demand',
2174 $$
2175 [%- USE date -%]
2176 <pre>
2177 Queue ID: [% target.0.queue.id %]
2178 Queue Name: [% target.0.queue.name %]
2179 Queue Type: [% target.0.queue.queue_type %]
2180 Complete? [% target.0.queue.complete %]
2181
2182     [% FOR vqbr IN target %]
2183 =-=-=
2184  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2185  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2186  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2187  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2188  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2189  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2190  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2191  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2192  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2193  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2194  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2195  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2196  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2197  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2198  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2199
2200     [% END %]
2201 </pre>
2202 $$
2203     )
2204 ;
2205
2206 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2207     39, 'attributes')
2208     ,( 39, 'queue')
2209 ;
2210
2211 INSERT INTO action_trigger.event_definition (
2212         id,
2213         active,
2214         owner,
2215         name,
2216         hook,
2217         validator,
2218         reactor,
2219         group_field,
2220         granularity,
2221         template
2222     ) VALUES (
2223         40,
2224         TRUE,
2225         1,
2226         'CSV Output for Queued Bib Records',
2227         'vandelay.queued_bib_record.csv',
2228         'NOOP_True',
2229         'ProcessTemplate',
2230         'queue.owner',
2231         'print-on-demand',
2232 $$
2233 [%- USE date -%]
2234 "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"
2235 [% 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('"', '""') %]"
2236 [% END %]
2237 $$
2238     )
2239 ;
2240
2241 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2242     40, 'attributes')
2243     ,( 40, 'queue')
2244 ;
2245
2246 INSERT INTO action_trigger.event_definition (
2247         id,
2248         active,
2249         owner,
2250         name,
2251         hook,
2252         validator,
2253         reactor,
2254         group_field,
2255         granularity,
2256         template
2257     ) VALUES (
2258         41,
2259         TRUE,
2260         1,
2261         'Email Output for Queued Bib Records',
2262         'vandelay.queued_bib_record.email',
2263         'NOOP_True',
2264         'SendEmail',
2265         'queue.owner',
2266         NULL,
2267 $$
2268 [%- USE date -%]
2269 [%- SET user = target.0.queue.owner -%]
2270 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2271 From: [%- params.sender_email || default_sender %]
2272 Subject: Bibs from Import Queue
2273
2274 Queue ID: [% target.0.queue.id %]
2275 Queue Name: [% target.0.queue.name %]
2276 Queue Type: [% target.0.queue.queue_type %]
2277 Complete? [% target.0.queue.complete %]
2278
2279     [% FOR vqbr IN target %]
2280 =-=-=
2281  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
2282  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
2283  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
2284  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
2285  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
2286  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
2287  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
2288  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
2289  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
2290  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
2291  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
2292  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
2293  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
2294  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
2295  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
2296
2297     [% END %]
2298
2299 $$
2300     )
2301 ;
2302
2303 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2304     41, 'attributes')
2305     ,( 41, 'queue')
2306     ,( 41, 'queue.owner')
2307 ;
2308
2309 INSERT INTO action_trigger.event_definition (
2310         id,
2311         active,
2312         owner,
2313         name,
2314         hook,
2315         validator,
2316         reactor,
2317         group_field,
2318         granularity,
2319         template
2320     ) VALUES (
2321         42,
2322         TRUE,
2323         1,
2324         'Print Output for Queued Authority Records',
2325         'vandelay.queued_auth_record.print',
2326         'NOOP_True',
2327         'ProcessTemplate',
2328         'queue.owner',
2329         'print-on-demand',
2330 $$
2331 [%- USE date -%]
2332 <pre>
2333 Queue ID: [% target.0.queue.id %]
2334 Queue Name: [% target.0.queue.name %]
2335 Queue Type: [% target.0.queue.queue_type %]
2336 Complete? [% target.0.queue.complete %]
2337
2338     [% FOR vqar IN target %]
2339 =-=-=
2340  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2341
2342     [% END %]
2343 </pre>
2344 $$
2345     )
2346 ;
2347
2348 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2349     42, 'attributes')
2350     ,( 42, 'queue')
2351 ;
2352
2353 INSERT INTO action_trigger.event_definition (
2354         id,
2355         active,
2356         owner,
2357         name,
2358         hook,
2359         validator,
2360         reactor,
2361         group_field,
2362         granularity,
2363         template
2364     ) VALUES (
2365         43,
2366         TRUE,
2367         1,
2368         'CSV Output for Queued Authority Records',
2369         'vandelay.queued_auth_record.csv',
2370         'NOOP_True',
2371         'ProcessTemplate',
2372         'queue.owner',
2373         'print-on-demand',
2374 $$
2375 [%- USE date -%]
2376 "Record Identifier"
2377 [% FOR vqar IN target %]"[% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) | replace('"', '""') %]"
2378 [% END %]
2379 $$
2380     )
2381 ;
2382
2383 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2384     43, 'attributes')
2385     ,( 43, 'queue')
2386 ;
2387
2388 INSERT INTO action_trigger.event_definition (
2389         id,
2390         active,
2391         owner,
2392         name,
2393         hook,
2394         validator,
2395         reactor,
2396         group_field,
2397         granularity,
2398         template
2399     ) VALUES (
2400         44,
2401         TRUE,
2402         1,
2403         'Email Output for Queued Authority Records',
2404         'vandelay.queued_auth_record.email',
2405         'NOOP_True',
2406         'SendEmail',
2407         'queue.owner',
2408         NULL,
2409 $$
2410 [%- USE date -%]
2411 [%- SET user = target.0.queue.owner -%]
2412 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2413 From: [%- params.sender_email || default_sender %]
2414 Subject: Authorities from Import Queue
2415
2416 Queue ID: [% target.0.queue.id %]
2417 Queue Name: [% target.0.queue.name %]
2418 Queue Type: [% target.0.queue.queue_type %]
2419 Complete? [% target.0.queue.complete %]
2420
2421     [% FOR vqar IN target %]
2422 =-=-=
2423  Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
2424
2425     [% END %]
2426
2427 $$
2428     )
2429 ;
2430
2431 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2432     44, 'attributes')
2433     ,( 44, 'queue')
2434     ,( 44, 'queue.owner')
2435 ;
2436
2437 INSERT INTO action_trigger.event_definition (
2438         id,
2439         active,
2440         owner,
2441         name,
2442         hook,
2443         validator,
2444         reactor,
2445         group_field,
2446         granularity,
2447         template
2448     ) VALUES (
2449         45,
2450         TRUE,
2451         1,
2452         'Print Output for Import Items from Queued Bib Records',
2453         'vandelay.import_items.print',
2454         'NOOP_True',
2455         'ProcessTemplate',
2456         'record.queue.owner',
2457         'print-on-demand',
2458 $$
2459 [%- USE date -%]
2460 <pre>
2461 Queue ID: [% target.0.record.queue.id %]
2462 Queue Name: [% target.0.record.queue.name %]
2463 Queue Type: [% target.0.record.queue.queue_type %]
2464 Complete? [% target.0.record.queue.complete %]
2465
2466     [% FOR vii IN target %]
2467 =-=-=
2468  Import Item ID         | [% vii.id %]
2469  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2470  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2471  Attribute Definition   | [% vii.definition %]
2472  Import Error           | [% vii.import_error %]
2473  Import Error Detail    | [% vii.error_detail %]
2474  Owning Library         | [% vii.owning_lib %]
2475  Circulating Library    | [% vii.circ_lib %]
2476  Call Number            | [% vii.call_number %]
2477  Copy Number            | [% vii.copy_number %]
2478  Status                 | [% vii.status.name %]
2479  Shelving Location      | [% vii.location.name %]
2480  Circulate              | [% vii.circulate %]
2481  Deposit                | [% vii.deposit %]
2482  Deposit Amount         | [% vii.deposit_amount %]
2483  Reference              | [% vii.ref %]
2484  Holdable               | [% vii.holdable %]
2485  Price                  | [% vii.price %]
2486  Barcode                | [% vii.barcode %]
2487  Circulation Modifier   | [% vii.circ_modifier %]
2488  Circulate As MARC Type | [% vii.circ_as_type %]
2489  Alert Message          | [% vii.alert_message %]
2490  Public Note            | [% vii.pub_note %]
2491  Private Note           | [% vii.priv_note %]
2492  OPAC Visible           | [% vii.opac_visible %]
2493
2494     [% END %]
2495 </pre>
2496 $$
2497     )
2498 ;
2499
2500 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2501     45, 'record')
2502     ,( 45, 'record.attributes')
2503     ,( 45, 'record.queue')
2504     ,( 45, 'record.queue.owner')
2505 ;
2506
2507 INSERT INTO action_trigger.event_definition (
2508         id,
2509         active,
2510         owner,
2511         name,
2512         hook,
2513         validator,
2514         reactor,
2515         group_field,
2516         granularity,
2517         template
2518     ) VALUES (
2519         46,
2520         TRUE,
2521         1,
2522         'CSV Output for Import Items from Queued Bib Records',
2523         'vandelay.import_items.csv',
2524         'NOOP_True',
2525         'ProcessTemplate',
2526         'record.queue.owner',
2527         'print-on-demand',
2528 $$
2529 [%- USE date -%]
2530 "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"
2531 [% 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('"', '""') %]"
2532 [% END %]
2533 $$
2534     )
2535 ;
2536
2537 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2538     46, 'record')
2539     ,( 46, 'record.attributes')
2540     ,( 46, 'record.queue')
2541     ,( 46, 'record.queue.owner')
2542 ;
2543
2544 INSERT INTO action_trigger.event_definition (
2545         id,
2546         active,
2547         owner,
2548         name,
2549         hook,
2550         validator,
2551         reactor,
2552         group_field,
2553         granularity,
2554         template
2555     ) VALUES (
2556         47,
2557         TRUE,
2558         1,
2559         'Email Output for Import Items from Queued Bib Records',
2560         'vandelay.import_items.email',
2561         'NOOP_True',
2562         'SendEmail',
2563         'record.queue.owner',
2564         NULL,
2565 $$
2566 [%- USE date -%]
2567 [%- SET user = target.0.record.queue.owner -%]
2568 To: [%- params.recipient_email || user.email || 'root@localhost' %]
2569 From: [%- params.sender_email || default_sender %]
2570 Subject: Import Items from Import Queue
2571
2572 Queue ID: [% target.0.record.queue.id %]
2573 Queue Name: [% target.0.record.queue.name %]
2574 Queue Type: [% target.0.record.queue.queue_type %]
2575 Complete? [% target.0.record.queue.complete %]
2576
2577     [% FOR vii IN target %]
2578 =-=-=
2579  Import Item ID         | [% vii.id %]
2580  Title of work          | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
2581  ISBN                   | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
2582  Attribute Definition   | [% vii.definition %]
2583  Import Error           | [% vii.import_error %]
2584  Import Error Detail    | [% vii.error_detail %]
2585  Owning Library         | [% vii.owning_lib %]
2586  Circulating Library    | [% vii.circ_lib %]
2587  Call Number            | [% vii.call_number %]
2588  Copy Number            | [% vii.copy_number %]
2589  Status                 | [% vii.status.name %]
2590  Shelving Location      | [% vii.location.name %]
2591  Circulate              | [% vii.circulate %]
2592  Deposit                | [% vii.deposit %]
2593  Deposit Amount         | [% vii.deposit_amount %]
2594  Reference              | [% vii.ref %]
2595  Holdable               | [% vii.holdable %]
2596  Price                  | [% vii.price %]
2597  Barcode                | [% vii.barcode %]
2598  Circulation Modifier   | [% vii.circ_modifier %]
2599  Circulate As MARC Type | [% vii.circ_as_type %]
2600  Alert Message          | [% vii.alert_message %]
2601  Public Note            | [% vii.pub_note %]
2602  Private Note           | [% vii.priv_note %]
2603  OPAC Visible           | [% vii.opac_visible %]
2604
2605     [% END %]
2606 $$
2607     )
2608 ;
2609
2610 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
2611     47, 'record')
2612     ,( 47, 'record.attributes')
2613     ,( 47, 'record.queue')
2614     ,( 47, 'record.queue.owner')
2615 ;
2616
2617
2618
2619 SELECT evergreen.upgrade_deps_block_check('0574', :eg_version);
2620
2621 UPDATE action_trigger.event_definition SET template =
2622 $$
2623 [%- USE date -%]
2624 <style>
2625     table { border-collapse: collapse; }
2626     td { padding: 5px; border-bottom: 1px solid #888; }
2627     th { font-weight: bold; }
2628 </style>
2629 [%
2630     # Sort the holds into copy-location buckets
2631     # In the main print loop, sort each bucket by callnumber before printing
2632     SET holds_list = [];
2633     SET loc_data = [];
2634     SET current_location = target.0.current_copy.location.id;
2635     FOR hold IN target;
2636         IF current_location != hold.current_copy.location.id;
2637             SET current_location = hold.current_copy.location.id;
2638             holds_list.push(loc_data);
2639             SET loc_data = [];
2640         END;
2641         SET hold_data = {
2642             'hold' => hold,
2643             'callnumber' => hold.current_copy.call_number.label
2644         };
2645         loc_data.push(hold_data);
2646     END;
2647     holds_list.push(loc_data)
2648 %]
2649 <table>
2650     <thead>
2651         <tr>
2652             <th>Title</th>
2653             <th>Author</th>
2654             <th>Shelving Location</th>
2655             <th>Call Number</th>
2656             <th>Barcode/Part</th>
2657             <th>Patron</th>
2658         </tr>
2659     </thead>
2660     <tbody>
2661     [% FOR loc_data IN holds_list  %]
2662         [% FOR hold_data IN loc_data.sort('callnumber') %]
2663             [%
2664                 SET hold = hold_data.hold;
2665                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
2666             %]
2667             <tr>
2668                 <td>[% copy_data.title | truncate %]</td>
2669                 <td>[% copy_data.author | truncate %]</td>
2670                 <td>[% hold.current_copy.location.name %]</td>
2671                 <td>[% hold.current_copy.call_number.label %]</td>
2672                 <td>[% hold.current_copy.barcode %]
2673                     [% FOR part IN hold.current_copy.parts %]
2674                        [% part.part.label %]
2675                     [% END %]
2676                 </td>
2677                 <td>[% hold.usr.card.barcode %]</td>
2678             </tr>
2679         [% END %]
2680     [% END %]
2681     <tbody>
2682 </table>
2683 $$
2684     WHERE id = 35;
2685
2686 INSERT INTO action_trigger.environment (
2687         event_def,
2688         path
2689     ) VALUES
2690         (35, 'current_copy.parts'),
2691         (35, 'current_copy.parts.part')
2692 ;
2693
2694
2695 -- Evergreen DB patch XXXX.schema.authority-control-sets.sql
2696 --
2697 -- Schema upgrade to add Authority Control Set functionality
2698 --
2699
2700
2701 -- check whether patch can be applied
2702 SELECT evergreen.upgrade_deps_block_check('0575', :eg_version);
2703
2704 CREATE TABLE authority.control_set (
2705     id          SERIAL  PRIMARY KEY,
2706     name        TEXT    NOT NULL UNIQUE, -- i18n
2707     description TEXT                     -- i18n
2708 );
2709
2710 CREATE TABLE authority.control_set_authority_field (
2711     id          SERIAL  PRIMARY KEY,
2712     main_entry  INT     REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2713     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2714     tag         CHAR(3) NOT NULL,
2715     nfi CHAR(1),
2716     sf_list     TEXT    NOT NULL,
2717     name        TEXT    NOT NULL, -- i18n
2718     description TEXT              -- i18n
2719 );
2720
2721 CREATE TABLE authority.control_set_bib_field (
2722     id              SERIAL  PRIMARY KEY,
2723     authority_field INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2724     tag             CHAR(3) NOT NULL
2725 );
2726
2727 CREATE TABLE authority.thesaurus (
2728     code        TEXT    PRIMARY KEY,     -- MARC21 thesaurus code
2729     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2730     name        TEXT    NOT NULL UNIQUE, -- i18n
2731     description TEXT                     -- i18n
2732 );
2733
2734 CREATE TABLE authority.browse_axis (
2735     code        TEXT    PRIMARY KEY,
2736     name        TEXT    UNIQUE NOT NULL, -- i18n
2737     sorter      TEXT    REFERENCES config.record_attr_definition (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2738     description TEXT
2739 );
2740
2741 CREATE TABLE authority.browse_axis_authority_field_map (
2742     id          SERIAL  PRIMARY KEY,
2743     axis        TEXT    NOT NULL REFERENCES authority.browse_axis (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
2744     field       INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
2745 );
2746
2747 ALTER TABLE authority.record_entry ADD COLUMN control_set INT REFERENCES authority.control_set (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED;
2748 ALTER TABLE authority.rec_descriptor DROP COLUMN char_encoding, ADD COLUMN encoding_level TEXT, ADD COLUMN thesaurus TEXT;
2749
2750 CREATE INDEX authority_full_rec_value_index ON authority.full_rec (value);
2751 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);
2752  
2753 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
2754 DECLARE
2755     acsaf           authority.control_set_authority_field%ROWTYPE;
2756     tag_used        TEXT;
2757     sf              TEXT;
2758     thes_code       TEXT;
2759     cset            INT;
2760     heading_text    TEXT;
2761     tmp_text        TEXT;
2762 BEGIN
2763     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
2764     IF thes_code IS NULL THEN
2765         thes_code := '|';
2766     END IF;
2767
2768     SELECT control_set INTO cset FROM authority.thesaurus WHERE code = thes_code;
2769     IF NOT FOUND THEN
2770         cset = 1;
2771     END IF;
2772
2773     heading_text := '';
2774     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
2775         tag_used := acsaf.tag;
2776         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
2777             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
2778             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
2779                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
2780             END IF;
2781         END LOOP;
2782         EXIT WHEN heading_text <> '';
2783     END LOOP;
2784  
2785     IF thes_code = 'z' THEN
2786         thes_code := oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml);
2787     END IF;
2788
2789     IF heading_text <> '' THEN
2790         IF no_thesaurus IS TRUE THEN
2791             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
2792         ELSE
2793             heading_text := tag_used || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
2794         END IF;
2795     ELSE
2796         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
2797     END IF;
2798
2799     RETURN heading_text;
2800 END;
2801 $func$ LANGUAGE PLPGSQL IMMUTABLE;
2802
2803 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2804     SELECT authority.normalize_heading($1, TRUE);
2805 $func$ LANGUAGE SQL IMMUTABLE;
2806
2807 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
2808     SELECT authority.normalize_heading($1, FALSE);
2809 $func$ LANGUAGE SQL IMMUTABLE;
2810
2811 CREATE OR REPLACE VIEW authority.tracing_links AS
2812     SELECT  main.record AS record,
2813             main.id AS main_id,
2814             main.tag AS main_tag,
2815             oils_xpath_string('//*[@tag="'||main.tag||'"]/*[local-name()="subfield"]', are.marc) AS main_value,
2816             substr(link.value,1,1) AS relationship,
2817             substr(link.value,2,1) AS use_restriction,
2818             substr(link.value,3,1) AS deprecation,
2819             substr(link.value,4,1) AS display_restriction,
2820             link.id AS link_id,
2821             link.tag AS link_tag,
2822             oils_xpath_string('//*[@tag="'||link.tag||'"]/*[local-name()="subfield"]', are.marc) AS link_value,
2823             authority.normalize_heading(are.marc) AS normalized_main_value
2824       FROM  authority.full_rec main
2825             JOIN authority.record_entry are ON (main.record = are.id)
2826             JOIN authority.control_set_authority_field main_entry
2827                 ON (main_entry.tag = main.tag
2828                     AND main_entry.main_entry IS NULL
2829                     AND main.subfield = 'a' )
2830             JOIN authority.control_set_authority_field sub_entry
2831                 ON (main_entry.id = sub_entry.main_entry)
2832             JOIN authority.full_rec link
2833                 ON (link.record = main.record
2834                     AND link.tag = sub_entry.tag
2835                     AND link.subfield = 'w' );
2836  
2837 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
2838 DECLARE
2839     cset                INT;
2840     main_entry          authority.control_set_authority_field%ROWTYPE;
2841     bib_field           authority.control_set_bib_field%ROWTYPE;
2842     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
2843     replace_data        XML[] DEFAULT '{}'::XML[];
2844     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
2845     auth_field          XML[];
2846 BEGIN
2847     IF auth_id IS NULL THEN
2848         RETURN NULL;
2849     END IF;
2850
2851     -- Default to the LoC controll set
2852     SELECT COALESCE(control_set,1) INTO cset FROM authority.record_entry WHERE id = auth_id;
2853
2854     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
2855         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
2856         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
2857             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
2858                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
2859                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
2860             END LOOP;
2861             EXIT;
2862         END IF;
2863     END LOOP;
2864  
2865     RETURN XMLELEMENT(
2866         name record,
2867         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
2868         XMLELEMENT( name leader, '00881nam a2200193   4500'),
2869         replace_data,
2870         XMLELEMENT(
2871             name datafield,
2872             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
2873             XMLELEMENT(
2874                 name subfield,
2875                 XMLATTRIBUTES('r' AS code),
2876                 ARRAY_TO_STRING(replace_rules,',')
2877             )
2878         )
2879     )::TEXT;
2880 END;
2881 $f$ STABLE LANGUAGE PLPGSQL;
2882  
2883 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
2884     SELECT authority.generate_overlay_template( marc ) FROM authority.record_entry WHERE id = $1;
2885 $func$ LANGUAGE SQL;
2886  
2887 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
2888
2889     use MARC::Record;
2890     use MARC::File::XML (BinaryEncoding => 'UTF-8');
2891     use MARC::Charset;
2892     use strict;
2893
2894     MARC::Charset->assume_unicode(1);
2895
2896     my $target_xml = shift;
2897     my $source_xml = shift;
2898     my $field_spec = shift;
2899     my $force_add = shift || 0;
2900
2901     my $target_r = MARC::Record->new_from_xml( $target_xml );
2902     my $source_r = MARC::Record->new_from_xml( $source_xml );
2903
2904     return $target_xml unless ($target_r && $source_r);
2905
2906     my @field_list = split(',', $field_spec);
2907
2908     my %fields;
2909     for my $f (@field_list) {
2910         $f =~ s/^\s*//; $f =~ s/\s*$//;
2911         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
2912             my $field = $1;
2913             $field =~ s/\s+//;
2914             my $sf = $2;
2915             $sf =~ s/\s+//;
2916             my $match = $3;
2917             $match =~ s/^\s*//; $match =~ s/\s*$//;
2918             $fields{$field} = { sf => [ split('', $sf) ] };
2919             if ($match) {
2920                 my ($msf,$mre) = split('~', $match);
2921                 if (length($msf) > 0 and length($mre) > 0) {
2922                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
2923                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
2924                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
2925                 }
2926             }
2927         }
2928     }
2929
2930     for my $f ( keys %fields) {
2931         if ( @{$fields{$f}{sf}} ) {
2932             for my $from_field ($source_r->field( $f )) {
2933                 my @tos = $target_r->field( $f );
2934                 if (!@tos) {
2935                     next if (exists($fields{$f}{match}) and !$force_add);
2936                     my @new_fields = map { $_->clone } $source_r->field( $f );
2937                     $target_r->insert_fields_ordered( @new_fields );
2938                 } else {
2939                     for my $to_field (@tos) {
2940                         if (exists($fields{$f}{match})) {
2941                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
2942                         }
2943                         my @new_sf = map { ($_ => $from_field->subfield($_)) } grep { defined($from_field->subfield($_)) } @{$fields{$f}{sf}};
2944                         $to_field->add_subfields( @new_sf );
2945                     }
2946                 }
2947             }
2948         } else {
2949             my @new_fields = map { $_->clone } $source_r->field( $f );
2950             $target_r->insert_fields_ordered( @new_fields );
2951         }
2952     }
2953
2954     $target_xml = $target_r->as_xml_record;
2955     $target_xml =~ s/^<\?.+?\?>$//mo;
2956     $target_xml =~ s/\n//sgo;
2957     $target_xml =~ s/>\s+</></sgo;
2958
2959     return $target_xml;
2960
2961 $_$ LANGUAGE PLPERLU;
2962
2963
2964 CREATE INDEX by_heading ON authority.record_entry (authority.simple_normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE;
2965
2966 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, search_field, facet_field) VALUES
2967     (28, 'identifier', 'authority_id', oils_i18n_gettext(28, 'Authority Record ID', 'cmf', 'label'), 'marcxml', '//marc:datafield/marc:subfield[@code="0"]', FALSE, TRUE);
2968  
2969 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('AUT','z',' ');
2970 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MFHD','uvxy',' ');
2971  
2972 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'AUT', 17, 1, ' ');
2973 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Subj', '008', 'AUT', 11, 1, '|');
2974 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('RecStat', 'ldr', 'AUT', 5, 1, 'n');
2975  
2976 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
2977     SELECT  m.id,
2978             i.id,
2979             -1
2980       FROM  config.metabib_field m,
2981             config.index_normalizer i
2982       WHERE i.func = 'remove_paren_substring'
2983             AND m.id IN (28);
2984
2985 SELECT SETVAL('authority.control_set_id_seq'::TEXT, 100);
2986 SELECT SETVAL('authority.control_set_authority_field_id_seq'::TEXT, 1000);
2987 SELECT SETVAL('authority.control_set_bib_field_id_seq'::TEXT, 1000);
2988
2989 INSERT INTO authority.control_set (id, name, description) VALUES (
2990     1,
2991     oils_i18n_gettext('1','LoC','acs','name'),
2992     oils_i18n_gettext('1','Library of Congress standard authority record control semantics','acs','description')
2993 );
2994
2995 INSERT INTO authority.control_set_authority_field (id, control_set, main_entry, tag, sf_list, name) VALUES
2996
2997 -- Main entries
2998     (1, 1, NULL, '100', 'abcdefklmnopqrstvxyz', oils_i18n_gettext('1','Heading -- Personal Name','acsaf','name')),
2999     (2, 1, NULL, '110', 'abcdefgklmnoprstvxyz', oils_i18n_gettext('2','Heading -- Corporate Name','acsaf','name')),
3000     (3, 1, NULL, '111', 'acdefgklnpqstvxyz', oils_i18n_gettext('3','Heading -- Meeting Name','acsaf','name')),
3001     (4, 1, NULL, '130', 'adfgklmnoprstvxyz', oils_i18n_gettext('4','Heading -- Uniform Title','acsaf','name')),
3002     (5, 1, NULL, '150', 'abvxyz', oils_i18n_gettext('5','Heading -- Topical Term','acsaf','name')),
3003     (6, 1, NULL, '151', 'avxyz', oils_i18n_gettext('6','Heading -- Geographic Name','acsaf','name')),
3004     (7, 1, NULL, '155', 'avxyz', oils_i18n_gettext('7','Heading -- Genre/Form Term','acsaf','name')),
3005     (8, 1, NULL, '180', 'vxyz', oils_i18n_gettext('8','Heading -- General Subdivision','acsaf','name')),
3006     (9, 1, NULL, '181', 'vxyz', oils_i18n_gettext('9','Heading -- Geographic Subdivision','acsaf','name')),
3007     (10, 1, NULL, '182', 'vxyz', oils_i18n_gettext('10','Heading -- Chronological Subdivision','acsaf','name')),
3008     (11, 1, NULL, '185', 'vxyz', oils_i18n_gettext('11','Heading -- Form Subdivision','acsaf','name')),
3009     (12, 1, NULL, '148', 'avxyz', oils_i18n_gettext('12','Heading -- Chronological Term','acsaf','name')),
3010
3011 -- See Also From tracings
3012     (21, 1, 1, '500', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('21','See Also From Tracing -- Personal Name','acsaf','name')),
3013     (22, 1, 2, '510', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('22','See Also From Tracing -- Corporate Name','acsaf','name')),
3014     (23, 1, 3, '511', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('23','See Also From Tracing -- Meeting Name','acsaf','name')),
3015     (24, 1, 4, '530', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('24','See Also From Tracing -- Uniform Title','acsaf','name')),
3016     (25, 1, 5, '550', 'abivwxyz4', oils_i18n_gettext('25','See Also From Tracing -- Topical Term','acsaf','name')),
3017     (26, 1, 6, '551', 'aivwxyz4', oils_i18n_gettext('26','See Also From Tracing -- Geographic Name','acsaf','name')),
3018     (27, 1, 7, '555', 'aivwxyz4', oils_i18n_gettext('27','See Also From Tracing -- Genre/Form Term','acsaf','name')),
3019     (28, 1, 8, '580', 'ivwxyz4', oils_i18n_gettext('28','See Also From Tracing -- General Subdivision','acsaf','name')),
3020     (29, 1, 9, '581', 'ivwxyz4', oils_i18n_gettext('29','See Also From Tracing -- Geographic Subdivision','acsaf','name')),
3021     (30, 1, 10, '582', 'ivwxyz4', oils_i18n_gettext('30','See Also From Tracing -- Chronological Subdivision','acsaf','name')),
3022     (31, 1, 11, '585', 'ivwxyz4', oils_i18n_gettext('31','See Also From Tracing -- Form Subdivision','acsaf','name')),
3023     (32, 1, 12, '548', 'aivwxyz4', oils_i18n_gettext('32','See Also From Tracing -- Chronological Term','acsaf','name')),
3024
3025 -- Linking entries
3026     (41, 1, 1, '700', 'abcdefghjklmnopqrstvwxyz25', oils_i18n_gettext('41','Established Heading Linking Entry -- Personal Name','acsaf','name')),
3027     (42, 1, 2, '710', 'abcdefghklmnoprstvwxyz25', oils_i18n_gettext('42','Established Heading Linking Entry -- Corporate Name','acsaf','name')),
3028     (43, 1, 3, '711', 'acdefghklnpqstvwxyz25', oils_i18n_gettext('43','Established Heading Linking Entry -- Meeting Name','acsaf','name')),
3029     (44, 1, 4, '730', 'adfghklmnoprstvwxyz25', oils_i18n_gettext('44','Established Heading Linking Entry -- Uniform Title','acsaf','name')),
3030     (45, 1, 5, '750', 'abvwxyz25', oils_i18n_gettext('45','Established Heading Linking Entry -- Topical Term','acsaf','name')),
3031     (46, 1, 6, '751', 'avwxyz25', oils_i18n_gettext('46','Established Heading Linking Entry -- Geographic Name','acsaf','name')),
3032     (47, 1, 7, '755', 'avwxyz25', oils_i18n_gettext('47','Established Heading Linking Entry -- Genre/Form Term','acsaf','name')),
3033     (48, 1, 8, '780', 'vwxyz25', oils_i18n_gettext('48','Subdivision Linking Entry -- General Subdivision','acsaf','name')),
3034     (49, 1, 9, '781', 'vwxyz25', oils_i18n_gettext('49','Subdivision Linking Entry -- Geographic Subdivision','acsaf','name')),
3035     (50, 1, 10, '782', 'vwxyz25', oils_i18n_gettext('50','Subdivision Linking Entry -- Chronological Subdivision','acsaf','name')),
3036     (51, 1, 11, '785', 'vwxyz25', oils_i18n_gettext('51','Subdivision Linking Entry -- Form Subdivision','acsaf','name')),
3037     (52, 1, 12, '748', 'avwxyz25', oils_i18n_gettext('52','Established Heading Linking Entry -- Chronological Term','acsaf','name')),
3038
3039 -- See From tracings
3040     (61, 1, 1, '400', 'abcdefiklmnopqrstvwxyz4', oils_i18n_gettext('61','See Also Tracing -- Personal Name','acsaf','name')),
3041     (62, 1, 2, '410', 'abcdefgiklmnoprstvwxyz4', oils_i18n_gettext('62','See Also Tracing -- Corporate Name','acsaf','name')),
3042     (63, 1, 3, '411', 'acdefgiklnpqstvwxyz4', oils_i18n_gettext('63','See Also Tracing -- Meeting Name','acsaf','name')),
3043     (64, 1, 4, '430', 'adfgiklmnoprstvwxyz4', oils_i18n_gettext('64','See Also Tracing -- Uniform Title','acsaf','name')),
3044     (65, 1, 5, '450', 'abivwxyz4', oils_i18n_gettext('65','See Also Tracing -- Topical Term','acsaf','name')),
3045     (66, 1, 6, '451', 'aivwxyz4', oils_i18n_gettext('66','See Also Tracing -- Geographic Name','acsaf','name')),
3046     (67, 1, 7, '455', 'aivwxyz4', oils_i18n_gettext('67','See Also Tracing -- Genre/Form Term','acsaf','name')),
3047     (68, 1, 8, '480', 'ivwxyz4', oils_i18n_gettext('68','See Also Tracing -- General Subdivision','acsaf','name')),
3048     (69, 1, 9, '481', 'ivwxyz4', oils_i18n_gettext('69','See Also Tracing -- Geographic Subdivision','acsaf','name')),
3049     (70, 1, 10, '482', 'ivwxyz4', oils_i18n_gettext('70','See Also Tracing -- Chronological Subdivision','acsaf','name')),
3050     (71, 1, 11, '485', 'ivwxyz4', oils_i18n_gettext('71','See Also Tracing -- Form Subdivision','acsaf','name')),
3051     (72, 1, 12, '448', 'aivwxyz4', oils_i18n_gettext('72','See Also Tracing -- Chronological Term','acsaf','name'));
3052
3053 INSERT INTO authority.browse_axis (code,name,description,sorter) VALUES
3054     ('title','Title','Title axis','titlesort'),
3055     ('author','Author','Author axis','titlesort'),
3056     ('subject','Subject','Subject axis','titlesort'),
3057     ('topic','Topic','Topic Subject axis','titlesort');
3058
3059 INSERT INTO authority.browse_axis_authority_field_map (axis,field) VALUES
3060     ('author',  1 ),
3061     ('author',  2 ),
3062     ('author',  3 ),
3063     ('title',   4 ),
3064     ('topic',   5 ),
3065     ('subject', 5 ),
3066     ('subject', 6 ),
3067     ('subject', 7 ),
3068     ('subject', 12);
3069
3070 INSERT INTO authority.control_set_bib_field (tag, authority_field) 
3071     SELECT '100', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3072         UNION
3073     SELECT '600', id FROM authority.control_set_authority_field WHERE tag IN ('100','180','181','182','185')
3074         UNION
3075     SELECT '700', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3076         UNION
3077     SELECT '800', id FROM authority.control_set_authority_field WHERE tag IN ('100')
3078         UNION
3079
3080     SELECT '110', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3081         UNION
3082     SELECT '610', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3083         UNION
3084     SELECT '710', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3085         UNION
3086     SELECT '810', id FROM authority.control_set_authority_field WHERE tag IN ('110')
3087         UNION
3088
3089     SELECT '111', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3090         UNION
3091     SELECT '611', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3092         UNION
3093     SELECT '711', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3094         UNION
3095     SELECT '811', id FROM authority.control_set_authority_field WHERE tag IN ('111')
3096         UNION
3097
3098     SELECT '130', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3099         UNION
3100     SELECT '240', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3101         UNION
3102     SELECT '630', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3103         UNION
3104     SELECT '730', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3105         UNION
3106     SELECT '830', id FROM authority.control_set_authority_field WHERE tag IN ('130')
3107         UNION
3108
3109     SELECT '648', id FROM authority.control_set_authority_field WHERE tag IN ('148')
3110         UNION
3111
3112     SELECT '650', id FROM authority.control_set_authority_field WHERE tag IN ('150','180','181','182','185')
3113         UNION
3114     SELECT '651', id FROM authority.control_set_authority_field WHERE tag IN ('151','180','181','182','185')
3115         UNION
3116     SELECT '655', id FROM authority.control_set_authority_field WHERE tag IN ('155','180','181','182','185')
3117 ;
3118
3119 INSERT INTO authority.thesaurus (code, name, control_set) VALUES
3120     ('a', oils_i18n_gettext('a','Library of Congress Subject Headings','at','name'), 1),
3121     ('b', oils_i18n_gettext('b',$$LC subject headings for children's literature$$,'at','name'), 1), -- silly vim '
3122     ('c', oils_i18n_gettext('c','Medical Subject Headings','at','name'), 1),
3123     ('d', oils_i18n_gettext('d','National Agricultural Library subject authority file','at','name'), 1),
3124     ('k', oils_i18n_gettext('k','Canadian Subject Headings','at','name'), 1),
3125     ('n', oils_i18n_gettext('n','Not applicable','at','name'), 1),
3126     ('r', oils_i18n_gettext('r','Art and Architecture Thesaurus','at','name'), 1),
3127     ('s', oils_i18n_gettext('s','Sears List of Subject Headings','at','name'), 1),
3128     ('v', oils_i18n_gettext('v','Repertoire de vedettes-matiere','at','name'), 1),
3129     ('z', oils_i18n_gettext('z','Other','at','name'), 1),
3130     ('|', oils_i18n_gettext('|','No attempt to code','at','name'), 1);
3131  
3132 CREATE OR REPLACE FUNCTION authority.map_thesaurus_to_control_set () RETURNS TRIGGER AS $func$
3133 BEGIN
3134     IF NEW.control_set IS NULL THEN
3135         SELECT  control_set INTO NEW.control_set
3136           FROM  authority.thesaurus
3137           WHERE vandelay.marc21_extract_fixed_field(NEW.marc,'Subj') = code;
3138     END IF;
3139
3140     RETURN NEW;
3141 END;
3142 $func$ LANGUAGE PLPGSQL;
3143
3144 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 ();
3145
3146 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
3147 BEGIN
3148     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
3149     INSERT INTO authority.rec_descriptor (record, record_status, encoding_level, thesaurus)
3150         SELECT  auth_id,
3151                 vandelay.marc21_extract_fixed_field(marc,'RecStat'),
3152                 vandelay.marc21_extract_fixed_field(marc,'ELvl'),
3153                 vandelay.marc21_extract_fixed_field(marc,'Subj')
3154           FROM  authority.record_entry
3155           WHERE id = auth_id;
3156      RETURN;
3157  END;
3158  $func$ LANGUAGE PLPGSQL;
3159
3160 --Removed dupe authority.indexing_ingest_or_delete
3161
3162 -- Evergreen DB patch 0577.schema.vandelay-item-import-copy-loc-ancestors.sql
3163 --
3164 -- Ingest items copy location inheritance
3165 --
3166
3167 -- check whether patch can be applied
3168 SELECT evergreen.upgrade_deps_block_check('0577', :eg_version); -- berick
3169
3170 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3171 DECLARE
3172
3173     owning_lib      TEXT;
3174     circ_lib        TEXT;
3175     call_number     TEXT;
3176     copy_number     TEXT;
3177     status          TEXT;
3178     location        TEXT;
3179     circulate       TEXT;
3180     deposit         TEXT;
3181     deposit_amount  TEXT;
3182     ref             TEXT;
3183     holdable        TEXT;
3184     price           TEXT;
3185     barcode         TEXT;
3186     circ_modifier   TEXT;
3187     circ_as_type    TEXT;
3188     alert_message   TEXT;
3189     opac_visible    TEXT;
3190     pub_note        TEXT;
3191     priv_note       TEXT;
3192
3193     attr_def        RECORD;
3194     tmp_attr_set    RECORD;
3195     attr_set        vandelay.import_item%ROWTYPE;
3196
3197     xpath           TEXT;
3198
3199 BEGIN
3200
3201     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3202
3203     IF FOUND THEN
3204
3205         attr_set.definition := attr_def.id;
3206
3207         -- Build the combined XPath
3208
3209         owning_lib :=
3210             CASE
3211                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3212                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3213                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3214             END;
3215
3216         circ_lib :=
3217             CASE
3218                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3219                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3220                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3221             END;
3222
3223         call_number :=
3224             CASE
3225                 WHEN attr_def.call_number IS NULL THEN 'null()'
3226                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3227                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3228             END;
3229
3230         copy_number :=
3231             CASE
3232                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3233                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3234                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3235             END;
3236
3237         status :=
3238             CASE
3239                 WHEN attr_def.status IS NULL THEN 'null()'
3240                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3241                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3242             END;
3243
3244         location :=
3245             CASE
3246                 WHEN attr_def.location IS NULL THEN 'null()'
3247                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3248                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3249             END;
3250
3251         circulate :=
3252             CASE
3253                 WHEN attr_def.circulate IS NULL THEN 'null()'
3254                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3255                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3256             END;
3257
3258         deposit :=
3259             CASE
3260                 WHEN attr_def.deposit IS NULL THEN 'null()'
3261                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3262                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3263             END;
3264
3265         deposit_amount :=
3266             CASE
3267                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3268                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3269                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3270             END;
3271
3272         ref :=
3273             CASE
3274                 WHEN attr_def.ref IS NULL THEN 'null()'
3275                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3276                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3277             END;
3278
3279         holdable :=
3280             CASE
3281                 WHEN attr_def.holdable IS NULL THEN 'null()'
3282                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3283                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3284             END;
3285
3286         price :=
3287             CASE
3288                 WHEN attr_def.price IS NULL THEN 'null()'
3289                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3290                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3291             END;
3292
3293         barcode :=
3294             CASE
3295                 WHEN attr_def.barcode IS NULL THEN 'null()'
3296                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3297                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3298             END;
3299
3300         circ_modifier :=
3301             CASE
3302                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3303                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3304                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3305             END;
3306
3307         circ_as_type :=
3308             CASE
3309                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3310                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3311                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3312             END;
3313
3314         alert_message :=
3315             CASE
3316                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3317                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3318                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3319             END;
3320
3321         opac_visible :=
3322             CASE
3323                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3324                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3325                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3326             END;
3327
3328         pub_note :=
3329             CASE
3330                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3331                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3332                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3333             END;
3334         priv_note :=
3335             CASE
3336                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3337                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3338                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3339             END;
3340
3341
3342         xpath :=
3343             owning_lib      || '|' ||
3344             circ_lib        || '|' ||
3345             call_number     || '|' ||
3346             copy_number     || '|' ||
3347             status          || '|' ||
3348             location        || '|' ||
3349             circulate       || '|' ||
3350             deposit         || '|' ||
3351             deposit_amount  || '|' ||
3352             ref             || '|' ||
3353             holdable        || '|' ||
3354             price           || '|' ||
3355             barcode         || '|' ||
3356             circ_modifier   || '|' ||
3357             circ_as_type    || '|' ||
3358             alert_message   || '|' ||
3359             pub_note        || '|' ||
3360             priv_note       || '|' ||
3361             opac_visible;
3362
3363         -- RAISE NOTICE 'XPath: %', xpath;
3364
3365         FOR tmp_attr_set IN
3366                 SELECT  *
3367                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3368                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3369                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3370                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3371         LOOP
3372
3373             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3374             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3375
3376             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
3377             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
3378
3379             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3380             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3381             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3382
3383
3384             -- search up the org unit tree for a matching copy location
3385
3386             WITH RECURSIVE anscestor_depth AS (
3387                 SELECT  ou.id,
3388                     out.depth AS depth,
3389                     ou.parent_ou
3390                 FROM  actor.org_unit ou
3391                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3392                 WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3393                     UNION ALL
3394                 SELECT  ou.id,
3395                     out.depth,
3396                     ou.parent_ou
3397                 FROM  actor.org_unit ou
3398                     JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3399                     JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3400             ) SELECT  cpl.id INTO attr_set.location
3401                 FROM  anscestor_depth a
3402                     JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3403                 WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3404                 ORDER BY a.depth DESC
3405                 LIMIT 1; 
3406
3407             attr_set.circulate      :=
3408                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3409                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3410
3411             attr_set.deposit        :=
3412                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3413                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3414
3415             attr_set.holdable       :=
3416                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3417                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3418
3419             attr_set.opac_visible   :=
3420                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3421                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
3422
3423             attr_set.ref            :=
3424                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
3425                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
3426
3427             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
3428             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
3429             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
3430
3431             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
3432             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
3433             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
3434             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
3435             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3436             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
3437             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
3438             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
3439
3440             RETURN NEXT attr_set;
3441
3442         END LOOP;
3443
3444     END IF;
3445
3446     RETURN;
3447
3448 END;
3449 $$ LANGUAGE PLPGSQL;
3450
3451
3452 -- Evergreen DB patch XXXX.data.org-setting-ui.circ.billing.uncheck_bills_and_unfocus_payment_box.sql
3453 --
3454 -- New org setting ui.circ.billing.uncheck_bills_and_unfocus_payment_box
3455 --
3456
3457 -- check whether patch can be applied
3458 SELECT evergreen.upgrade_deps_block_check('0584', :eg_version);
3459
3460 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3461     VALUES ( 
3462         'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3463         oils_i18n_gettext(
3464             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3465             'GUI: Uncheck bills by default in the patron billing interface',
3466             'coust',
3467             'label'
3468         ),
3469         oils_i18n_gettext(
3470             'ui.circ.billing.uncheck_bills_and_unfocus_payment_box',
3471             'Uncheck bills by default in the patron billing interface,'
3472             || ' and focus on the Uncheck All button instead of the'
3473             || ' Payment Received field.',
3474             'coust',
3475             'description'
3476         ),
3477         'bool'
3478     );
3479
3480
3481 -- check whether patch can be applied
3482 SELECT evergreen.upgrade_deps_block_check('0585', :eg_version);
3483
3484 INSERT into config.org_unit_setting_type
3485 ( name, label, description, datatype ) VALUES
3486 ( 'circ.checkout_fills_related_hold_exact_match_only',
3487     'Checkout Fills Related Hold On Valid Copy Only',
3488     '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.',
3489     'bool');
3490
3491
3492 -- check whether patch can be applied
3493 SELECT evergreen.upgrade_deps_block_check('0586', :eg_version);
3494
3495 INSERT INTO permission.perm_list (id, code, description) VALUES (
3496     511,
3497     'PERSISTENT_LOGIN',
3498     oils_i18n_gettext(
3499         511,
3500         'Allows a user to authenticate and get a long-lived session (length configured in opensrf.xml)',
3501         'ppl',
3502         'description'
3503     )
3504 );
3505
3506 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
3507     SELECT
3508         pgt.id, perm.id, aout.depth, FALSE
3509     FROM
3510         permission.grp_tree pgt,
3511         permission.perm_list perm,
3512         actor.org_unit_type aout
3513     WHERE
3514         pgt.name = 'Users' AND
3515         aout.name = 'Consortium' AND
3516         perm.code = 'PERSISTENT_LOGIN';
3517
3518 \qecho 
3519 \qecho If this transaction succeeded, your users (staff and patrons) now have
3520 \qecho the PERSISTENT_LOGIN permission by default.
3521 \qecho 
3522
3523
3524 -- Evergreen DB patch XXXX.data.org-setting-circ.offline.skip_foo_if_newer_status_changed_time.sql
3525 --
3526 -- New org setting circ.offline.skip_checkout_if_newer_status_changed_time
3527 -- New org setting circ.offline.skip_renew_if_newer_status_changed_time
3528 -- New org setting circ.offline.skip_checkin_if_newer_status_changed_time
3529 --
3530
3531 -- check whether patch can be applied
3532 SELECT evergreen.upgrade_deps_block_check('0593', :eg_version);
3533
3534 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
3535     VALUES ( 
3536         'circ.offline.skip_checkout_if_newer_status_changed_time',
3537         oils_i18n_gettext(
3538             'circ.offline.skip_checkout_if_newer_status_changed_time',
3539             'Offline: Skip offline checkout if newer item Status Changed Time.',
3540             'coust',
3541             'label'
3542         ),
3543         oils_i18n_gettext(
3544             'circ.offline.skip_checkout_if_newer_status_changed_time',
3545             'Skip offline checkout transaction (raise exception when'
3546             || ' processing) if item Status Changed Time is newer than the'
3547             || ' recorded transaction time.  WARNING: The Reshelving to'
3548             || ' Available status rollover will trigger this.',
3549             'coust',
3550             'description'
3551         ),
3552         'bool'
3553     ),( 
3554         'circ.offline.skip_renew_if_newer_status_changed_time',
3555         oils_i18n_gettext(
3556             'circ.offline.skip_renew_if_newer_status_changed_time',
3557             'Offline: Skip offline renewal if newer item Status Changed Time.',
3558             'coust',
3559             'label'
3560         ),
3561         oils_i18n_gettext(
3562             'circ.offline.skip_renew_if_newer_status_changed_time',
3563             'Skip offline renewal transaction (raise exception when'
3564             || ' processing) if item Status Changed Time is newer than the'
3565             || ' recorded transaction time.  WARNING: The Reshelving to'
3566             || ' Available status rollover will trigger this.',
3567             'coust',
3568             'description'
3569         ),
3570         'bool'
3571     ),( 
3572         'circ.offline.skip_checkin_if_newer_status_changed_time',
3573         oils_i18n_gettext(
3574             'circ.offline.skip_checkin_if_newer_status_changed_time',
3575             'Offline: Skip offline checkin if newer item Status Changed Time.',
3576             'coust',
3577             'label'
3578         ),
3579         oils_i18n_gettext(
3580             'circ.offline.skip_checkin_if_newer_status_changed_time',
3581             'Skip offline checkin transaction (raise exception when'
3582             || ' processing) if item Status Changed Time is newer than the'
3583             || ' recorded transaction time.  WARNING: The Reshelving to'
3584             || ' Available status rollover will trigger this.',
3585             'coust',
3586             'description'
3587         ),
3588         'bool'
3589     );
3590
3591 -- Evergreen DB patch YYYY.schema.acp_status_date_changed.sql
3592 --
3593 -- Change trigger which updates copy status_changed_time to ignore the
3594 -- Reshelving->Available status rollover
3595
3596 -- FIXME: 0039.schema.acp_status_date_changed.sql defines this the first time
3597 -- around, but along with the column itself, etc.  And it gets modified with
3598 -- 0562.schema.copy_active_date.sql.  Not sure how to use the supercedes /
3599 -- deprecate stuff for upgrade scripts, if it's even applicable when a given
3600 -- upgrade script is doing so much.
3601
3602 -- check whether patch can be applied
3603 SELECT evergreen.upgrade_deps_block_check('0594', :eg_version);
3604
3605 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
3606 RETURNS TRIGGER AS $$
3607 BEGIN
3608         IF NEW.status <> OLD.status AND NOT (NEW.status = 0 AND OLD.status = 7) THEN
3609         NEW.status_changed_time := now();
3610         IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
3611             NEW.active_date := now();
3612         END IF;
3613     END IF;
3614     RETURN NEW;
3615 END;
3616 $$ LANGUAGE plpgsql;
3617
3618 -- Evergreen DB patch 0595.data.org-setting-ui.patron_search.result_cap.sql
3619 --
3620 -- New org setting ui.patron_search.result_cap
3621 --
3622
3623 -- check whether patch can be applied
3624 SELECT evergreen.upgrade_deps_block_check('0595', :eg_version);
3625
3626 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
3627     VALUES (
3628         'ui.patron_search.result_cap',
3629         oils_i18n_gettext(
3630             'ui.patron_search.result_cap',
3631             'GUI: Cap results in Patron Search at this number.',
3632             'coust',
3633             'label'
3634         ),
3635         oils_i18n_gettext(
3636             'ui.patron_search.result_cap',
3637             'So for example, if you search for John Doe, normally you would get'
3638             || ' at most 50 results.  This setting allows you to raise or lower'
3639             || ' that limit.',
3640             'coust',
3641             'description'
3642         ),
3643         'integer'
3644     );
3645
3646 -- Evergreen DB patch 0596.schema.vandelay-item-import-error-detail.sql
3647
3648 -- check whether patch can be applied
3649 SELECT evergreen.upgrade_deps_block_check('0596', :eg_version);
3650
3651 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3652     'import.item.invalid.status', oils_i18n_gettext('import.item.invalid.status', 'Invalid value for "status"', 'vie', 'description') );
3653 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3654     'import.item.invalid.price', oils_i18n_gettext('import.item.invalid.price', 'Invalid value for "price"', 'vie', 'description') );
3655 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3656     'import.item.invalid.deposit_amount', oils_i18n_gettext('import.item.invalid.deposit_amount', 'Invalid value for "deposit_amount"', 'vie', 'description') );
3657 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3658     'import.item.invalid.owning_lib', oils_i18n_gettext('import.item.invalid.owning_lib', 'Invalid value for "owning_lib"', 'vie', 'description') );
3659 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3660     'import.item.invalid.circ_lib', oils_i18n_gettext('import.item.invalid.circ_lib', 'Invalid value for "circ_lib"', 'vie', 'description') );
3661 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3662     'import.item.invalid.copy_number', oils_i18n_gettext('import.item.invalid.copy_number', 'Invalid value for "copy_number"', 'vie', 'description') );
3663 INSERT INTO vandelay.import_error ( code, description ) VALUES ( 
3664     'import.item.invalid.circ_as_type', oils_i18n_gettext('import.item.invalid.circ_as_type', 'Invalid value for "circ_as_type"', 'vie', 'description') );
3665
3666 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
3667 DECLARE
3668
3669     owning_lib      TEXT;
3670     circ_lib        TEXT;
3671     call_number     TEXT;
3672     copy_number     TEXT;
3673     status          TEXT;
3674     location        TEXT;
3675     circulate       TEXT;
3676     deposit         TEXT;
3677     deposit_amount  TEXT;
3678     ref             TEXT;
3679     holdable        TEXT;
3680     price           TEXT;
3681     barcode         TEXT;
3682     circ_modifier   TEXT;
3683     circ_as_type    TEXT;
3684     alert_message   TEXT;
3685     opac_visible    TEXT;
3686     pub_note        TEXT;
3687     priv_note       TEXT;
3688
3689     attr_def        RECORD;
3690     tmp_attr_set    RECORD;
3691     attr_set        vandelay.import_item%ROWTYPE;
3692
3693     xpath           TEXT;
3694     tmp_str         TEXT;
3695
3696 BEGIN
3697
3698     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
3699
3700     IF FOUND THEN
3701
3702         attr_set.definition := attr_def.id;
3703
3704         -- Build the combined XPath
3705
3706         owning_lib :=
3707             CASE
3708                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
3709                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
3710                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
3711             END;
3712
3713         circ_lib :=
3714             CASE
3715                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
3716                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
3717                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
3718             END;
3719
3720         call_number :=
3721             CASE
3722                 WHEN attr_def.call_number IS NULL THEN 'null()'
3723                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
3724                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
3725             END;
3726
3727         copy_number :=
3728             CASE
3729                 WHEN attr_def.copy_number IS NULL THEN 'null()'
3730                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
3731                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
3732             END;
3733
3734         status :=
3735             CASE
3736                 WHEN attr_def.status IS NULL THEN 'null()'
3737                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
3738                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
3739             END;
3740
3741         location :=
3742             CASE
3743                 WHEN attr_def.location IS NULL THEN 'null()'
3744                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
3745                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
3746             END;
3747
3748         circulate :=
3749             CASE
3750                 WHEN attr_def.circulate IS NULL THEN 'null()'
3751                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
3752                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
3753             END;
3754
3755         deposit :=
3756             CASE
3757                 WHEN attr_def.deposit IS NULL THEN 'null()'
3758                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
3759                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
3760             END;
3761
3762         deposit_amount :=
3763             CASE
3764                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
3765                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
3766                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
3767             END;
3768
3769         ref :=
3770             CASE
3771                 WHEN attr_def.ref IS NULL THEN 'null()'
3772                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
3773                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
3774             END;
3775
3776         holdable :=
3777             CASE
3778                 WHEN attr_def.holdable IS NULL THEN 'null()'
3779                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
3780                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
3781             END;
3782
3783         price :=
3784             CASE
3785                 WHEN attr_def.price IS NULL THEN 'null()'
3786                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
3787                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
3788             END;
3789
3790         barcode :=
3791             CASE
3792                 WHEN attr_def.barcode IS NULL THEN 'null()'
3793                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
3794                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
3795             END;
3796
3797         circ_modifier :=
3798             CASE
3799                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
3800                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
3801                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
3802             END;
3803
3804         circ_as_type :=
3805             CASE
3806                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
3807                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
3808                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
3809             END;
3810
3811         alert_message :=
3812             CASE
3813                 WHEN attr_def.alert_message IS NULL THEN 'null()'
3814                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
3815                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
3816             END;
3817
3818         opac_visible :=
3819             CASE
3820                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
3821                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
3822                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
3823             END;
3824
3825         pub_note :=
3826             CASE
3827                 WHEN attr_def.pub_note IS NULL THEN 'null()'
3828                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
3829                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
3830             END;
3831         priv_note :=
3832             CASE
3833                 WHEN attr_def.priv_note IS NULL THEN 'null()'
3834                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
3835                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
3836             END;
3837
3838
3839         xpath :=
3840             owning_lib      || '|' ||
3841             circ_lib        || '|' ||
3842             call_number     || '|' ||
3843             copy_number     || '|' ||
3844             status          || '|' ||
3845             location        || '|' ||
3846             circulate       || '|' ||
3847             deposit         || '|' ||
3848             deposit_amount  || '|' ||
3849             ref             || '|' ||
3850             holdable        || '|' ||
3851             price           || '|' ||
3852             barcode         || '|' ||
3853             circ_modifier   || '|' ||
3854             circ_as_type    || '|' ||
3855             alert_message   || '|' ||
3856             pub_note        || '|' ||
3857             priv_note       || '|' ||
3858             opac_visible;
3859
3860         FOR tmp_attr_set IN
3861                 SELECT  *
3862                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
3863                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
3864                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
3865                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
3866         LOOP
3867
3868             attr_set.import_error := NULL;
3869             attr_set.error_detail := NULL;
3870             attr_set.deposit_amount := NULL;
3871             attr_set.copy_number := NULL;
3872             attr_set.price := NULL;
3873
3874             IF tmp_attr_set.pr != '' THEN
3875                 tmp_str = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
3876                 IF tmp_str = '' THEN 
3877                     attr_set.import_error := 'import.item.invalid.price';
3878                     attr_set.error_detail := tmp_attr_set.pr; -- original value
3879                     RETURN NEXT attr_set; CONTINUE; 
3880                 END IF;
3881                 attr_set.price := tmp_str::NUMERIC(8,2); 
3882             END IF;
3883
3884             IF tmp_attr_set.dep_amount != '' THEN
3885                 tmp_str = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
3886                 IF tmp_str = '' THEN 
3887                     attr_set.import_error := 'import.item.invalid.deposit_amount';
3888                     attr_set.error_detail := tmp_attr_set.dep_amount; 
3889                     RETURN NEXT attr_set; CONTINUE; 
3890                 END IF;
3891                 attr_set.deposit_amount := tmp_str::NUMERIC(8,2); 
3892             END IF;
3893
3894             IF tmp_attr_set.cnum != '' THEN
3895                 tmp_str = REGEXP_REPLACE(tmp_attr_set.cnum, E'[^0-9]', '', 'g');
3896                 IF tmp_str = '' THEN 
3897                     attr_set.import_error := 'import.item.invalid.copy_number';
3898                     attr_set.error_detail := tmp_attr_set.cnum; 
3899                     RETURN NEXT attr_set; CONTINUE; 
3900                 END IF;
3901                 attr_set.copy_number := tmp_str::INT; 
3902             END IF;
3903
3904             IF tmp_attr_set.ol != '' THEN
3905                 SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
3906                 IF NOT FOUND THEN
3907                     attr_set.import_error := 'import.item.invalid.owning_lib';
3908                     attr_set.error_detail := tmp_attr_set.ol;
3909                     RETURN NEXT attr_set; CONTINUE; 
3910                 END IF;
3911             END IF;
3912
3913             IF tmp_attr_set.clib != '' THEN
3914                 SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
3915                 IF NOT FOUND THEN
3916                     attr_set.import_error := 'import.item.invalid.circ_lib';
3917                     attr_set.error_detail := tmp_attr_set.clib;
3918                     RETURN NEXT attr_set; CONTINUE; 
3919                 END IF;
3920             END IF;
3921
3922             IF tmp_attr_set.cs != '' THEN
3923                 SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
3924                 IF NOT FOUND THEN
3925                     attr_set.import_error := 'import.item.invalid.status';
3926                     attr_set.error_detail := tmp_attr_set.cs;
3927                     RETURN NEXT attr_set; CONTINUE; 
3928                 END IF;
3929             END IF;
3930
3931             IF tmp_attr_set.circ_mod != '' THEN
3932                 SELECT code INTO attr_set.circ_modifier FROM config.circ_modifier WHERE code = tmp_attr_set.circ_mod;
3933                 IF NOT FOUND THEN
3934                     attr_set.import_error := 'import.item.invalid.circ_modifier';
3935                     attr_set.error_detail := tmp_attr_set.circ_mod;
3936                     RETURN NEXT attr_set; CONTINUE; 
3937                 END IF;
3938             END IF;
3939
3940             IF tmp_attr_set.circ_as != '' THEN
3941                 SELECT code INTO attr_set.circ_as_type FROM config.coded_value_map WHERE ctype = 'item_type' AND code = tmp_attr_set.circ_as;
3942                 IF NOT FOUND THEN
3943                     attr_set.import_error := 'import.item.invalid.circ_as_type';
3944                     attr_set.error_detail := tmp_attr_set.circ_as;
3945                     RETURN NEXT attr_set; CONTINUE; 
3946                 END IF;
3947             END IF;
3948
3949             IF tmp_attr_set.cl != '' THEN
3950
3951                 -- search up the org unit tree for a matching copy location
3952                 WITH RECURSIVE anscestor_depth AS (
3953                     SELECT  ou.id,
3954                         out.depth AS depth,
3955                         ou.parent_ou
3956                     FROM  actor.org_unit ou
3957                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3958                     WHERE ou.id = COALESCE(attr_set.owning_lib, attr_set.circ_lib)
3959                         UNION ALL
3960                     SELECT  ou.id,
3961                         out.depth,
3962                         ou.parent_ou
3963                     FROM  actor.org_unit ou
3964                         JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
3965                         JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
3966                 ) SELECT  cpl.id INTO attr_set.location
3967                     FROM  anscestor_depth a
3968                         JOIN asset.copy_location cpl ON (cpl.owning_lib = a.id)
3969                     WHERE LOWER(cpl.name) = LOWER(tmp_attr_set.cl)
3970                     ORDER BY a.depth DESC
3971                     LIMIT 1; 
3972
3973                 IF NOT FOUND THEN
3974                     attr_set.import_error := 'import.item.invalid.location';
3975                     attr_set.error_detail := tmp_attr_set.cs;
3976                     RETURN NEXT attr_set; CONTINUE; 
3977                 END IF;
3978             END IF;
3979
3980             attr_set.circulate      :=
3981                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
3982                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
3983
3984             attr_set.deposit        :=
3985                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
3986                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
3987
3988             attr_set.holdable       :=
3989                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
3990                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
3991
3992             attr_set.opac_visible   :=
3993                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
3994                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
3995
3996             attr_set.ref            :=
3997                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
3998                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
3999
4000             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
4001             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
4002             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4003             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
4004             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
4005             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
4006
4007             RETURN NEXT attr_set;
4008
4009         END LOOP;
4010
4011     END IF;
4012
4013     RETURN;
4014
4015 END;
4016 $$ LANGUAGE PLPGSQL;
4017
4018 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
4019 DECLARE
4020     attr_def    BIGINT;
4021     item_data   vandelay.import_item%ROWTYPE;
4022 BEGIN
4023
4024     IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
4025         RETURN NEW;
4026     END IF;
4027
4028     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
4029
4030     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
4031         INSERT INTO vandelay.import_item (
4032             record,
4033             definition,
4034             owning_lib,
4035             circ_lib,
4036             call_number,
4037             copy_number,
4038             status,
4039             location,
4040             circulate,
4041             deposit,
4042             deposit_amount,
4043             ref,
4044             holdable,
4045             price,
4046             barcode,
4047             circ_modifier,
4048             circ_as_type,
4049             alert_message,
4050             pub_note,
4051             priv_note,
4052             opac_visible,
4053             import_error,
4054             error_detail
4055         ) VALUES (
4056             NEW.id,
4057             item_data.definition,
4058             item_data.owning_lib,
4059             item_data.circ_lib,
4060             item_data.call_number,
4061             item_data.copy_number,
4062             item_data.status,
4063             item_data.location,
4064             item_data.circulate,
4065             item_data.deposit,
4066             item_data.deposit_amount,
4067             item_data.ref,
4068             item_data.holdable,
4069             item_data.price,
4070             item_data.barcode,
4071             item_data.circ_modifier,
4072             item_data.circ_as_type,
4073             item_data.alert_message,
4074             item_data.pub_note,
4075             item_data.priv_note,
4076             item_data.opac_visible,
4077             item_data.import_error,
4078             item_data.error_detail
4079         );
4080     END LOOP;
4081
4082     RETURN NULL;
4083 END;
4084 $func$ LANGUAGE PLPGSQL;
4085
4086 -- Evergreen DB patch XXXX.schema.vandelay.bib_match_isxn_caseless.sql
4087
4088
4089 -- check whether patch can be applied
4090 SELECT evergreen.upgrade_deps_block_check('0597', :eg_version);
4091
4092 CREATE INDEX metabib_full_rec_isxn_caseless_idx
4093     ON metabib.real_full_rec (LOWER(value))
4094     WHERE tag IN ('020', '022', '024');
4095
4096
4097 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4098     record_xml TEXT
4099 ) RETURNS HSTORE AS $$
4100 BEGIN
4101     RETURN (SELECT
4102         HSTORE(
4103             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4104             ARRAY_ACCUM(value)
4105         )
4106         FROM (
4107             SELECT
4108                 tag, subfield,
4109                 CASE WHEN tag IN ('020', '022', '024') THEN  -- caseless
4110                     ARRAY_ACCUM(LOWER(value))::TEXT
4111                 ELSE
4112                     ARRAY_ACCUM(value)::TEXT
4113                 END AS value
4114                 FROM vandelay.flatten_marc(record_xml)
4115                 GROUP BY tag, subfield ORDER BY tag, subfield
4116         ) subquery
4117     );
4118 END;
4119 $$ LANGUAGE PLPGSQL;
4120
4121 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4122     node vandelay.match_set_point
4123 ) RETURNS VOID AS $$
4124 DECLARE
4125     jrow        TEXT;
4126     my_alias    TEXT;
4127     op          TEXT;
4128     tagkey      TEXT;
4129     caseless    BOOL;
4130 BEGIN
4131     -- remember $1 is tags_rstore, and $2 is svf_rstore
4132
4133     IF node.negate THEN
4134         op := '<>';
4135     ELSE
4136         op := '=';
4137     END IF;
4138
4139     caseless := FALSE;
4140
4141     IF node.tag IS NOT NULL THEN
4142         caseless := (node.tag IN ('020', '022', '024'));
4143         tagkey := node.tag;
4144         IF node.subfield IS NOT NULL THEN
4145             tagkey := tagkey || node.subfield;
4146         END IF;
4147     END IF;
4148
4149     my_alias := 'n' || node.id::TEXT;
4150
4151     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4152         ' AS quality FROM metabib.';
4153     IF node.tag IS NOT NULL THEN
4154         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4155             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4156             node.tag || '''';
4157         IF node.subfield IS NOT NULL THEN
4158             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4159                 node.subfield || '''';
4160         END IF;
4161         jrow := jrow || ' AND (';
4162
4163         IF caseless THEN
4164             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4165         ELSE
4166             jrow := jrow || my_alias || '.value ' || op;
4167         END IF;
4168
4169         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4170     ELSE    -- svf
4171         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4172             my_alias || '.id = bre.id AND (' ||
4173             my_alias || '.attrs->''' || node.svf ||
4174             ''' ' || op || ' $2->''' || node.svf || '''))';
4175     END IF;
4176     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4177 END;
4178 $$ LANGUAGE PLPGSQL;
4179
4180 -- Evergreen DB patch 0598.schema.vandelay_one_match_per.sql
4181 --
4182
4183
4184 -- check whether patch can be applied
4185 SELECT evergreen.upgrade_deps_block_check('0598', :eg_version);
4186
4187 CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
4188     match_set_id INTEGER, record_xml TEXT
4189 ) RETURNS SETOF vandelay.match_set_test_result AS $$
4190 DECLARE
4191     tags_rstore HSTORE;
4192     svf_rstore  HSTORE;
4193     coal        TEXT;
4194     joins       TEXT;
4195     query_      TEXT;
4196     wq          TEXT;
4197     qvalue      INTEGER;
4198     rec         RECORD;
4199 BEGIN
4200     tags_rstore := vandelay.flatten_marc_hstore(record_xml);
4201     svf_rstore := vandelay.extract_rec_attrs(record_xml);
4202
4203     CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
4204     CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
4205
4206     -- generate the where clause and return that directly (into wq), and as
4207     -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
4208     wq := vandelay.get_expr_from_match_set(match_set_id);
4209
4210     query_ := 'SELECT DISTINCT(bre.id) AS record, ';
4211
4212     -- qrows table is for the quality bits we add to the SELECT clause
4213     SELECT ARRAY_TO_STRING(
4214         ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
4215     ) INTO coal FROM _vandelay_tmp_qrows;
4216
4217     -- our query string so far is the SELECT clause and the inital FROM.
4218     -- no JOINs yet nor the WHERE clause
4219     query_ := query_ || coal || ' AS quality ' || E'\n' ||
4220         'FROM biblio.record_entry bre ';
4221
4222     -- jrows table is for the joins we must make (and the real text conditions)
4223     SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
4224         FROM _vandelay_tmp_jrows;
4225
4226     -- add those joins and the where clause to our query.
4227     query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
4228
4229     -- this will return rows of record,quality
4230     FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
4231         RETURN NEXT rec;
4232     END LOOP;
4233
4234     DROP TABLE _vandelay_tmp_qrows;
4235     DROP TABLE _vandelay_tmp_jrows;
4236     RETURN;
4237 END;
4238
4239 $$ LANGUAGE PLPGSQL;
4240
4241 -- Evergreen DB patch 0606.schema.czs_use_perm_column.sql
4242 --
4243 -- This adds a column to config.z3950_source called use_perm.
4244 -- The idea is that if a permission is set for a given source,
4245 -- then staff will need the referenced permission to use that
4246 -- source.
4247 --
4248
4249 -- check whether patch can be applied
4250 SELECT evergreen.upgrade_deps_block_check('0606', :eg_version);
4251
4252 ALTER TABLE config.z3950_source 
4253     ADD COLUMN use_perm INT REFERENCES permission.perm_list (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
4254
4255 COMMENT ON COLUMN config.z3950_source.use_perm IS $$
4256 If set, this permission is required for the source to be listed in the staff
4257 client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
4258 $$;
4259
4260 -- Evergreen DB patch 0608.data.vandelay-export-error-match-info.sql
4261 --
4262 --
4263
4264
4265 -- check whether patch can be applied
4266 SELECT evergreen.upgrade_deps_block_check('0608', :eg_version);
4267
4268 -- Add vqbr.import_error, vqbr.error_detail, and vqbr.matches.size to queue print output
4269
4270 UPDATE action_trigger.event_definition SET template = $$
4271 [%- USE date -%]
4272 <pre>
4273 Queue ID: [% target.0.queue.id %]
4274 Queue Name: [% target.0.queue.name %]
4275 Queue Type: [% target.0.queue.queue_type %]
4276 Complete? [% target.0.queue.complete %]
4277
4278     [% FOR vqbr IN target %]
4279 =-=-=
4280  Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
4281  Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
4282  Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
4283  Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
4284  ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
4285  ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
4286  Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
4287  Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
4288  TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
4289  TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
4290  Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
4291  Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
4292  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
4293  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
4294  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
4295  Import Error     | [% vqbr.import_error %]
4296  Error Detail     | [% vqbr.error_detail %]
4297  Match Count      | [% vqbr.matches.size %]
4298
4299     [% END %]
4300 </pre>
4301 $$
4302 WHERE id = 39;
4303
4304
4305 -- Do the same for the CVS version
4306
4307 UPDATE action_trigger.event_definition SET template = $$
4308 [%- USE date -%]
4309 "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"
4310 [% 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 %]"
4311 [% END %]
4312 $$
4313 WHERE id = 40;
4314
4315 -- Add matches to the env for both
4316 INSERT INTO action_trigger.environment (event_def, path) VALUES (39, 'matches');
4317 INSERT INTO action_trigger.environment (event_def, path) VALUES (40, 'matches');
4318
4319
4320 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4321
4322 -- check whether patch can be applied
4323 SELECT evergreen.upgrade_deps_block_check('0609', :eg_version);
4324
4325 ALTER TABLE acq.lineitem_detail 
4326     ADD COLUMN receiver INT REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
4327
4328
4329 -- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
4330
4331 -- check whether patch can be applied
4332 SELECT evergreen.upgrade_deps_block_check('0610', :eg_version);
4333
4334 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
4335     'acq.copy_creator_uses_receiver',
4336     oils_i18n_gettext( 
4337         'acq.copy_creator_uses_receiver',
4338         'Acq: Set copy creator as receiver',
4339         'coust',
4340         'label'
4341     ),
4342     oils_i18n_gettext( 
4343         'acq.copy_creator_uses_receiver',
4344         'When receiving a copy in acquisitions, set the copy "creator" to be the staff that received the copy',
4345         'coust',
4346         'label'
4347     ),
4348     'bool'
4349 );
4350
4351 -- Evergreen DB patch 0611.data.magic_macros.sql
4352
4353 -- check whether patch can be applied
4354 SELECT evergreen.upgrade_deps_block_check('0611', :eg_version);
4355
4356 INSERT into config.org_unit_setting_type
4357 ( name, label, description, datatype ) VALUES
4358 (
4359         'circ.staff_client.receipt.header_text',
4360         oils_i18n_gettext(
4361             'circ.staff_client.receipt.header_text',
4362             'Receipt Template: Content of header_text include',
4363             'coust',
4364             'label'
4365         ),
4366         oils_i18n_gettext(
4367             'circ.staff_client.receipt.header_text',
4368             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(header_text)%',
4369             'coust',
4370             'description'
4371         ),
4372         'string'
4373     )
4374 ,(
4375         'circ.staff_client.receipt.footer_text',
4376         oils_i18n_gettext(
4377             'circ.staff_client.receipt.footer_text',
4378             'Receipt Template: Content of footer_text include',
4379             'coust',
4380             'label'
4381         ),
4382         oils_i18n_gettext(
4383             'circ.staff_client.receipt.footer_text',
4384             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(footer_text)%',
4385             'coust',
4386             'description'
4387         ),
4388         'string'
4389     )
4390 ,(
4391         'circ.staff_client.receipt.notice_text',
4392         oils_i18n_gettext(
4393             'circ.staff_client.receipt.notice_text',
4394             'Receipt Template: Content of notice_text include',
4395             'coust',
4396             'label'
4397         ),
4398         oils_i18n_gettext(
4399             'circ.staff_client.receipt.notice_text',
4400             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(notice_text)%',
4401             'coust',
4402             'description'
4403         ),
4404         'string'
4405     )
4406 ,(
4407         'circ.staff_client.receipt.alert_text',
4408         oils_i18n_gettext(
4409             'circ.staff_client.receipt.alert_text',
4410             'Receipt Template: Content of alert_text include',
4411             'coust',
4412             'label'
4413         ),
4414         oils_i18n_gettext(
4415             'circ.staff_client.receipt.alert_text',
4416             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(alert_text)%',
4417             'coust',
4418             'description'
4419         ),
4420         'string'
4421     )
4422 ,(
4423         'circ.staff_client.receipt.event_text',
4424         oils_i18n_gettext(
4425             'circ.staff_client.receipt.event_text',
4426             'Receipt Template: Content of event_text include',
4427             'coust',
4428             'label'
4429         ),
4430         oils_i18n_gettext(
4431             'circ.staff_client.receipt.event_text',
4432             'Text/HTML/Macros to be inserted into receipt templates in place of %INCLUDE(event_text)%',
4433             'coust',
4434             'description'
4435         ),
4436         'string'
4437     );
4438
4439 -- Evergreen DB patch 0612.schema.authority_overlay_protection.sql
4440 --
4441
4442
4443 -- check whether patch can be applied
4444 SELECT evergreen.upgrade_deps_block_check('0612', :eg_version);
4445
4446 -- FIXME: add/check SQL statements to perform the upgrade
4447
4448 -- Function to generate an ephemeral overlay template from an authority record
4449 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
4450 DECLARE
4451     cset                INT;
4452     main_entry          authority.control_set_authority_field%ROWTYPE;
4453     bib_field           authority.control_set_bib_field%ROWTYPE;
4454     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
4455     replace_data        XML[] DEFAULT '{}'::XML[];
4456     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
4457     auth_field          XML[];
4458 BEGIN
4459     IF auth_id IS NULL THEN
4460         RETURN NULL;
4461     END IF;
4462
4463     -- Default to the LoC controll set
4464     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
4465
4466     -- if none, make a best guess
4467     IF cset IS NULL THEN
4468         SELECT  control_set INTO cset
4469           FROM  authority.control_set_authority_field
4470           WHERE tag IN (
4471                     SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marc::XML)::TEXT[])
4472                       FROM  authority.record_entry
4473                       WHERE id = auth_id
4474                 )
4475           LIMIT 1;
4476     END IF;
4477
4478     -- if STILL none, no-op change
4479     IF cset IS NULL THEN
4480         RETURN XMLELEMENT(
4481             name record,
4482             XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4483             XMLELEMENT( name leader, '00881nam a2200193   4500'),
4484             XMLELEMENT(
4485                 name datafield,
4486                 XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4487                 XMLELEMENT(
4488                     name subfield,
4489                     XMLATTRIBUTES('d' AS code),
4490                     '901c'
4491                 )
4492             )
4493         )::TEXT;
4494     END IF;
4495
4496     FOR main_entry IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
4497         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
4498         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
4499             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
4500                 replace_data := replace_data || XMLELEMENT( name datafield, XMLATTRIBUTES(bib_field.tag AS tag), XPATH('//*[local-name()="subfield"]',auth_field[1])::XML[]);
4501                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
4502             END LOOP;
4503             EXIT;
4504         END IF;
4505     END LOOP;
4506
4507     RETURN XMLELEMENT(
4508         name record,
4509         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
4510         XMLELEMENT( name leader, '00881nam a2200193   4500'),
4511         replace_data,
4512         XMLELEMENT(
4513             name datafield,
4514             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
4515             XMLELEMENT(
4516                 name subfield,
4517                 XMLATTRIBUTES('r' AS code),
4518                 ARRAY_TO_STRING(replace_rules,',')
4519             )
4520         )
4521     )::TEXT;
4522 END;
4523 $f$ STABLE LANGUAGE PLPGSQL;
4524
4525
4526
4527 -- Evergreen DB patch 0613.schema.vandelay_isxn_normalization.sql
4528 --
4529
4530
4531 -- check whether patch can be applied
4532 SELECT evergreen.upgrade_deps_block_check('0613', :eg_version);
4533
4534 CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
4535     record_xml TEXT
4536 ) RETURNS HSTORE AS $func$
4537 BEGIN
4538     RETURN (SELECT
4539         HSTORE(
4540             ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
4541             ARRAY_ACCUM(value)
4542         )
4543         FROM (
4544             SELECT  tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
4545               FROM  (SELECT tag,
4546                             subfield,
4547                             CASE WHEN tag = '020' THEN -- caseless -- isbn
4548                                 LOWER((REGEXP_MATCHES(value,$$^(\S{10,17})$$))[1] || '%')
4549                             WHEN tag = '022' THEN -- caseless -- issn
4550                                 LOWER((REGEXP_MATCHES(value,$$^(\S{4}[- ]?\S{4})$$))[1] || '%')
4551                             WHEN tag = '024' THEN -- caseless -- upc (other)
4552                                 LOWER(value || '%')
4553                             ELSE
4554                                 value
4555                             END AS value
4556                       FROM  vandelay.flatten_marc(record_xml)) x
4557                 GROUP BY tag, subfield ORDER BY tag, subfield
4558         ) subquery
4559     );
4560 END;
4561 $func$ LANGUAGE PLPGSQL;
4562
4563 CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
4564     node vandelay.match_set_point
4565 ) RETURNS VOID AS $$
4566 DECLARE
4567     jrow        TEXT;
4568     my_alias    TEXT;
4569     op          TEXT;
4570     tagkey      TEXT;
4571     caseless    BOOL;
4572 BEGIN
4573     -- remember $1 is tags_rstore, and $2 is svf_rstore
4574
4575     caseless := FALSE;
4576
4577     IF node.tag IS NOT NULL THEN
4578         caseless := (node.tag IN ('020', '022', '024'));
4579         tagkey := node.tag;
4580         IF node.subfield IS NOT NULL THEN
4581             tagkey := tagkey || node.subfield;
4582         END IF;
4583     END IF;
4584
4585     IF node.negate THEN
4586         IF caseless THEN
4587             op := 'NOT LIKE';
4588         ELSE
4589             op := '<>';
4590         END IF;
4591     ELSE
4592         IF caseless THEN
4593             op := 'LIKE';
4594         ELSE
4595             op := '=';
4596         END IF;
4597     END IF;
4598
4599     my_alias := 'n' || node.id::TEXT;
4600
4601     jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
4602         ' AS quality FROM metabib.';
4603     IF node.tag IS NOT NULL THEN
4604         jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
4605             my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
4606             node.tag || '''';
4607         IF node.subfield IS NOT NULL THEN
4608             jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
4609                 node.subfield || '''';
4610         END IF;
4611         jrow := jrow || ' AND (';
4612
4613         IF caseless THEN
4614             jrow := jrow || 'LOWER(' || my_alias || '.value) ' || op;
4615         ELSE
4616             jrow := jrow || my_alias || '.value ' || op;
4617         END IF;
4618
4619         jrow := jrow || ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
4620     ELSE    -- svf
4621         jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
4622             my_alias || '.id = bre.id AND (' ||
4623             my_alias || '.attrs->''' || node.svf ||
4624             ''' ' || op || ' $2->''' || node.svf || '''))';
4625     END IF;
4626     INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
4627 END;
4628 $$ LANGUAGE PLPGSQL;
4629
4630
4631
4632 -- Evergreen DB patch XXXX.schema.generic-mapping-index-normalizer.sql
4633 --
4634
4635 -- check whether patch can be applied
4636 SELECT evergreen.upgrade_deps_block_check('0615', :eg_version);
4637
4638 -- evergreen.generic_map_normalizer 
4639
4640 CREATE OR REPLACE FUNCTION evergreen.generic_map_normalizer ( TEXT, TEXT ) RETURNS TEXT AS $f$
4641 my $string = shift;
4642 my %map;
4643
4644 my $default = $string;
4645
4646 $_ = shift;
4647 while (/^\s*?(.*?)\s*?=>\s*?(\S+)\s*/) {
4648     if ($1 eq '') {
4649         $default = $2;
4650     } else {
4651         $map{$2} = [split(/\s*,\s*/, $1)];
4652     }
4653     $_ = $';
4654 }
4655
4656 for my $key ( keys %map ) {
4657     return $key if (grep { $_ eq $string } @{ $map{$key} });
4658 }
4659
4660 return $default;
4661
4662 $f$ LANGUAGE PLPERLU;
4663
4664 -- evergreen.generic_map_normalizer 
4665
4666 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
4667     'Generic Mapping Normalizer', 
4668     'Map values or sets of values to new values',
4669     'generic_map_normalizer', 
4670     1
4671 );
4672
4673
4674 SELECT evergreen.upgrade_deps_block_check('0616', :eg_version);
4675
4676 CREATE OR REPLACE FUNCTION actor.org_unit_prox_update () RETURNS TRIGGER as $$
4677 BEGIN
4678
4679
4680 IF TG_OP = 'DELETE' THEN
4681
4682     DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4683
4684 END IF;
4685
4686 IF TG_OP = 'UPDATE' THEN
4687
4688     IF NEW.parent_ou <> OLD.parent_ou THEN
4689
4690         DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
4691             INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4692             SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4693                 FROM  actor.org_unit l, actor.org_unit r
4694                 WHERE (l.id = NEW.id or r.id = NEW.id);
4695
4696     END IF;
4697
4698 END IF;
4699
4700 IF TG_OP = 'INSERT' THEN
4701
4702      INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
4703      SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
4704          FROM  actor.org_unit l, actor.org_unit r
4705          WHERE (l.id = NEW.id or r.id = NEW.id);
4706
4707 END IF;
4708
4709 RETURN null;
4710
4711 END;
4712 $$ LANGUAGE plpgsql;
4713
4714
4715 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 ();
4716
4717
4718 SELECT evergreen.upgrade_deps_block_check('0617', :eg_version);
4719
4720 -- add notify columns to booking.reservation
4721 ALTER TABLE booking.reservation
4722   ADD COLUMN email_notify BOOLEAN NOT NULL DEFAULT FALSE;
4723
4724 -- create the hook and validator
4725 INSERT INTO action_trigger.hook (key, core_type, description, passive)
4726   VALUES ('reservation.available', 'bresv', 'A reservation is available for pickup', false);
4727 INSERT INTO action_trigger.validator (module, description)
4728   VALUES ('ReservationIsAvailable','Checked that a reserved resource is available for checkout');
4729
4730 -- create org unit setting to toggle checkbox display
4731 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
4732   VALUES ('booking.allow_email_notify', 'booking.allow_email_notify', 'Permit email notification when a reservation is ready for pickup.', 'bool');
4733
4734
4735 SELECT evergreen.upgrade_deps_block_check('0618', :eg_version);
4736
4737 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';
4738
4739 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';
4740
4741 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';
4742
4743 -- Evergreen DB patch 0619.schema.au_last_update_time.sql
4744
4745 -- check whether patch can be applied
4746 SELECT evergreen.upgrade_deps_block_check('0619', :eg_version);
4747
4748 -- Add new column last_update_time to actor.usr, with trigger to maintain it
4749 -- Add corresponding new column to auditor.actor_usr_history
4750
4751 ALTER TABLE actor.usr
4752         ADD COLUMN last_update_time TIMESTAMPTZ;
4753
4754 ALTER TABLE auditor.actor_usr_history
4755         ADD COLUMN last_update_time TIMESTAMPTZ;
4756
4757 CREATE OR REPLACE FUNCTION actor.au_updated()
4758 RETURNS TRIGGER AS $$
4759 BEGIN
4760     NEW.last_update_time := now();
4761         RETURN NEW;
4762 END;
4763 $$ LANGUAGE plpgsql;
4764
4765 CREATE TRIGGER au_update_trig
4766         BEFORE INSERT OR UPDATE ON actor.usr
4767         FOR EACH ROW EXECUTE PROCEDURE actor.au_updated();
4768
4769 -- Evergreen DB patch XXXX.data.opac_payment_history_age_limit.sql
4770
4771
4772 SELECT evergreen.upgrade_deps_block_check('0621', :eg_version);
4773
4774 INSERT into config.org_unit_setting_type (name, label, description, datatype)
4775 VALUES (
4776     'opac.payment_history_age_limit',
4777     oils_i18n_gettext('opac.payment_history_age_limit',
4778         'OPAC: Payment History Age Limit', 'coust', 'label'),
4779     oils_i18n_gettext('opac.payment_history_age_limit',
4780         'The OPAC should not display payments by patrons that are older than any interval defined here.', 'coust', 'label'),
4781     'interval'
4782 );
4783
4784 -- Updates config.org_unit_setting_type to remove the old tag prefixes for once 
4785 -- groups have been added.
4786 --
4787
4788 SELECT evergreen.upgrade_deps_block_check('0622', :eg_version);
4789
4790 INSERT INTO config.settings_group (name, label) VALUES
4791 ('sys', oils_i18n_gettext('config.settings_group.system', 'System', 'coust', 'label')),
4792 ('gui', oils_i18n_gettext('config.settings_group.gui', 'GUI', 'coust', 'label')),
4793 ('lib', oils_i18n_gettext('config.settings_group.lib', 'Library', 'coust', 'label')),
4794 ('sec', oils_i18n_gettext('config.settings_group.sec', 'Security', 'coust', 'label')),
4795 ('cat', oils_i18n_gettext('config.settings_group.cat', 'Cataloging', 'coust', 'label')),
4796 ('holds', oils_i18n_gettext('config.settings_group.holds', 'Holds', 'coust', 'label')),
4797 ('circ', oils_i18n_gettext('config.settings_group.circulation', 'Circulation', 'coust', 'label')),
4798 ('self', oils_i18n_gettext('config.settings_group.self', 'Self Check', 'coust', 'label')),
4799 ('opac', oils_i18n_gettext('config.settings_group.opac', 'OPAC', 'coust', 'label')),
4800 ('prog', oils_i18n_gettext('config.settings_group.program', 'Program', 'coust', 'label')),
4801 ('glob', oils_i18n_gettext('config.settings_group.global', 'Global', 'coust', 'label')),
4802 ('finance', oils_i18n_gettext('config.settings_group.finances', 'Finanaces', 'coust', 'label')),
4803 ('credit', oils_i18n_gettext('config.settings_group.ccp', 'Credit Card Processing', 'coust', 'label')),
4804 ('serial', oils_i18n_gettext('config.settings_group.serial', 'Serials', 'coust', 'label')),
4805 ('recall', oils_i18n_gettext('config.settings_group.recall', 'Recalls', 'coust', 'label')),
4806 ('booking', oils_i18n_gettext('config.settings_group.booking', 'Booking', 'coust', 'label')),
4807 ('offline', oils_i18n_gettext('config.settings_group.offline', 'Offline', 'coust', 'label')),
4808 ('receipt_template', oils_i18n_gettext('config.settings_group.receipt_template', 'Receipt Template', 'coust', 'label'));
4809
4810 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Set copy creator as receiver' WHERE name = 'acq.copy_creator_uses_receiver';
4811 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_circ_modifier';
4812 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.default_copy_location';
4813 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.block';
4814 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'acq.fund.balance_limit.warn';
4815 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.holds.allow_holds_from_purchase_request';
4816 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_barcode_prefix';
4817 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'acq.tmp_callnumber_prefix';
4818 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.opac_timeout';
4819 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.persistent_login_interval';
4820 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'auth.staff_timeout';
4821 UPDATE config.org_unit_setting_type SET grp = 'booking' WHERE name = 'booking.allow_email_notify';
4822 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'cat.bib.alert_on_empty';
4823 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';
4824 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'cat.bib.keep_on_empty';
4825 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default Classification Scheme' WHERE name = 'cat.default_classification_scheme';
4826 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (fast add)' WHERE name = 'cat.default_copy_status_fast';
4827 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Default copy status (normal)' WHERE name = 'cat.default_copy_status_normal';
4828 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'cat.default_item_price';
4829 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font family' WHERE name = 'cat.label.font.family';
4830 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font size' WHERE name = 'cat.label.font.size';
4831 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine and pocket label font weight' WHERE name = 'cat.label.font.weight';
4832 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';
4833 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label maximum lines' WHERE name = 'cat.spine.line.height';
4834 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label left margin' WHERE name = 'cat.spine.line.margin';
4835 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Spine label line width' WHERE name = 'cat.spine.line.width';
4836 UPDATE config.org_unit_setting_type SET grp = 'cat', label='Delete volume with last copy' WHERE name = 'cat.volume.delete_on_empty';
4837 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';
4838 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Block Renewal of Items Needed for Holds' WHERE name = 'circ.block_renews_for_holds';
4839 UPDATE config.org_unit_setting_type SET grp = 'booking', label='Elbow room' WHERE name = 'circ.booking_reservation.default_elbow_room';
4840 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_lost_on_zero';
4841 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.charge_on_damaged';
4842 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_auto_renew_age';
4843 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold';
4844 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.checkout_fills_related_hold_exact_match_only';
4845 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_never_checked_out.mark_missing';
4846 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.claim_return.copy_status';
4847 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.damaged.void_ovedue';
4848 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.damaged_item_processing_fee';
4849 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';
4850 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard boundary' WHERE name = 'circ.hold_boundary.hard';
4851 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft boundary' WHERE name = 'circ.hold_boundary.soft';
4852 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Alert Interval' WHERE name = 'circ.hold_expire_alert_interval';
4853 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Expire Interval' WHERE name = 'circ.hold_expire_interval';
4854 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.hold_shelf_status_delay';
4855 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Soft stalling interval' WHERE name = 'circ.hold_stalling.soft';
4856 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Hard stalling interval' WHERE name = 'circ.hold_stalling_hard';
4857 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Use Active Date for Age Protection' WHERE name = 'circ.holds.age_protect.active_date';
4858 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Behind Desk Pickup Supported' WHERE name = 'circ.holds.behind_desk_pickup_supported';
4859 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display age' WHERE name = 'circ.holds.canceled.display_age';
4860 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Canceled holds display count' WHERE name = 'circ.holds.canceled.display_count';
4861 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Clear shelf copy status' WHERE name = 'circ.holds.clear_shelf.copy_status';
4862 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';
4863 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Default Estimated Wait' WHERE name = 'circ.holds.default_estimated_wait_interval';
4864 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.default_shelf_expire_interval';
4865 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';
4866 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Alert' WHERE name = 'circ.holds.hold_has_copy_at.alert';
4867 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Has Local Copy Block' WHERE name = 'circ.holds.hold_has_copy_at.block';
4868 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Maximum library target attempts' WHERE name = 'circ.holds.max_org_unit_target_loops';
4869 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Minimum Estimated Wait' WHERE name = 'circ.holds.min_estimated_wait_interval';
4870 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Org Unit Target Weight' WHERE name = 'circ.holds.org_unit_target_weight';
4871 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';
4872 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Truncated loan period.' WHERE name = 'circ.holds.recall_return_interval';
4873 UPDATE config.org_unit_setting_type SET grp = 'recall', label='Circulation duration that triggers a recall.' WHERE name = 'circ.holds.recall_threshold';
4874 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';
4875 UPDATE config.org_unit_setting_type SET grp = 'holds' WHERE name = 'circ.holds.target_skip_me';
4876 UPDATE config.org_unit_setting_type SET grp = 'holds', label='Reset request time on un-cancel' WHERE name = 'circ.holds.uncancel.reset_request_time';
4877 UPDATE config.org_unit_setting_type SET grp = 'holds', label='FIFO' WHERE name = 'circ.holds_fifo';
4878 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'circ.item_checkout_history.max';
4879 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost Checkin Generates New Overdues' WHERE name = 'circ.lost.generate_overdue_on_checkin';
4880 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Lost items usable on checkin' WHERE name = 'circ.lost_immediately_available';
4881 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'circ.lost_materials_processing_fee';
4882 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost max interval' WHERE name = 'circ.max_accept_return_of_lost';
4883 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Cap Max Fine at Item Price' WHERE name = 'circ.max_fine.cap_at_price';
4884 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.max_patron_claim_return_count';
4885 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Item Status for Missing Pieces' WHERE name = 'circ.missing_pieces.copy_status';
4886 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'circ.obscure_dob';
4887 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';
4888 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';
4889 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';
4890 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Offline: Patron Usernames Allowed' WHERE name = 'circ.offline.username_allowed';
4891 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';
4892 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';
4893 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';
4894 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';
4895 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';
4896 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.patron_invalid_address_apply_penalty';
4897 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.pre_cat_copy_circ_lib';
4898 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'circ.reshelving_complete.interval';
4899 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Restore overdues on lost item return' WHERE name = 'circ.restore_overdue_on_lost_return';
4900 UPDATE config.org_unit_setting_type SET grp = 'self', label='Pop-up alert for errors' WHERE name = 'circ.selfcheck.alert.popup';
4901 UPDATE config.org_unit_setting_type SET grp = 'self', label='Audio Alerts' WHERE name = 'circ.selfcheck.alert.sound';
4902 UPDATE config.org_unit_setting_type SET grp = 'self' WHERE name = 'circ.selfcheck.auto_override_checkout_events';
4903 UPDATE config.org_unit_setting_type SET grp = 'self', label='Block copy checkout status' WHERE name = 'circ.selfcheck.block_checkout_on_copy_status';
4904 UPDATE config.org_unit_setting_type SET grp = 'self', label='Patron Login Timeout (in seconds)' WHERE name = 'circ.selfcheck.patron_login_timeout';
4905 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require Patron Password' WHERE name = 'circ.selfcheck.patron_password_required';
4906 UPDATE config.org_unit_setting_type SET grp = 'self', label='Require patron password' WHERE name = 'circ.selfcheck.require_patron_password';
4907 UPDATE config.org_unit_setting_type SET grp = 'self', label='Workstation Required' WHERE name = 'circ.selfcheck.workstation_required';
4908 UPDATE config.org_unit_setting_type SET grp = 'circ' WHERE name = 'circ.staff_client.actor_on_checkout';
4909 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'circ.staff_client.do_not_auto_attempt_print';
4910 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of alert_text include' WHERE name = 'circ.staff_client.receipt.alert_text';
4911 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of event_text include' WHERE name = 'circ.staff_client.receipt.event_text';
4912 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of footer_text include' WHERE name = 'circ.staff_client.receipt.footer_text';
4913 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of header_text include' WHERE name = 'circ.staff_client.receipt.header_text';
4914 UPDATE config.org_unit_setting_type SET grp = 'receipt_template', label='Content of notice_text include' WHERE name = 'circ.staff_client.receipt.notice_text';
4915 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Minimum Transit Checkin Interval' WHERE name = 'circ.transit.min_checkin_interval';
4916 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Deactivate Card' WHERE name = 'circ.user_merge.deactivate_cards';
4917 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Address Delete' WHERE name = 'circ.user_merge.delete_addresses';
4918 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Patron Merge Barcode Delete' WHERE name = 'circ.user_merge.delete_cards';
4919 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Void lost item billing when returned' WHERE name = 'circ.void_lost_on_checkin';
4920 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';
4921 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';
4922 UPDATE config.org_unit_setting_type SET grp = 'finance' WHERE name = 'credit.payments.allow';
4923 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable AuthorizeNet payments' WHERE name = 'credit.processor.authorizenet.enabled';
4924 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet login' WHERE name = 'credit.processor.authorizenet.login';
4925 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet password' WHERE name = 'credit.processor.authorizenet.password';
4926 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet server' WHERE name = 'credit.processor.authorizenet.server';
4927 UPDATE config.org_unit_setting_type SET grp = 'credit', label='AuthorizeNet test mode' WHERE name = 'credit.processor.authorizenet.testmode';
4928 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Name default credit processor' WHERE name = 'credit.processor.default';
4929 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayflowPro payments' WHERE name = 'credit.processor.payflowpro.enabled';
4930 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro login/merchant ID' WHERE name = 'credit.processor.payflowpro.login';
4931 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro partner' WHERE name = 'credit.processor.payflowpro.partner';
4932 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro password' WHERE name = 'credit.processor.payflowpro.password';
4933 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro test mode' WHERE name = 'credit.processor.payflowpro.testmode';
4934 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayflowPro vendor' WHERE name = 'credit.processor.payflowpro.vendor';
4935 UPDATE config.org_unit_setting_type SET grp = 'credit', label='Enable PayPal payments' WHERE name = 'credit.processor.paypal.enabled';
4936 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal login' WHERE name = 'credit.processor.paypal.login';
4937 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal password' WHERE name = 'credit.processor.paypal.password';
4938 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal signature' WHERE name = 'credit.processor.paypal.signature';
4939 UPDATE config.org_unit_setting_type SET grp = 'credit', label='PayPal test mode' WHERE name = 'credit.processor.paypal.testmode';
4940 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Dates with this pattern.' WHERE name = 'format.date';
4941 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Format Times with this pattern.' WHERE name = 'format.time';
4942 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.default_locale';
4943 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'global.juvenile_age_threshold';
4944 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'global.password_regex';
4945 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';
4946 UPDATE config.org_unit_setting_type SET grp = 'lib', label='Courier Code' WHERE name = 'lib.courier_code';
4947 UPDATE config.org_unit_setting_type SET grp = 'lib' WHERE name = 'notice.telephony.callfile_lines';
4948 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Allow pending addresses' WHERE name = 'opac.allow_pending_address';
4949 UPDATE config.org_unit_setting_type SET grp = 'glob' WHERE name = 'opac.barcode_regex';
4950 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Use fully compressed serial holdings' WHERE name = 'opac.fully_compressed_serial_holdings';
4951 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Org Unit Hiding Depth' WHERE name = 'opac.org_unit_hiding.depth';
4952 UPDATE config.org_unit_setting_type SET grp = 'opac', label='Payment History Age Limit' WHERE name = 'opac.payment_history_age_limit';
4953 UPDATE config.org_unit_setting_type SET grp = 'prog' WHERE name = 'org.bounced_emails';
4954 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Boundary' WHERE name = 'org.patron_opt_boundary';
4955 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Patron Opt-In Default' WHERE name = 'org.patron_opt_default';
4956 UPDATE config.org_unit_setting_type SET grp = 'sec' WHERE name = 'patron.password.use_phone';
4957 UPDATE config.org_unit_setting_type SET grp = 'serial', label='Previous Issuance Copy Location' WHERE name = 'serial.prev_issuance_copy_location';
4958 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Patrons Logged' WHERE name = 'ui.admin.patron_log.max_entries';
4959 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Work Log: Maximum Actions Logged' WHERE name = 'ui.admin.work_log.max_entries';
4960 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';
4961 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';
4962 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';
4963 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';
4964 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.patron_summary.horizontal';
4965 UPDATE config.org_unit_setting_type SET grp = 'gui' WHERE name = 'ui.circ.show_billing_tab_on_bills';
4966 UPDATE config.org_unit_setting_type SET grp = 'circ', label='Suppress popup-dialogs during check-in.' WHERE name = 'ui.circ.suppress_checkin_popups';
4967 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Button bar' WHERE name = 'ui.general.button_bar';
4968 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Hotkeyset' WHERE name = 'ui.general.hotkeyset';
4969 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Idle timeout' WHERE name = 'ui.general.idle_timeout';
4970 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Country for New Addresses in Patron Editor' WHERE name = 'ui.patron.default_country';
4971 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default Ident Type for Patron Registration' WHERE name = 'ui.patron.default_ident_type';
4972 UPDATE config.org_unit_setting_type SET grp = 'sec', label='Default level of patrons'' internet access' WHERE name = 'ui.patron.default_inet_access_level';
4973 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show active field on patron registration' WHERE name = 'ui.patron.edit.au.active.show';
4974 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest active field on patron registration' WHERE name = 'ui.patron.edit.au.active.suggest';
4975 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';
4976 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';
4977 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.show';
4978 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest alias field on patron registration' WHERE name = 'ui.patron.edit.au.alias.suggest';
4979 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.show';
4980 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest barred field on patron registration' WHERE name = 'ui.patron.edit.au.barred.suggest';
4981 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';
4982 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';
4983 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';
4984 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';
4985 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';
4986 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';
4987 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';
4988 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';
4989 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';
4990 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';
4991 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.require';
4992 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.show';
4993 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest dob field on patron registration' WHERE name = 'ui.patron.edit.au.dob.suggest';
4994 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';
4995 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';
4996 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require email field on patron registration' WHERE name = 'ui.patron.edit.au.email.require';
4997 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show email field on patron registration' WHERE name = 'ui.patron.edit.au.email.show';
4998 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest email field on patron registration' WHERE name = 'ui.patron.edit.au.email.suggest';
4999 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';
5000 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';
5001 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';
5002 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';
5003 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';
5004 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';
5005 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';
5006 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';
5007 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';
5008 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.show';
5009 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest juvenile field on patron registration' WHERE name = 'ui.patron.edit.au.juvenile.suggest';
5010 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';
5011 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';
5012 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';
5013 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';
5014 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';
5015 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';
5016 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';
5017 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';
5018 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';
5019 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Show suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.show';
5020 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Suggest suffix field on patron registration' WHERE name = 'ui.patron.edit.au.suffix.suggest';
5021 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Require county field on patron registration' WHERE name = 'ui.patron.edit.aua.county.require';
5022 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';
5023 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';
5024 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Default showing suggested patron registration fields' WHERE name = 'ui.patron.edit.default_suggested';
5025 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Example for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.example';
5026 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Regex for phone fields on patron registration' WHERE name = 'ui.patron.edit.phone.regex';
5027 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';
5028 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';
5029 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';
5030 UPDATE config.org_unit_setting_type SET grp = 'gui', label='Unified Volume/Item Creator/Editor' WHERE name = 'ui.unified_volume_copy_editor';
5031 UPDATE config.org_unit_setting_type SET grp = 'gui', label='URL for remote directory containing list column settings.' WHERE name = 'url.remote_column_settings';
5032
5033
5034
5035
5036 SELECT evergreen.upgrade_deps_block_check('0623', :eg_version);
5037
5038
5039 CREATE TABLE config.org_unit_setting_type_log (
5040     id              BIGSERIAL   PRIMARY KEY,
5041     date_applied    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
5042     org             INT         REFERENCES actor.org_unit (id),
5043     original_value  TEXT,
5044     new_value       TEXT,
5045     field_name      TEXT      REFERENCES config.org_unit_setting_type (name)
5046 );
5047
5048 -- Log each change in oust to oustl, so admins can see what they messed up if someting stops working.
5049 CREATE OR REPLACE FUNCTION ous_change_log() RETURNS TRIGGER AS $ous_change_log$
5050     DECLARE
5051     original TEXT;
5052     BEGIN
5053         -- Check for which setting is being updated, and log it.
5054         SELECT INTO original value FROM actor.org_unit_setting WHERE name = NEW.name AND org_unit = NEW.org_unit;
5055                 
5056         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (NEW.org_unit, original, NEW.value, NEW.name);
5057         
5058         RETURN NEW;
5059     END;
5060 $ous_change_log$ LANGUAGE plpgsql;    
5061
5062 CREATE TRIGGER log_ous_change
5063     BEFORE INSERT OR UPDATE ON actor.org_unit_setting
5064     FOR EACH ROW EXECUTE PROCEDURE ous_change_log();
5065
5066 CREATE OR REPLACE FUNCTION ous_delete_log() RETURNS TRIGGER AS $ous_delete_log$
5067     DECLARE
5068     original TEXT;
5069     BEGIN
5070         -- Check for which setting is being updated, and log it.
5071         SELECT INTO original value FROM actor.org_unit_setting WHERE name = OLD.name AND org_unit = OLD.org_unit;
5072                 
5073         INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (OLD.org_unit, original, 'null', OLD.name);
5074         
5075         RETURN OLD;
5076     END;
5077 $ous_delete_log$ LANGUAGE plpgsql;    
5078
5079 CREATE TRIGGER log_ous_del
5080     BEFORE DELETE ON actor.org_unit_setting
5081     FOR EACH ROW EXECUTE PROCEDURE ous_delete_log();
5082
5083 -- Evergreen DB patch 0625.data.opac_staff_saved_search_size.sql
5084
5085
5086 SELECT evergreen.upgrade_deps_block_check('0625', :eg_version);
5087
5088 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype)
5089 VALUES (
5090     'opac.staff_saved_search.size', 'opac',
5091     oils_i18n_gettext('opac.staff_saved_search.size',
5092         'OPAC: Number of staff client saved searches to display on left side of results and record details pages', 'coust', 'label'),
5093     oils_i18n_gettext('opac.staff_saved_search.size',
5094         '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'),
5095     'integer'
5096 );
5097
5098 -- Evergreen DB patch 0626.schema.bookbag-goodies.sql
5099
5100
5101 SELECT evergreen.upgrade_deps_block_check('0626', :eg_version);
5102
5103 ALTER TABLE container.biblio_record_entry_bucket
5104     ADD COLUMN description TEXT;
5105
5106 ALTER TABLE container.call_number_bucket
5107     ADD COLUMN description TEXT;
5108
5109 ALTER TABLE container.copy_bucket
5110     ADD COLUMN description TEXT;
5111
5112 ALTER TABLE container.user_bucket
5113     ADD COLUMN description TEXT;
5114
5115 INSERT INTO action_trigger.hook (key, core_type, description, passive)
5116 VALUES (
5117     'container.biblio_record_entry_bucket.csv',
5118     'cbreb',
5119     oils_i18n_gettext(
5120         'container.biblio_record_entry_bucket.csv',
5121         'Produce a CSV file representing a bookbag',
5122         'ath',
5123         'description'
5124     ),
5125     FALSE
5126 );
5127
5128 INSERT INTO action_trigger.reactor (module, description)
5129 VALUES (
5130     'ContainerCSV',
5131     oils_i18n_gettext(
5132         'ContainerCSV',
5133         'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
5134         'atr',
5135         'description'
5136     )
5137 );
5138
5139 INSERT INTO action_trigger.event_definition (
5140     id, active, owner,
5141     name, hook, reactor,
5142     validator, template
5143 ) VALUES (
5144     48, TRUE, 1,
5145     'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
5146     'NOOP_True',
5147 $$
5148 [%-
5149 # target is the bookbag itself. The 'items' variable does not need to be in
5150 # the environment because a special reactor will take care of filling it in.
5151
5152 FOR item IN items;
5153     bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
5154     title = "";
5155     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
5156         title = title _ part.textContent;
5157     END;
5158     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
5159
5160     helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
5161 END -%]
5162 $$
5163 );
5164
5165 -- Evergreen DB patch 0627.data.patron-password-reset-msg.sql
5166 --
5167 -- Updates password reset template to match TPAC reset form
5168 --
5169
5170 -- check whether patch can be applied
5171 SELECT evergreen.upgrade_deps_block_check('0627', :eg_version);
5172
5173 UPDATE action_trigger.event_definition SET template = 
5174 $$
5175 [%- USE date -%]
5176 [%- user = target.usr -%]
5177 To: [%- params.recipient_email || user.email %]
5178 From: [%- params.sender_email || user.home_ou.email || default_sender %]
5179 Subject: [% user.home_ou.name %]: library account password reset request
5180
5181 You have received this message because you, or somebody else, requested a reset
5182 of your library system password. If you did not request a reset of your library
5183 system password, just ignore this message and your current password will
5184 continue to work.
5185
5186 If you did request a reset of your library system password, please perform
5187 the following steps to continue the process of resetting your password:
5188
5189 1. Open the following link in a web browser: https://[% params.hostname %]/eg/opac/password_reset/[% target.uuid %]
5190 The browser displays a password reset form.
5191
5192 2. Enter your new password in the password reset form in the browser. You must
5193 enter the password twice to ensure that you do not make a mistake. If the
5194 passwords match, you will then be able to log in to your library system account
5195 with the new password.
5196
5197 $$
5198 WHERE id = 20; -- Password reset request notification
5199
5200
5201 SELECT evergreen.upgrade_deps_block_check('0630', :eg_version);
5202
5203 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5204 ( 'circ.transit.suppress_hold', 'circ',
5205     oils_i18n_gettext('circ.transit.suppress_hold',
5206         'Suppress Hold Transits Group',
5207         'coust', 'label'),
5208     oils_i18n_gettext('circ.transit.suppress_hold',
5209         '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.',
5210         'coust', 'description'),
5211     'string')
5212 ,( 'circ.transit.suppress_non_hold', 'circ',
5213     oils_i18n_gettext('circ.transit.suppress_non_hold',
5214         'Suppress Non-Hold Transits Group',
5215         'coust', 'label'),
5216     oils_i18n_gettext('circ.transit.suppress_non_hold',
5217         '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.',
5218         'coust', 'description'),
5219     'string');
5220
5221
5222 -- check whether patch can be applied
5223 SELECT evergreen.upgrade_deps_block_check('0632', :eg_version);
5224
5225 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
5226 ( 'opac.username_regex', 'glob',
5227     oils_i18n_gettext('opac.username_regex',
5228         'Patron username format',
5229         'coust', 'label'),
5230     oils_i18n_gettext('opac.username_regex',
5231         'Regular expression defining the patron username format, used for patron registration and self-service username changing only',
5232         'coust', 'description'),
5233     'string')
5234 ,( 'opac.lock_usernames', 'glob',
5235     oils_i18n_gettext('opac.lock_usernames',
5236         'Lock Usernames',
5237         'coust', 'label'),
5238     oils_i18n_gettext('opac.lock_usernames',
5239         'If enabled username changing via the OPAC will be disabled',
5240         'coust', 'description'),
5241     'bool')
5242 ,( 'opac.unlimit_usernames', 'glob',
5243     oils_i18n_gettext('opac.unlimit_usernames',
5244         'Allow multiple username changes',
5245         'coust', 'label'),
5246     oils_i18n_gettext('opac.unlimit_usernames',
5247         '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.',
5248         'coust', 'description'),
5249     'bool')
5250 ;
5251
5252 -- Evergreen DB patch 0635.data.opac.jump-to-details-setting.sql
5253 --
5254
5255
5256 -- check whether patch can be applied
5257 SELECT evergreen.upgrade_deps_block_check('0635', :eg_version);
5258
5259 INSERT INTO config.org_unit_setting_type ( name, grp, label, description, datatype )
5260     VALUES (
5261         'opac.staff.jump_to_details_on_single_hit', 
5262         'opac',
5263         oils_i18n_gettext(
5264             'opac.staff.jump_to_details_on_single_hit',
5265             'Jump to details on 1 hit (staff client)',
5266             'coust', 
5267             'label'
5268         ),
5269         oils_i18n_gettext(
5270             'opac.staff.jump_to_details_on_single_hit',
5271             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the OPAC within the staff client',
5272             'coust', 
5273             'description'
5274         ),
5275         'bool'
5276     ), (
5277         'opac.patron.jump_to_details_on_single_hit', 
5278         'opac',
5279         oils_i18n_gettext(
5280             'opac.patron.jump_to_details_on_single_hit',
5281             'Jump to details on 1 hit (public)',
5282             'coust', 
5283             'label'
5284         ),
5285         oils_i18n_gettext(
5286             'opac.patron.jump_to_details_on_single_hit',
5287             'When a search yields only 1 result, jump directly to the record details page.  This setting only affects the public OPAC',
5288             'coust', 
5289             'description'
5290         ),
5291         'bool'
5292     );
5293
5294 -- Evergreen DB patch 0636.data.grace_period_extend.sql
5295 --
5296 -- OU setting turns on grace period auto extension. By default they only do so
5297 -- when the grace period ends on a closed date, but there are two modifiers to
5298 -- change that.
5299 -- 
5300 -- The first modifier causes grace periods to extend for all closed dates that
5301 -- they intersect. This is "grace periods are only consumed by open days."
5302 -- 
5303 -- The second modifier causes a grace period that ends just before a closed
5304 -- day, with or without extension having happened, to include the closed day
5305 -- (and any following it) as well. This is mainly so that a backdate into the
5306 -- closed period following the grace period will assume the "best case" of the
5307 -- item having been returned after hours on the last day of the closed date.
5308 --
5309
5310
5311 -- check whether patch can be applied
5312 SELECT evergreen.upgrade_deps_block_check('0636', :eg_version);
5313
5314 INSERT INTO config.org_unit_setting_type(name, grp, label, description, datatype) VALUES
5315
5316 ( 'circ.grace.extend', 'circ',
5317     oils_i18n_gettext('circ.grace.extend',
5318         'Auto-Extend Grace Periods',
5319         'coust', 'label'),
5320     oils_i18n_gettext('circ.grace.extend',
5321         '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.',
5322         'coust', 'description'),
5323     'bool')
5324
5325 ,( 'circ.grace.extend.all', 'circ',
5326     oils_i18n_gettext('circ.grace.extend.all',
5327         'Auto-Extending Grace Periods extend for all closed dates',
5328         'coust', 'label'),
5329     oils_i18n_gettext('circ.grace.extend.all',
5330         '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".',
5331         'coust', 'description'),
5332     'bool')
5333
5334 ,( 'circ.grace.extend.into_closed', 'circ',
5335     oils_i18n_gettext('circ.grace.extend.into_closed',
5336         'Auto-Extending Grace Periods include trailing closed dates',
5337         'coust', 'label'),
5338     oils_i18n_gettext('circ.grace.extend.into_closed',
5339          '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.',
5340         'coust', 'description'),
5341     'bool');
5342
5343
5344 -- XXXX.schema-acs-nfi.sql
5345
5346 SELECT evergreen.upgrade_deps_block_check('0640', :eg_version);
5347
5348 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
5349 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
5350 BEGIN
5351
5352     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
5353         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
5354         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
5355         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5356           -- Should remove matching $0 from controlled fields at the same time?
5357         RETURN NEW; -- and we're done
5358     END IF;
5359
5360     IF TG_OP = 'UPDATE' THEN -- re-ingest?
5361         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
5362
5363         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
5364             RETURN NEW;
5365         END IF;
5366
5367         -- Propagate these updates to any linked bib records
5368         PERFORM authority.propagate_changes(NEW.id) FROM authority.record_entry WHERE id = NEW.id;
5369
5370         DELETE FROM authority.simple_heading WHERE record = NEW.id;
5371     END IF;
5372
5373     INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5374         SELECT record, atag, value, sort_value FROM authority.simple_heading_set(NEW.marc);
5375
5376     -- Flatten and insert the afr data
5377     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
5378     IF NOT FOUND THEN
5379         PERFORM authority.reingest_authority_full_rec(NEW.id);
5380         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
5381         IF NOT FOUND THEN
5382             PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
5383         END IF;
5384     END IF;
5385
5386     RETURN NEW;
5387 END;
5388 $func$ LANGUAGE PLPGSQL;
5389
5390 -- Entries that need to respect an NFI
5391 UPDATE authority.control_set_authority_field SET nfi = '2'
5392     WHERE id IN (4,24,44,64);
5393
5394 DROP TRIGGER authority_full_rec_fti_trigger ON authority.full_rec;
5395 CREATE TRIGGER authority_full_rec_fti_trigger
5396     BEFORE UPDATE OR INSERT ON authority.full_rec
5397     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5398
5399 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
5400 DECLARE
5401     acsaf           authority.control_set_authority_field%ROWTYPE;
5402     tag_used        TEXT;
5403     nfi_used        TEXT;
5404     sf              TEXT;
5405     thes_code       TEXT;
5406     cset            INT;
5407     heading_text    TEXT;
5408     tmp_text        TEXT;
5409     first_sf        BOOL;
5410     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5411 BEGIN
5412     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
5413
5414     IF cset IS NULL THEN
5415         SELECT  control_set INTO cset
5416           FROM  authority.control_set_authority_field
5417           WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
5418           LIMIT 1;
5419     END IF;
5420
5421     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
5422     IF thes_code IS NULL THEN
5423         thes_code := '|';
5424     ELSIF thes_code = 'z' THEN
5425         thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
5426     END IF;
5427
5428     heading_text := '';
5429     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
5430         tag_used := acsaf.tag;
5431         nfi_used := acsaf.nfi;
5432         first_sf := TRUE;
5433         FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5434             tmp_text := oils_xpath_string('//*[@tag="'||tag_used||'"]/*[@code="'||sf||'"]', marcxml);
5435
5436             IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
5437
5438                 tmp_text := SUBSTRING(
5439                     tmp_text FROM
5440                     COALESCE(
5441                         NULLIF(
5442                             REGEXP_REPLACE(
5443                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5444                                 $$\D+$$,
5445                                 '',
5446                                 'g'
5447                             ),
5448                             ''
5449                         )::INT,
5450                         0
5451                     ) + 1
5452                 );
5453
5454             END IF;
5455
5456             first_sf := FALSE;
5457
5458             IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
5459                 heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
5460             END IF;
5461         END LOOP;
5462         EXIT WHEN heading_text <> '';
5463     END LOOP;
5464
5465     IF heading_text <> '' THEN
5466         IF no_thesaurus IS TRUE THEN
5467             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
5468         ELSE
5469             heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
5470         END IF;
5471     ELSE
5472         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
5473     END IF;
5474
5475     RETURN heading_text;
5476 END;
5477 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5478
5479 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5480     SELECT authority.normalize_heading($1, TRUE);
5481 $func$ LANGUAGE SQL IMMUTABLE;
5482
5483 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
5484     SELECT authority.normalize_heading($1, FALSE);
5485 $func$ LANGUAGE SQL IMMUTABLE;
5486
5487
5488 CREATE TABLE authority.simple_heading (
5489     id              BIGSERIAL   PRIMARY KEY,
5490     record          BIGINT      NOT NULL REFERENCES authority.record_entry (id),
5491     atag            INT         NOT NULL REFERENCES authority.control_set_authority_field (id),
5492     value           TEXT        NOT NULL,
5493     sort_value      TEXT        NOT NULL,
5494     index_vector    tsvector    NOT NULL
5495 );
5496 CREATE TRIGGER authority_simple_heading_fti_trigger
5497     BEFORE UPDATE OR INSERT ON authority.simple_heading
5498     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
5499
5500 CREATE INDEX authority_simple_heading_index_vector_idx ON authority.simple_heading USING GIST (index_vector);
5501 CREATE INDEX authority_simple_heading_value_idx ON authority.simple_heading (value);
5502 CREATE INDEX authority_simple_heading_sort_value_idx ON authority.simple_heading (sort_value);
5503
5504 CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
5505 DECLARE
5506     res             authority.simple_heading%ROWTYPE;
5507     acsaf           authority.control_set_authority_field%ROWTYPE;
5508     tag_used        TEXT;
5509     nfi_used        TEXT;
5510     sf              TEXT;
5511     cset            INT;
5512     heading_text    TEXT;
5513     sort_text       TEXT;
5514     tmp_text        TEXT;
5515     tmp_xml         TEXT;
5516     first_sf        BOOL;
5517     auth_id         INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml)::INT;
5518 BEGIN
5519
5520     res.record := auth_id;
5521
5522     SELECT  control_set INTO cset
5523       FROM  authority.control_set_authority_field
5524       WHERE tag IN ( SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]) )
5525       LIMIT 1;
5526
5527     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
5528
5529         res.atag := acsaf.id;
5530         tag_used := acsaf.tag;
5531         nfi_used := acsaf.nfi;
5532
5533         FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)) LOOP
5534             heading_text := '';
5535
5536             FOR sf IN SELECT * FROM regexp_split_to_table(acsaf.sf_list,'') LOOP
5537                 heading_text := heading_text || COALESCE( ' ' || oils_xpath_string('//*[@code="'||sf||'"]',tmp_xml::TEXT), '');
5538             END LOOP;
5539
5540             heading_text := public.naco_normalize(heading_text);
5541             
5542             IF nfi_used IS NOT NULL THEN
5543
5544                 sort_text := SUBSTRING(
5545                     heading_text FROM
5546                     COALESCE(
5547                         NULLIF(
5548                             REGEXP_REPLACE(
5549                                 oils_xpath_string('//*[@tag="'||tag_used||'"]/@ind'||nfi_used, marcxml),
5550                                 $$\D+$$,
5551                                 '',
5552                                 'g'
5553                             ),
5554                             ''
5555                         )::INT,
5556                         0
5557                     ) + 1
5558                 );
5559
5560             ELSE
5561                 sort_text := heading_text;
5562             END IF;
5563
5564             IF heading_text IS NOT NULL AND heading_text <> '' THEN
5565                 res.value := heading_text;
5566                 res.sort_value := sort_text;
5567                 RETURN NEXT res;
5568             END IF;
5569
5570         END LOOP;
5571
5572     END LOOP;
5573
5574     RETURN;
5575 END;
5576 $func$ LANGUAGE PLPGSQL IMMUTABLE;
5577
5578 -- Support function used to find the pivot for alpha-heading-browse style searching
5579 CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT ) RETURNS TEXT AS $$
5580 DECLARE
5581     sort_value_row  RECORD;
5582     value_row       RECORD;
5583     t_term          TEXT;
5584 BEGIN
5585
5586     t_term := public.naco_normalize(q);
5587
5588     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5589                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5590             ash.sort_value
5591       INTO  sort_value_row
5592       FROM  authority.simple_heading ash
5593       WHERE ash.atag = ANY (a)
5594             AND ash.sort_value >= t_term
5595       ORDER BY rank DESC, ash.sort_value
5596       LIMIT 1;
5597
5598     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
5599                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
5600             ash.sort_value
5601       INTO  value_row
5602       FROM  authority.simple_heading ash
5603       WHERE ash.atag = ANY (a)
5604             AND ash.value >= t_term
5605       ORDER BY rank DESC, ash.sort_value
5606       LIMIT 1;
5607
5608     IF value_row.rank > sort_value_row.rank THEN
5609         RETURN value_row.sort_value;
5610     ELSE
5611         RETURN sort_value_row.sort_value;
5612     END IF;
5613 END;
5614 $$ LANGUAGE PLPGSQL;
5615
5616
5617 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 $$
5618 DECLARE
5619     pivot_sort_value    TEXT;
5620     boffset             INT DEFAULT 0;
5621     aoffset             INT DEFAULT 0;
5622     blimit              INT DEFAULT 0;
5623     alimit              INT DEFAULT 0;
5624 BEGIN
5625
5626     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5627
5628     IF page = 0 THEN
5629         blimit := pagesize / 2;
5630         alimit := blimit;
5631
5632         IF pagesize % 2 <> 0 THEN
5633             alimit := alimit + 1;
5634         END IF;
5635     ELSE
5636         blimit := pagesize;
5637         alimit := blimit;
5638
5639         boffset := pagesize / 2;
5640         aoffset := boffset;
5641
5642         IF pagesize % 2 <> 0 THEN
5643             boffset := boffset + 1;
5644         END IF;
5645     END IF;
5646
5647     IF page <= 0 THEN
5648         RETURN QUERY
5649             -- "bottom" half of the browse results
5650             SELECT id FROM (
5651                 SELECT  ash.id,
5652                         row_number() over ()
5653                   FROM  authority.simple_heading ash
5654                   WHERE ash.atag = ANY (atag_list)
5655                         AND ash.sort_value < pivot_sort_value
5656                   ORDER BY ash.sort_value DESC
5657                   LIMIT blimit
5658                   OFFSET ABS(page) * pagesize - boffset
5659             ) x ORDER BY row_number DESC;
5660     END IF;
5661
5662     IF page >= 0 THEN
5663         RETURN QUERY
5664             -- "bottom" half of the browse results
5665             SELECT  ash.id
5666               FROM  authority.simple_heading ash
5667               WHERE ash.atag = ANY (atag_list)
5668                     AND ash.sort_value >= pivot_sort_value
5669               ORDER BY ash.sort_value
5670               LIMIT alimit
5671               OFFSET ABS(page) * pagesize - aoffset;
5672     END IF;
5673 END;
5674 $$ LANGUAGE PLPGSQL ROWS 10;
5675
5676 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 $$
5677 DECLARE
5678     pivot_sort_value    TEXT;
5679 BEGIN
5680
5681     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
5682
5683     IF page < 0 THEN
5684         RETURN QUERY
5685             -- "bottom" half of the browse results
5686             SELECT id FROM (
5687                 SELECT  ash.id,
5688                         row_number() over ()
5689                   FROM  authority.simple_heading ash
5690                   WHERE ash.atag = ANY (atag_list)
5691                         AND ash.sort_value < pivot_sort_value
5692                   ORDER BY ash.sort_value DESC
5693                   LIMIT pagesize
5694                   OFFSET (ABS(page) - 1) * pagesize
5695             ) x ORDER BY row_number DESC;
5696     END IF;
5697
5698     IF page >= 0 THEN
5699         RETURN QUERY
5700             -- "bottom" half of the browse results
5701             SELECT  ash.id
5702               FROM  authority.simple_heading ash
5703               WHERE ash.atag = ANY (atag_list)
5704                     AND ash.sort_value >= pivot_sort_value
5705               ORDER BY ash.sort_value
5706               LIMIT pagesize
5707               OFFSET ABS(page) * pagesize ;
5708     END IF;
5709 END;
5710 $$ LANGUAGE PLPGSQL ROWS 10;
5711
5712 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 $$
5713     SELECT  ash.id
5714       FROM  authority.simple_heading ash,
5715             public.naco_normalize($2) t(term),
5716             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5717       WHERE ash.atag = ANY ($1)
5718             AND ash.index_vector @@ ptsq.term
5719       ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
5720                     + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
5721                     + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
5722       LIMIT $4
5723       OFFSET $4 * $3;
5724 $$ LANGUAGE SQL ROWS 10;
5725
5726 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 $$
5727     SELECT  ash.id
5728       FROM  authority.simple_heading ash,
5729             public.naco_normalize($2) t(term),
5730             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
5731       WHERE ash.atag = ANY ($1)
5732             AND ash.index_vector @@ ptsq.term
5733       ORDER BY ash.sort_value
5734       LIMIT $4
5735       OFFSET $4 * $3;
5736 $$ LANGUAGE SQL ROWS 10;
5737
5738
5739 CREATE OR REPLACE FUNCTION authority.axis_authority_tags(a TEXT) RETURNS INT[] AS $$
5740     SELECT ARRAY_ACCUM(field) FROM authority.browse_axis_authority_field_map WHERE axis = $1;
5741 $$ LANGUAGE SQL;
5742
5743 CREATE OR REPLACE FUNCTION authority.axis_authority_tags_refs(a TEXT) RETURNS INT[] AS $$
5744     SELECT  ARRAY_CAT(
5745                 ARRAY[a.field],
5746                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.field)
5747             )
5748       FROM  authority.browse_axis_authority_field_map a
5749       WHERE axis = $1
5750 $$ LANGUAGE SQL;
5751
5752
5753
5754 CREATE OR REPLACE FUNCTION authority.btag_authority_tags(btag TEXT) RETURNS INT[] AS $$
5755     SELECT ARRAY_ACCUM(authority_field) FROM authority.control_set_bib_field WHERE tag = $1
5756 $$ LANGUAGE SQL;
5757
5758 CREATE OR REPLACE FUNCTION authority.btag_authority_tags_refs(btag TEXT) RETURNS INT[] AS $$
5759     SELECT  ARRAY_CAT(
5760                 ARRAY[a.authority_field],
5761                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.authority_field)
5762             )
5763       FROM  authority.control_set_bib_field a
5764       WHERE a.tag = $1
5765 $$ LANGUAGE SQL;
5766
5767
5768
5769 CREATE OR REPLACE FUNCTION authority.atag_authority_tags(atag TEXT) RETURNS INT[] AS $$
5770     SELECT ARRAY_ACCUM(id) FROM authority.control_set_authority_field WHERE tag = $1
5771 $$ LANGUAGE SQL;
5772
5773 CREATE OR REPLACE FUNCTION authority.atag_authority_tags_refs(atag TEXT) RETURNS INT[] AS $$
5774     SELECT  ARRAY_CAT(
5775                 ARRAY[a.id],
5776                 (SELECT ARRAY_ACCUM(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.id)
5777             )
5778       FROM  authority.control_set_authority_field a
5779       WHERE a.tag = $1
5780 $$ LANGUAGE SQL;
5781
5782
5783 CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
5784     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4)
5785 $$ LANGUAGE SQL ROWS 10;
5786
5787 CREATE OR REPLACE FUNCTION authority.btag_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.btag_authority_tags($1), $2, $3, $4)
5789 $$ LANGUAGE SQL ROWS 10;
5790
5791 CREATE OR REPLACE FUNCTION authority.atag_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.atag_authority_tags($1), $2, $3, $4)
5793 $$ LANGUAGE SQL ROWS 10;
5794
5795 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 $$
5796     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4)
5797 $$ LANGUAGE SQL ROWS 10;
5798
5799 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 $$
5800     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4)
5801 $$ LANGUAGE SQL ROWS 10;
5802
5803 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 $$
5804     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4)
5805 $$ LANGUAGE SQL ROWS 10;
5806
5807
5808 CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5809     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4)
5810 $$ LANGUAGE SQL ROWS 10;
5811
5812 CREATE OR REPLACE FUNCTION authority.btag_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.btag_authority_tags($1), $2, $3, $4)
5814 $$ LANGUAGE SQL ROWS 10;
5815
5816 CREATE OR REPLACE FUNCTION authority.atag_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.atag_authority_tags($1), $2, $3, $4)
5818 $$ LANGUAGE SQL ROWS 10;
5819
5820 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 $$
5821     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4)
5822 $$ LANGUAGE SQL ROWS 10;
5823
5824 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 $$
5825     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4)
5826 $$ LANGUAGE SQL ROWS 10;
5827
5828 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 $$
5829     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4)
5830 $$ LANGUAGE SQL ROWS 10;
5831
5832
5833 CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5834     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4)
5835 $$ LANGUAGE SQL ROWS 10;
5836
5837 CREATE OR REPLACE FUNCTION authority.btag_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.btag_authority_tags($1), $2, $3, $4)
5839 $$ LANGUAGE SQL ROWS 10;
5840
5841 CREATE OR REPLACE FUNCTION authority.atag_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.atag_authority_tags($1), $2, $3, $4)
5843 $$ LANGUAGE SQL ROWS 10;
5844
5845 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 $$
5846     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4)
5847 $$ LANGUAGE SQL ROWS 10;
5848
5849 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 $$
5850     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4)
5851 $$ LANGUAGE SQL ROWS 10;
5852
5853 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 $$
5854     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4)
5855 $$ LANGUAGE SQL ROWS 10;
5856
5857
5858 CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
5859     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4)
5860 $$ LANGUAGE SQL ROWS 10;
5861
5862 CREATE OR REPLACE FUNCTION authority.btag_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.btag_authority_tags($1), $2, $3, $4)
5864 $$ LANGUAGE SQL ROWS 10;
5865
5866 CREATE OR REPLACE FUNCTION authority.atag_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.atag_authority_tags($1), $2, $3, $4)
5868 $$ LANGUAGE SQL ROWS 10;
5869
5870 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 $$
5871     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4)
5872 $$ LANGUAGE SQL ROWS 10;
5873
5874 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 $$
5875     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4)
5876 $$ LANGUAGE SQL ROWS 10;
5877
5878 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 $$
5879     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4)
5880 $$ LANGUAGE SQL ROWS 10;
5881
5882
5883
5884 -- Evergreen DB patch 0641.schema.org_unit_setting_json_check.sql
5885 --
5886 --
5887
5888 -- check whether patch can be applied
5889 SELECT evergreen.upgrade_deps_block_check('0641', :eg_version);
5890
5891 ALTER TABLE actor.org_unit_setting ADD CONSTRAINT aous_must_be_json CHECK ( is_json(value) );
5892
5893 -- Evergreen DB patch 0642.data.acq-worksheet-hold-count.sql
5894
5895 -- check whether patch can be applied
5896 SELECT evergreen.upgrade_deps_block_check('0642', :eg_version);
5897
5898 UPDATE action_trigger.event_definition SET template = 
5899 $$
5900 [%- USE date -%]
5901 [%- SET li = target; -%]
5902 <div class="wrapper">
5903     <div class="summary" style='font-size:110%; font-weight:bold;'>
5904
5905         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
5906         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
5907         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
5908         <div class="lineid">Lineitem ID: [% li.id %]</div>
5909         <div>Open Holds: [% helpers.bre_open_hold_count(li.eg_bib_id) %]</div>
5910
5911         [% IF li.distribution_formulas.size > 0 %]
5912             [% SET forms = [] %]
5913             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
5914             <div>Distribution Formulas: [% forms.join(',') %]</div>
5915         [% END %]
5916
5917         [% IF li.lineitem_notes.size > 0 %]
5918             Lineitem Notes:
5919             <ul>
5920                 [%- FOR note IN li.lineitem_notes -%]
5921                     <li>
5922                     [% IF note.alert_text %]
5923                         [% note.alert_text.code -%] 
5924                         [% IF note.value -%]
5925                             : [% note.value %]
5926                         [% END %]
5927                     [% ELSE %]
5928                         [% note.value -%] 
5929                     [% END %]
5930                     </li>
5931                 [% END %]
5932             </ul>
5933         [% END %]
5934     </div>
5935     <br/>
5936     <table>
5937         <thead>
5938             <tr>
5939                 <th>Branch</th>
5940                 <th>Barcode</th>
5941                 <th>Call Number</th>
5942                 <th>Fund</th>
5943                 <th>Shelving Location</th>
5944                 <th>Recd.</th>
5945                 <th>Notes</th>
5946             </tr>
5947         </thead>
5948         <tbody>
5949         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
5950             [% 
5951                 IF detail.eg_copy_id;
5952                     SET copy = detail.eg_copy_id;
5953                     SET cn_label = copy.call_number.label;
5954                 ELSE; 
5955                     SET copy = detail; 
5956                     SET cn_label = detail.cn_label;
5957                 END 
5958             %]
5959             <tr>
5960                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
5961                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
5962                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
5963                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
5964                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
5965                 <td style='padding:5px;'>[% copy.location.name %]</td>
5966                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
5967                 <td style='padding:5px;'>[% detail.note %]</td>
5968             </tr>
5969         [% END %]
5970         </tbody>
5971     </table>
5972 </div>
5973 $$
5974 WHERE id = 14;
5975
5976
5977 SELECT evergreen.upgrade_deps_block_check('0643', :eg_version);
5978
5979 DO $$
5980 DECLARE x TEXT;
5981 BEGIN
5982
5983     FOR x IN
5984         SELECT  marc
5985           FROM  authority.record_entry
5986           WHERE id > 0
5987                 AND NOT deleted
5988                 AND id NOT IN (SELECT DISTINCT record FROM authority.simple_heading)
5989     LOOP
5990         INSERT INTO authority.simple_heading (record,atag,value,sort_value)
5991             SELECT record, atag, value, sort_value FROM authority.simple_heading_set(x);
5992     END LOOP;
5993 END;
5994 $$;
5995
5996
5997
5998 SELECT evergreen.upgrade_deps_block_check('0644', :eg_version);
5999
6000 INSERT into config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6001 ( 'circ.holds.target_when_closed', 'circ',
6002     oils_i18n_gettext('circ.holds.target_when_closed',
6003         'Target copies for a hold even if copy''s circ lib is closed',
6004         'coust', 'label'),
6005     oils_i18n_gettext('circ.holds.target_when_closed',
6006         '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).',
6007         'coust', 'description'),
6008     'bool'),
6009 ( 'circ.holds.target_when_closed_if_at_pickup_lib', 'circ',
6010     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6011         'Target copies for a hold even if copy''s circ lib is closed IF the circ lib is the hold''s pickup lib',
6012         'coust', 'label'),
6013     oils_i18n_gettext('circ.holds.target_when_closed_if_at_pickup_lib',
6014         '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.',
6015         'coust', 'description'),
6016     'bool')
6017 ;
6018
6019 -- Evergreen DB patch XXXX.data.hold-notification-cleanup-mod.sql
6020
6021 -- check whether patch can be applied
6022 SELECT evergreen.upgrade_deps_block_check('0647', :eg_version);
6023
6024 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
6025     'CreateHoldNotification',
6026     oils_i18n_gettext(
6027         'CreateHoldNotification',
6028         'Creates a hold_notification record for each notified hold',
6029         'atclean',
6030         'description'
6031     )
6032 );
6033
6034 UPDATE action_trigger.event_definition 
6035     SET 
6036         cleanup_success = 'CreateHoldNotification' 
6037     WHERE 
6038         id = 5 -- stock hold-ready email event_def
6039         AND cleanup_success IS NULL; -- don't clobber any existing cleanup mod
6040
6041 -- Evergreen DB patch XXXX.schema.unnest-hold-permit-upgrade-script-repair.sql
6042 --
6043 -- This patch makes no changes to the baseline schema and is 
6044 -- only meant to repair a previous upgrade script.
6045 --
6046
6047 -- check whether patch can be applied
6048 SELECT evergreen.upgrade_deps_block_check('0651', :eg_version);
6049
6050 --Removed dupe action.hold_request_permit_test
6051
6052 -- Evergreen DB patch XXXX.data.vandelay-queue-bib-bucket-type.sql
6053 --
6054
6055 -- check whether patch can be applied
6056 SELECT evergreen.upgrade_deps_block_check('0652', :eg_version);
6057
6058 INSERT INTO container.biblio_record_entry_bucket_type (code, label) VALUES (
6059     'vandelay_queue',
6060     oils_i18n_gettext('vandelay_queue', 'Vandelay Queue', 'cbrebt', 'label')
6061 );
6062
6063 -- Evergreen DB patch XXXX.schema.unapi-indb-optional-org.sql
6064
6065 -- check whether patch can be applied
6066 SELECT evergreen.upgrade_deps_block_check('0653', :eg_version);
6067
6068 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;
6069
6070 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$
6071 DECLARE
6072     layout          unapi.bre_output_layout%ROWTYPE;
6073     transform       config.xml_transform%ROWTYPE;
6074     item_format     TEXT;
6075     tmp_xml         TEXT;
6076     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
6077     ouid            INT;
6078     element_list    TEXT[];
6079 BEGIN
6080
6081     IF org = '-' OR org IS NULL THEN
6082         SELECT shortname INTO org FROM evergreen.org_top();
6083     END IF;
6084
6085     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6086     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6087
6088     IF layout.name IS NULL THEN
6089         RETURN NULL::XML;
6090     END IF;
6091
6092     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
6093     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
6094
6095     -- Gather the bib xml
6096     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
6097
6098     IF layout.title_element IS NOT NULL THEN
6099         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;
6100     END IF;
6101
6102     IF layout.description_element IS NOT NULL THEN
6103         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;
6104     END IF;
6105
6106     IF layout.creator_element IS NOT NULL THEN
6107         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;
6108     END IF;
6109
6110     IF layout.update_ts_element IS NOT NULL THEN
6111         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;
6112     END IF;
6113
6114     IF unapi_url IS NOT NULL THEN
6115         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;
6116     END IF;
6117
6118     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
6119
6120     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
6121     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
6122         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;
6123     END LOOP;
6124
6125     RETURN tmp_xml::XML;
6126 END;
6127 $F$ LANGUAGE PLPGSQL;
6128
6129 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$
6130 DECLARE
6131     me      biblio.record_entry%ROWTYPE;
6132     layout  unapi.bre_output_layout%ROWTYPE;
6133     xfrm    config.xml_transform%ROWTYPE;
6134     ouid    INT;
6135     tmp_xml TEXT;
6136     top_el  TEXT;
6137     output  XML;
6138     hxml    XML;
6139     axml    XML;
6140 BEGIN
6141
6142     IF org = '-' OR org IS NULL THEN
6143         SELECT shortname INTO org FROM evergreen.org_top();
6144     END IF;
6145
6146     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
6147
6148     IF ouid IS NULL THEN
6149         RETURN NULL::XML;
6150     END IF;
6151
6152     IF format = 'holdings_xml' THEN -- the special case
6153         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
6154         RETURN output;
6155     END IF;
6156
6157     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
6158
6159     IF layout.name IS NULL THEN
6160         RETURN NULL::XML;
6161     END IF;
6162
6163     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
6164
6165     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
6166
6167     -- grab SVF if we need them
6168     IF ('mra' = ANY (includes)) THEN 
6169         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
6170     ELSE
6171         axml := NULL::XML;
6172     END IF;
6173
6174     -- grab hodlings if we need them
6175     IF ('holdings_xml' = ANY (includes)) THEN 
6176         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns);
6177     ELSE
6178         hxml := NULL::XML;
6179     END IF;
6180
6181
6182     -- generate our item node
6183
6184
6185     IF format = 'marcxml' THEN
6186         tmp_xml := me.marc;
6187         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
6188            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
6189         END IF; 
6190     ELSE
6191         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
6192     END IF;
6193
6194     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
6195
6196     IF axml IS NOT NULL THEN 
6197         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
6198     END IF;
6199
6200     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
6201         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
6202     END IF;
6203
6204     IF ('bre.unapi' = ANY (includes)) THEN 
6205         output := REGEXP_REPLACE(
6206             tmp_xml,
6207             '</' || top_el || '>(.*?)',
6208             XMLELEMENT(
6209                 name abbr,
6210                 XMLATTRIBUTES(
6211                     'http://www.w3.org/1999/xhtml' AS xmlns,
6212                     'unapi-id' AS class,
6213                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
6214                 )
6215             )::TEXT || '</' || top_el || E'>\\1'
6216         );
6217     ELSE
6218         output := tmp_xml;
6219     END IF;
6220
6221     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
6222     RETURN output;
6223 END;
6224 $F$ LANGUAGE PLPGSQL;
6225
6226
6227
6228
6229 SELECT evergreen.upgrade_deps_block_check('0654', :eg_version);
6230
6231 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6232  ( 514, 'UPDATE_PATRON_ACTIVE_CARD', oils_i18n_gettext( 514,
6233     'Allows a user to manually adjust a patron''s active cards', 'ppl', 'description')),
6234  ( 515, 'UPDATE_PATRON_PRIMARY_CARD', oils_i18n_gettext( 515,
6235     'Allows a user to manually adjust a patron''s primary card', 'ppl', 'description'));
6236
6237 -- Evergreen DB patch 0655.config.bib_source.can_have_copies.sql
6238 --
6239 -- This column introduces the ability to prevent bib records associated
6240 -- with specific bib sources from being able to have volumes or MFHD
6241 -- records attached to them.
6242 --
6243
6244 -- check whether patch can be applied
6245 SELECT evergreen.upgrade_deps_block_check('0655', :eg_version);
6246
6247 ALTER TABLE config.bib_source
6248 ADD COLUMN can_have_copies BOOL NOT NULL DEFAULT TRUE;
6249
6250 -- Evergreen DB patch XXXX.LP893315_schema.function.filter_deleted_acns_from_unapi.holdings_xml.sql
6251 --
6252 -- Prevent deleted call numbers from hiding active call numbers / copies / URIs
6253 --
6254
6255 -- check whether patch can be applied
6256 SELECT evergreen.upgrade_deps_block_check('0656', :eg_version);
6257
6258 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$
6259      SELECT  XMLELEMENT(
6260                  name holdings,
6261                  XMLATTRIBUTES(
6262                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
6263                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
6264                  ),
6265                  XMLELEMENT(
6266                      name counts,
6267                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
6268                          SELECT  XMLELEMENT(
6269                                      name count,
6270                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6271                                  )::text
6272                            FROM  asset.opac_ou_record_copy_count($2,  $1)
6273                                      UNION
6274                          SELECT  XMLELEMENT(
6275                                      name count,
6276                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
6277                                  )::text
6278                            FROM  asset.staff_ou_record_copy_count($2, $1)
6279                                      ORDER BY 1
6280                      )x)
6281                  ),
6282                  CASE 
6283                      WHEN ('bmp' = ANY ($5)) THEN
6284                         XMLELEMENT(
6285                             name monograph_parts,
6286                             (SELECT XMLAGG(bmp) FROM (
6287                                 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)
6288                                   FROM  biblio.monograph_part
6289                                   WHERE record = $1
6290                             )x)
6291                         )
6292                      ELSE NULL
6293                  END,
6294                  XMLELEMENT(
6295                      name volumes,
6296                      (SELECT XMLAGG(acn) FROM (
6297                         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)
6298                           FROM  asset.call_number acn
6299                           WHERE acn.record = $1
6300                                 AND acn.deleted IS FALSE
6301                                 AND EXISTS (
6302                                     SELECT  1
6303                                       FROM  asset.copy acp
6304                                             JOIN actor.org_unit_descendants(
6305                                                 $2,
6306                                                 (COALESCE(
6307                                                     $4,
6308                                                     (SELECT aout.depth
6309                                                       FROM  actor.org_unit_type aout
6310                                                             JOIN actor.org_unit aou ON (aou.ou_type = aout.id AND aou.id = $2)
6311                                                     )
6312                                                 ))
6313                                             ) aoud ON (acp.circ_lib = aoud.id)
6314                                       LIMIT 1
6315                                )
6316                           ORDER BY label_sortkey
6317                           LIMIT $6
6318                           OFFSET $7
6319                      )x)
6320                  ),
6321                  CASE WHEN ('ssub' = ANY ($5)) THEN 
6322                      XMLELEMENT(
6323                          name subscriptions,
6324                          (SELECT XMLAGG(ssub) FROM (
6325                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6326                               FROM  serial.subscription
6327                               WHERE record_entry = $1
6328                         )x)
6329                      )
6330                  ELSE NULL END,
6331                  CASE WHEN ('acp' = ANY ($5)) THEN 
6332                      XMLELEMENT(
6333                          name foreign_copies,
6334                          (SELECT XMLAGG(acp) FROM (
6335                             SELECT  unapi.acp(p.target_copy,'xml','copy','{}'::TEXT[], $3, $4, $6, $7, FALSE)
6336                               FROM  biblio.peer_bib_copy_map p
6337                                     JOIN asset.copy c ON (p.target_copy = c.id)
6338                               WHERE NOT c.deleted AND peer_record = $1
6339                         )x)
6340                      )
6341                  ELSE NULL END
6342              );
6343 $F$ LANGUAGE SQL;
6344
6345 -- Evergreen DB patch 0657.schema.address-alert.sql
6346 --
6347
6348 -- check whether patch can be applied
6349 SELECT evergreen.upgrade_deps_block_check('0657', :eg_version);
6350
6351 CREATE TABLE actor.address_alert (
6352     id              SERIAL  PRIMARY KEY,
6353     owner           INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
6354     active          BOOL    NOT NULL DEFAULT TRUE,
6355     match_all       BOOL    NOT NULL DEFAULT TRUE,
6356     alert_message   TEXT    NOT NULL,
6357     street1         TEXT,
6358     street2         TEXT,
6359     city            TEXT,
6360     county          TEXT,
6361     state           TEXT,
6362     country         TEXT,
6363     post_code       TEXT,
6364     mailing_address BOOL    NOT NULL DEFAULT FALSE,
6365     billing_address BOOL    NOT NULL DEFAULT FALSE
6366 );
6367
6368 CREATE OR REPLACE FUNCTION actor.address_alert_matches (
6369         org_unit INT, 
6370         street1 TEXT, 
6371         street2 TEXT, 
6372         city TEXT, 
6373         county TEXT, 
6374         state TEXT, 
6375         country TEXT, 
6376         post_code TEXT,
6377         mailing_address BOOL DEFAULT FALSE,
6378         billing_address BOOL DEFAULT FALSE
6379     ) RETURNS SETOF actor.address_alert AS $$
6380
6381 SELECT *
6382 FROM actor.address_alert
6383 WHERE
6384     active
6385     AND owner IN (SELECT id FROM actor.org_unit_ancestors($1)) 
6386     AND (
6387         (NOT mailing_address AND NOT billing_address)
6388         OR (mailing_address AND $9)
6389         OR (billing_address AND $10)
6390     )
6391     AND (
6392             (
6393                 match_all
6394                 AND COALESCE($2, '') ~* COALESCE(street1,   '.*')
6395                 AND COALESCE($3, '') ~* COALESCE(street2,   '.*')
6396                 AND COALESCE($4, '') ~* COALESCE(city,      '.*')
6397                 AND COALESCE($5, '') ~* COALESCE(county,    '.*')
6398                 AND COALESCE($6, '') ~* COALESCE(state,     '.*')
6399                 AND COALESCE($7, '') ~* COALESCE(country,   '.*')
6400                 AND COALESCE($8, '') ~* COALESCE(post_code, '.*')
6401             ) OR (
6402                 NOT match_all 
6403                 AND (  
6404                        $2 ~* street1
6405                     OR $3 ~* street2
6406                     OR $4 ~* city
6407                     OR $5 ~* county
6408                     OR $6 ~* state
6409                     OR $7 ~* country
6410                     OR $8 ~* post_code
6411                 )
6412             )
6413         )
6414     ORDER BY actor.org_unit_proximity(owner, $1)
6415 $$ LANGUAGE SQL;
6416
6417
6418 /* UNDO
6419 DROP FUNCTION actor.address_alert_matches(INT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, BOOL, BOOL);
6420 DROP TABLE actor.address_alert;
6421 */
6422 -- Evergreen DB patch 0659.add_create_report_perms.sql
6423 --
6424 -- Add a permission to control the ability to create report templates
6425 --
6426
6427 -- check whether patch can be applied
6428 SELECT evergreen.upgrade_deps_block_check('0659', :eg_version);
6429
6430 -- FIXME: add/check SQL statements to perform the upgrade
6431 INSERT INTO permission.perm_list ( id, code, description ) VALUES
6432  ( 516, 'CREATE_REPORT_TEMPLATE', oils_i18n_gettext( 516,
6433     'Allows a user to create report templates', 'ppl', 'description' ));
6434
6435 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
6436     SELECT grp, 516, depth, grantable
6437         FROM permission.grp_perm_map
6438         WHERE perm = (
6439             SELECT id
6440                 FROM permission.perm_list
6441                 WHERE code = 'RUN_REPORTS'
6442         );
6443
6444
6445
6446 SELECT evergreen.upgrade_deps_block_check('0660', :eg_version);
6447
6448 UPDATE action_trigger.event_definition SET template = $$
6449 [%-
6450 # target is the bookbag itself. The 'items' variable does not need to be in
6451 # the environment because a special reactor will take care of filling it in.
6452
6453 FOR item IN items;
6454     bibxml = helpers.unapi_bre(item.target_biblio_record_entry, {flesh => '{mra}'});
6455     title = "";
6456     FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
6457         title = title _ part.textContent;
6458     END;
6459     author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
6460     item_type = bibxml.findnodes('//*[local-name()="attributes"]/*[local-name()="field"][@name="item_type"]').getAttribute('coded-value');
6461
6462     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";
6463 END -%]
6464 $$
6465 WHERE reactor = 'ContainerCSV';
6466
6467 -- Evergreen DB patch 0661.data.yaous-opac-tag-circed-items.sql
6468 --
6469 -- Add org unit setting that enables users who have opted in to
6470 -- tracking their circulation history to see which items they
6471 -- have previously checked out in search results.
6472 --
6473
6474 -- check whether patch can be applied
6475 SELECT evergreen.upgrade_deps_block_check('0661', :eg_version);
6476
6477 INSERT into config.org_unit_setting_type 
6478     (name, grp, label, description, datatype) 
6479     VALUES ( 
6480         'opac.search.tag_circulated_items', 
6481         'opac',
6482         oils_i18n_gettext(
6483             'opac.search.tag_circulated_items',
6484             'Tag Circulated Items in Results',
6485             'coust', 
6486             'label'
6487         ),
6488         oils_i18n_gettext(
6489             'opac.search.tag_circulated_items',
6490             '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',
6491             'coust', 
6492             'description'
6493         ),
6494         'bool'
6495     );
6496
6497
6498 -- Evergreen DB patch 0662.schema.coded-value-map-index-normalizer.sql
6499 --
6500
6501 -- check whether patch can be applied
6502 SELECT evergreen.upgrade_deps_block_check('0662', :eg_version);
6503
6504 -- create the normalizer
6505 CREATE OR REPLACE FUNCTION evergreen.coded_value_map_normalizer( input TEXT, ctype TEXT ) 
6506     RETURNS TEXT AS $F$
6507         SELECT COALESCE(value,$1) 
6508             FROM config.coded_value_map 
6509             WHERE ctype = $2 AND code = $1;
6510 $F$ LANGUAGE SQL;
6511
6512 -- register the normalizer
6513 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
6514     'Coded Value Map Normalizer', 
6515     'Applies coded_value_map mapping of values',
6516     'coded_value_map_normalizer', 
6517     1
6518 );
6519
6520 -- Evergreen DB patch 0663.schema.archive_circ_stat_cats.sql
6521 --
6522 -- Enables users to set copy and patron stat cats to be archivable
6523 -- for the purposes of statistics even after the circs are aged.
6524 --
6525
6526 -- check whether patch can be applied
6527 SELECT evergreen.upgrade_deps_block_check('0663', :eg_version);
6528
6529 -- New tables
6530
6531 CREATE TABLE action.archive_actor_stat_cat (
6532     id          BIGSERIAL   PRIMARY KEY,
6533     xact        BIGINT      NOT NULL,
6534     stat_cat    INT         NOT NULL,
6535     value       TEXT        NOT NULL
6536 );
6537
6538 CREATE TABLE action.archive_asset_stat_cat (
6539     id          BIGSERIAL   PRIMARY KEY,
6540     xact        BIGINT      NOT NULL,
6541     stat_cat    INT         NOT NULL,
6542     value       TEXT        NOT NULL
6543 );
6544
6545 -- Add columns to existing tables
6546
6547 -- Archive Flag Columns
6548 ALTER TABLE actor.stat_cat
6549     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6550 ALTER TABLE asset.stat_cat
6551     ADD COLUMN checkout_archive BOOL NOT NULL DEFAULT FALSE;
6552
6553 -- Circulation copy column
6554 ALTER TABLE action.circulation
6555     ADD COLUMN copy_location INT NULL REFERENCES asset.copy_location(id) DEFERRABLE INITIALLY DEFERRED;
6556
6557 -- Create trigger function to auto-fill the copy_location field
6558 CREATE OR REPLACE FUNCTION action.fill_circ_copy_location () RETURNS TRIGGER AS $$
6559 BEGIN
6560     SELECT INTO NEW.copy_location location FROM asset.copy WHERE id = NEW.target_copy;
6561     RETURN NEW;
6562 END;
6563 $$ LANGUAGE PLPGSQL;
6564
6565 -- Create trigger function to auto-archive stat cat entries
6566 CREATE OR REPLACE FUNCTION action.archive_stat_cats () RETURNS TRIGGER AS $$
6567 BEGIN
6568     INSERT INTO action.archive_actor_stat_cat(xact, stat_cat, value)
6569         SELECT NEW.id, asceum.stat_cat, asceum.stat_cat_entry
6570         FROM actor.stat_cat_entry_usr_map asceum
6571              JOIN actor.stat_cat sc ON asceum.stat_cat = sc.id
6572         WHERE NEW.usr = asceum.target_usr AND sc.checkout_archive;
6573     INSERT INTO action.archive_asset_stat_cat(xact, stat_cat, value)
6574         SELECT NEW.id, ascecm.stat_cat, asce.value
6575         FROM asset.stat_cat_entry_copy_map ascecm
6576              JOIN asset.stat_cat sc ON ascecm.stat_cat = sc.id
6577              JOIN asset.stat_cat_entry asce ON ascecm.stat_cat_entry = asce.id
6578         WHERE NEW.target_copy = ascecm.owning_copy AND sc.checkout_archive;
6579     RETURN NULL;
6580 END;
6581 $$ LANGUAGE PLPGSQL;
6582
6583 -- Apply triggers
6584 CREATE TRIGGER fill_circ_copy_location_tgr BEFORE INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.fill_circ_copy_location();
6585 CREATE TRIGGER archive_stat_cats_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.archive_stat_cats();
6586
6587 -- Ensure all triggers are disabled for speedy updates!
6588 ALTER TABLE action.circulation DISABLE TRIGGER ALL;
6589
6590 -- Update view to use circ's copy_location field instead of the copy's current copy_location field
6591 CREATE OR REPLACE VIEW action.all_circulation AS
6592     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6593         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6594         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
6595         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6596         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6597         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6598       FROM  action.aged_circulation
6599             UNION ALL
6600     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,
6601         cp.call_number AS copy_call_number, circ.copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
6602         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6603         circ.checkin_lib, circ.renewal_remaining, circ.grace_period, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6604         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6605         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6606         circ.parent_circ
6607       FROM  action.circulation circ
6608         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6609         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6610         JOIN actor.usr p ON (circ.usr = p.id)
6611         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6612         LEFT JOIN actor.usr_address b ON (p.billing_address = b.id);
6613
6614 -- Update action.circulation with real copy_location numbers instead of all NULL
6615 DO $$BEGIN RAISE WARNING 'We are about to do an update on every row in action.circulation. This may take a while. %', timeofday(); END;$$;
6616 UPDATE action.circulation circ SET copy_location = ac.location FROM asset.copy ac WHERE ac.id = circ.target_copy;
6617
6618 -- Set not null/default on new column, re-enable triggers
6619 ALTER TABLE action.circulation
6620     ALTER COLUMN copy_location SET NOT NULL,
6621     ALTER COLUMN copy_location SET DEFAULT 1,
6622     ENABLE TRIGGER ALL;
6623
6624 -- Evergreen DB patch 0664.schema.hold-current-shelf-lib.sql
6625 --
6626 --
6627
6628
6629 -- check whether patch can be applied
6630 SELECT evergreen.upgrade_deps_block_check('0664', :eg_version);
6631
6632 -- add the new column
6633 ALTER TABLE action.hold_request ADD COLUMN current_shelf_lib 
6634     INT REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED;
6635
6636 -- Add some others before the UPDATE we are about to do breaks our ability to add columns
6637 -- But we need this table first.
6638 CREATE TABLE config.sms_carrier (
6639     id              SERIAL PRIMARY KEY,
6640     region          TEXT,
6641     name            TEXT,
6642     email_gateway   TEXT,
6643     active          BOOLEAN DEFAULT TRUE
6644 );
6645
6646 ALTER TABLE action.hold_request ADD COLUMN sms_notify TEXT;
6647 ALTER TABLE action.hold_request ADD COLUMN sms_carrier INT REFERENCES config.sms_carrier (id);
6648 ALTER TABLE action.hold_request ADD CONSTRAINT sms_check CHECK (
6649     sms_notify IS NULL
6650     OR sms_carrier IS NOT NULL -- and implied sms_notify IS NOT NULL
6651 );
6652
6653
6654
6655 -- set the value for current_shelf_lib on existing shelved holds
6656 UPDATE action.hold_request
6657     SET current_shelf_lib = pickup_lib
6658     FROM asset.copy
6659     WHERE 
6660             action.hold_request.shelf_time IS NOT NULL 
6661         AND action.hold_request.capture_time IS NOT NULL
6662         AND action.hold_request.current_copy IS NOT NULL
6663         AND action.hold_request.fulfillment_time IS NULL
6664         AND action.hold_request.cancel_time IS NULL
6665         AND asset.copy.id = action.hold_request.current_copy
6666         AND asset.copy.status = 8; -- on holds shelf
6667
6668
6669 SELECT evergreen.upgrade_deps_block_check('0666', :eg_version);
6670
6671 -- 950.data.seed-values.sql
6672 INSERT INTO config.settings_group (name, label) VALUES
6673     (
6674         'sms',
6675         oils_i18n_gettext(
6676             'sms',
6677             'SMS Text Messages',
6678             'csg',
6679             'label'
6680         )
6681     )
6682 ;
6683
6684 INSERT INTO config.org_unit_setting_type (name, grp, label, description, datatype) VALUES
6685     (
6686         'sms.enable',
6687         'sms',
6688         oils_i18n_gettext(
6689             'sms.enable',
6690             'Enable features that send SMS text messages.',
6691             'coust',
6692             'label'
6693         ),
6694         oils_i18n_gettext(
6695             'sms.enable',
6696             '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.',
6697             'coust',
6698             'description'
6699         ),
6700         'bool'
6701     )
6702     ,(
6703         'sms.disable_authentication_requirement.callnumbers',
6704         'sms',
6705         oils_i18n_gettext(
6706             'sms.disable_authentication_requirement.callnumbers',
6707             'Disable auth requirement for texting call numbers.',
6708             'coust',
6709             'label'
6710         ),
6711         oils_i18n_gettext(
6712             'sms.disable_authentication_requirement.callnumbers',
6713             'Disable authentication requirement for sending call number information via SMS from the OPAC.',
6714             'coust',
6715             'description'
6716         ),
6717         'bool'
6718     )
6719 ;
6720
6721 -- 090.schema.action.sql
6722 -- 950.data.seed-values.sql
6723 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype,fm_class) VALUES (
6724     'opac.default_sms_carrier',
6725     'sms',
6726     TRUE,
6727     oils_i18n_gettext(
6728         'opac.default_sms_carrier',
6729         'Default SMS/Text Carrier',
6730         'cust',
6731         'label'
6732     ),
6733     oils_i18n_gettext(
6734         'opac.default_sms_carrier',
6735         'Default SMS/Text Carrier',
6736         'cust',
6737         'description'
6738     ),
6739     'link',
6740     'csc'
6741 );
6742
6743 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6744     'opac.default_sms_notify',
6745     'sms',
6746     TRUE,
6747     oils_i18n_gettext(
6748         'opac.default_sms_notify',
6749         'Default SMS/Text Number',
6750         'cust',
6751         'label'
6752     ),
6753     oils_i18n_gettext(
6754         'opac.default_sms_notify',
6755         'Default SMS/Text Number',
6756         'cust',
6757         'description'
6758     ),
6759     'string'
6760 );
6761
6762 INSERT INTO config.usr_setting_type (name,grp,opac_visible,label,description,datatype) VALUES (
6763     'opac.default_phone',
6764     'opac',
6765     TRUE,
6766     oils_i18n_gettext(
6767         'opac.default_phone',
6768         'Default Phone Number',
6769         'cust',
6770         'label'
6771     ),
6772     oils_i18n_gettext(
6773         'opac.default_phone',
6774         'Default Phone Number',
6775         'cust',
6776         'description'
6777     ),
6778     'string'
6779 );
6780
6781 SELECT setval( 'config.sms_carrier_id_seq', 1000 );
6782 INSERT INTO config.sms_carrier VALUES
6783
6784     -- Testing
6785     (
6786         1,
6787         oils_i18n_gettext(
6788             1,
6789             'Local',
6790             'csc',
6791             'region'
6792         ),
6793         oils_i18n_gettext(
6794             1,
6795             'Test Carrier',
6796             'csc',
6797             'name'
6798         ),
6799         'opensrf+$number@localhost',
6800         FALSE
6801     ),
6802
6803     -- Canada & USA
6804     (
6805         2,
6806         oils_i18n_gettext(
6807             2,
6808             'Canada & USA',
6809             'csc',
6810             'region'
6811         ),
6812         oils_i18n_gettext(
6813             2,
6814             'Rogers Wireless',
6815             'csc',
6816             'name'
6817         ),
6818         '$number@pcs.rogers.com',
6819         TRUE
6820     ),
6821     (
6822         3,
6823         oils_i18n_gettext(
6824             3,
6825             'Canada & USA',
6826             'csc',
6827             'region'
6828         ),
6829         oils_i18n_gettext(
6830             3,
6831             'Rogers Wireless (Alternate)',
6832             'csc',
6833             'name'
6834         ),
6835         '1$number@mms.rogers.com',
6836         TRUE
6837     ),
6838     (
6839         4,
6840         oils_i18n_gettext(
6841             4,
6842             'Canada & USA',
6843             'csc',
6844             'region'
6845         ),
6846         oils_i18n_gettext(
6847             4,
6848             'Telus Mobility',
6849             'csc',
6850             'name'
6851         ),
6852         '$number@msg.telus.com',
6853         TRUE
6854     ),
6855
6856     -- Canada
6857     (
6858         5,
6859         oils_i18n_gettext(
6860             5,
6861             'Canada',
6862             'csc',
6863             'region'
6864         ),
6865         oils_i18n_gettext(
6866             5,
6867             'Koodo Mobile',
6868             'csc',
6869             'name'
6870         ),
6871         '$number@msg.telus.com',
6872         TRUE
6873     ),
6874     (
6875         6,
6876         oils_i18n_gettext(
6877             6,
6878             'Canada',
6879             'csc',
6880             'region'
6881         ),
6882         oils_i18n_gettext(
6883             6,
6884             'Fido',
6885             'csc',
6886             'name'
6887         ),
6888         '$number@fido.ca',
6889         TRUE
6890     ),
6891     (
6892         7,
6893         oils_i18n_gettext(
6894             7,
6895             'Canada',
6896             'csc',
6897             'region'
6898         ),
6899         oils_i18n_gettext(
6900             7,
6901             'Bell Mobility & Solo Mobile',
6902             'csc',
6903             'name'
6904         ),
6905         '$number@txt.bell.ca',
6906         TRUE
6907     ),
6908     (
6909         8,
6910         oils_i18n_gettext(
6911             8,
6912             'Canada',
6913             'csc',
6914             'region'
6915         ),
6916         oils_i18n_gettext(
6917             8,
6918             'Bell Mobility & Solo Mobile (Alternate)',
6919             'csc',
6920             'name'
6921         ),
6922         '$number@txt.bellmobility.ca',
6923         TRUE
6924     ),
6925     (
6926         9,
6927         oils_i18n_gettext(
6928             9,
6929             'Canada',
6930             'csc',
6931             'region'
6932         ),
6933         oils_i18n_gettext(
6934             9,
6935             'Aliant',
6936             'csc',
6937             'name'
6938         ),
6939         '$number@sms.wirefree.informe.ca',
6940         TRUE
6941     ),
6942     (
6943         10,
6944         oils_i18n_gettext(
6945             10,
6946             'Canada',
6947             'csc',
6948             'region'
6949         ),
6950         oils_i18n_gettext(
6951             10,
6952             'PC Telecom',
6953             'csc',
6954             'name'
6955         ),
6956         '$number@mobiletxt.ca',
6957         TRUE
6958     ),
6959     (
6960         11,
6961         oils_i18n_gettext(
6962             11,
6963             'Canada',
6964             'csc',
6965             'region'
6966         ),
6967         oils_i18n_gettext(
6968             11,
6969             'SaskTel',
6970             'csc',
6971             'name'
6972         ),
6973         '$number@sms.sasktel.com',
6974         TRUE
6975     ),
6976     (
6977         12,
6978         oils_i18n_gettext(
6979             12,
6980             'Canada',
6981             'csc',
6982             'region'
6983         ),
6984         oils_i18n_gettext(
6985             12,
6986             'MTS Mobility',
6987             'csc',
6988             'name'
6989         ),
6990         '$number@text.mtsmobility.com',
6991         TRUE
6992     ),
6993     (
6994         13,
6995         oils_i18n_gettext(
6996             13,
6997             'Canada',
6998             'csc',
6999             'region'
7000         ),
7001         oils_i18n_gettext(
7002             13,
7003             'Virgin Mobile',
7004             'csc',
7005             'name'
7006         ),
7007         '$number@vmobile.ca',
7008         TRUE
7009     ),
7010
7011     -- International
7012     (
7013         14,
7014         oils_i18n_gettext(
7015             14,
7016             'International',
7017             'csc',
7018             'region'
7019         ),
7020         oils_i18n_gettext(
7021             14,
7022             'Iridium',
7023             'csc',
7024             'name'
7025         ),
7026         '$number@msg.iridium.com',
7027         TRUE
7028     ),
7029     (
7030         15,
7031         oils_i18n_gettext(
7032             15,
7033             'International',
7034             'csc',
7035             'region'
7036         ),
7037         oils_i18n_gettext(
7038             15,
7039             'Globalstar',
7040             'csc',
7041             'name'
7042         ),
7043         '$number@msg.globalstarusa.com',
7044         TRUE
7045     ),
7046     (
7047         16,
7048         oils_i18n_gettext(
7049             16,
7050             'International',
7051             'csc',
7052             'region'
7053         ),
7054         oils_i18n_gettext(
7055             16,
7056             'Bulletin.net',
7057             'csc',
7058             'name'
7059         ),
7060         '$number@bulletinmessenger.net', -- International Formatted number
7061         TRUE
7062     ),
7063     (
7064         17,
7065         oils_i18n_gettext(
7066             17,
7067             'International',
7068             'csc',
7069             'region'
7070         ),
7071         oils_i18n_gettext(
7072             17,
7073             'Panacea Mobile',
7074             'csc',
7075             'name'
7076         ),
7077         '$number@api.panaceamobile.com',
7078         TRUE
7079     ),
7080
7081     -- USA
7082     (
7083         18,
7084         oils_i18n_gettext(
7085             18,
7086             'USA',
7087             'csc',
7088             'region'
7089         ),
7090         oils_i18n_gettext(
7091             18,
7092             'C Beyond',
7093             'csc',
7094             'name'
7095         ),
7096         '$number@cbeyond.sprintpcs.com',
7097         TRUE
7098     ),
7099     (
7100         19,
7101         oils_i18n_gettext(
7102             19,
7103             'Alaska, USA',
7104             'csc',
7105             'region'
7106         ),
7107         oils_i18n_gettext(
7108             19,
7109             'General Communications, Inc.',
7110             'csc',
7111             'name'
7112         ),
7113         '$number@mobile.gci.net',
7114         TRUE
7115     ),
7116     (
7117         20,
7118         oils_i18n_gettext(
7119             20,
7120             'California, USA',
7121             'csc',
7122             'region'
7123         ),
7124         oils_i18n_gettext(
7125             20,
7126             'Golden State Cellular',
7127             'csc',
7128             'name'
7129         ),
7130         '$number@gscsms.com',
7131         TRUE
7132     ),
7133     (
7134         21,
7135         oils_i18n_gettext(
7136             21,
7137             'Cincinnati, Ohio, USA',
7138             'csc',
7139             'region'
7140         ),
7141         oils_i18n_gettext(
7142             21,
7143             'Cincinnati Bell',
7144             'csc',
7145             'name'
7146         ),
7147         '$number@gocbw.com',
7148         TRUE
7149     ),
7150     (
7151         22,
7152         oils_i18n_gettext(
7153             22,
7154             'Hawaii, USA',
7155             'csc',
7156             'region'
7157         ),
7158         oils_i18n_gettext(
7159             22,
7160             'Hawaiian Telcom Wireless',
7161             'csc',
7162             'name'
7163         ),
7164         '$number@hawaii.sprintpcs.com',
7165         TRUE
7166     ),
7167     (
7168         23,
7169         oils_i18n_gettext(
7170             23,
7171             'Midwest, USA',
7172             'csc',
7173             'region'
7174         ),
7175         oils_i18n_gettext(
7176             23,
7177             'i wireless (T-Mobile)',
7178             'csc',
7179             'name'
7180         ),
7181         '$number.iws@iwspcs.net',
7182         TRUE
7183     ),
7184     (
7185         24,
7186         oils_i18n_gettext(
7187             24,
7188             'USA',
7189             'csc',
7190             'region'
7191         ),
7192         oils_i18n_gettext(
7193             24,
7194             'i-wireless (Sprint PCS)',
7195             'csc',
7196             'name'
7197         ),
7198         '$number@iwirelesshometext.com',
7199         TRUE
7200     ),
7201     (
7202         25,
7203         oils_i18n_gettext(
7204             25,
7205             'USA',
7206             'csc',
7207             'region'
7208         ),
7209         oils_i18n_gettext(
7210             25,
7211             'MetroPCS',
7212             'csc',
7213             'name'
7214         ),
7215         '$number@mymetropcs.com',
7216         TRUE
7217     ),
7218     (
7219         26,
7220         oils_i18n_gettext(
7221             26,
7222             'USA',
7223             'csc',
7224             'region'
7225         ),
7226         oils_i18n_gettext(
7227             26,
7228             'Kajeet',
7229             'csc',
7230             'name'
7231         ),
7232         '$number@mobile.kajeet.net',
7233         TRUE
7234     ),
7235     (
7236         27,
7237         oils_i18n_gettext(
7238             27,
7239             'USA',
7240             'csc',
7241             'region'
7242         ),
7243         oils_i18n_gettext(
7244             27,
7245             'Element Mobile',
7246             'csc',
7247             'name'
7248         ),
7249         '$number@SMS.elementmobile.net',
7250         TRUE
7251     ),
7252     (
7253         28,
7254         oils_i18n_gettext(
7255             28,
7256             'USA',
7257             'csc',
7258             'region'
7259         ),
7260         oils_i18n_gettext(
7261             28,
7262             'Esendex',
7263             'csc',
7264             'name'
7265         ),
7266         '$number@echoemail.net',
7267         TRUE
7268     ),
7269     (
7270         29,
7271         oils_i18n_gettext(
7272             29,
7273             'USA',
7274             'csc',
7275             'region'
7276         ),
7277         oils_i18n_gettext(
7278             29,
7279             'Boost Mobile',
7280             'csc',
7281             'name'
7282         ),
7283         '$number@myboostmobile.com',
7284         TRUE
7285     ),
7286     (
7287         30,
7288         oils_i18n_gettext(
7289             30,
7290             'USA',
7291             'csc',
7292             'region'
7293         ),
7294         oils_i18n_gettext(
7295             30,
7296             'BellSouth',
7297             'csc',
7298             'name'
7299         ),
7300         '$number@bellsouth.com',
7301         TRUE
7302     ),
7303     (
7304         31,
7305         oils_i18n_gettext(
7306             31,
7307             'USA',
7308             'csc',
7309             'region'
7310         ),
7311         oils_i18n_gettext(
7312             31,
7313             'Bluegrass Cellular',
7314             'csc',
7315             'name'
7316         ),
7317         '$number@sms.bluecell.com',
7318         TRUE
7319     ),
7320     (
7321         32,
7322         oils_i18n_gettext(
7323             32,
7324             'USA',
7325             'csc',
7326             'region'
7327         ),
7328         oils_i18n_gettext(
7329             32,
7330             'AT&T Enterprise Paging',
7331             'csc',
7332             'name'
7333         ),
7334         '$number@page.att.net',
7335         TRUE
7336     ),
7337     (
7338         33,
7339         oils_i18n_gettext(
7340             33,
7341             'USA',
7342             'csc',
7343             'region'
7344         ),
7345         oils_i18n_gettext(
7346             33,
7347             'AT&T Mobility/Wireless',
7348             'csc',
7349             'name'
7350         ),
7351         '$number@txt.att.net',
7352         TRUE
7353     ),
7354     (
7355         34,
7356         oils_i18n_gettext(
7357             34,
7358             'USA',
7359             'csc',
7360             'region'
7361         ),
7362         oils_i18n_gettext(
7363             34,
7364             'AT&T Global Smart Messaging Suite',
7365             'csc',
7366             'name'
7367         ),
7368         '$number@sms.smartmessagingsuite.com',
7369         TRUE
7370     ),
7371     (
7372         35,
7373         oils_i18n_gettext(
7374             35,
7375             'USA',
7376             'csc',
7377             'region'
7378         ),
7379         oils_i18n_gettext(
7380             35,
7381             'Alltel (Allied Wireless)',
7382             'csc',
7383             'name'
7384         ),
7385         '$number@sms.alltelwireless.com',
7386         TRUE
7387     ),
7388     (
7389         36,
7390         oils_i18n_gettext(
7391             36,
7392             'USA',
7393             'csc',
7394             'region'
7395         ),
7396         oils_i18n_gettext(
7397             36,
7398             'Alaska Communications',
7399             'csc',
7400             'name'
7401         ),
7402         '$number@msg.acsalaska.com',
7403         TRUE
7404     ),
7405     (
7406         37,
7407         oils_i18n_gettext(
7408             37,
7409             'USA',
7410             'csc',
7411             'region'
7412         ),
7413         oils_i18n_gettext(
7414             37,
7415             'Ameritech',
7416             'csc',
7417             'name'
7418         ),
7419         '$number@paging.acswireless.com',
7420         TRUE
7421     ),
7422     (
7423         38,
7424         oils_i18n_gettext(
7425             38,
7426             'USA',
7427             'csc',
7428             'region'
7429         ),
7430         oils_i18n_gettext(
7431             38,
7432             'Cingular (GoPhone prepaid)',
7433             'csc',
7434             'name'
7435         ),
7436         '$number@cingulartext.com',
7437         TRUE
7438     ),
7439     (
7440         39,
7441         oils_i18n_gettext(
7442             39,
7443             'USA',
7444             'csc',
7445             'region'
7446         ),
7447         oils_i18n_gettext(
7448             39,
7449             'Cingular (Postpaid)',
7450             'csc',
7451             'name'
7452         ),
7453         '$number@cingular.com',
7454         TRUE
7455     ),
7456     (
7457         40,
7458         oils_i18n_gettext(
7459             40,
7460             'USA',
7461             'csc',
7462             'region'
7463         ),
7464         oils_i18n_gettext(
7465             40,
7466             'Cellular One (Dobson) / O2 / Orange',
7467             'csc',
7468             'name'
7469         ),
7470         '$number@mobile.celloneusa.com',
7471         TRUE
7472     ),
7473     (
7474         41,
7475         oils_i18n_gettext(
7476             41,
7477             'USA',
7478             'csc',
7479             'region'
7480         ),
7481         oils_i18n_gettext(
7482             41,
7483             'Cellular South',
7484             'csc',
7485             'name'
7486         ),
7487         '$number@csouth1.com',
7488         TRUE
7489     ),
7490     (
7491         42,
7492         oils_i18n_gettext(
7493             42,
7494             'USA',
7495             'csc',
7496             'region'
7497         ),
7498         oils_i18n_gettext(
7499             42,
7500             'Cellcom',
7501             'csc',
7502             'name'
7503         ),
7504         '$number@cellcom.quiktxt.com',
7505         TRUE
7506     ),
7507     (
7508         43,
7509         oils_i18n_gettext(
7510             43,
7511             'USA',
7512             'csc',
7513             'region'
7514         ),
7515         oils_i18n_gettext(
7516             43,
7517             'Chariton Valley Wireless',
7518             'csc',
7519             'name'
7520         ),
7521         '$number@sms.cvalley.net',
7522         TRUE
7523     ),
7524     (
7525         44,
7526         oils_i18n_gettext(
7527             44,
7528             'USA',
7529             'csc',
7530             'region'
7531         ),
7532         oils_i18n_gettext(
7533             44,
7534             'Cricket',
7535             'csc',
7536             'name'
7537         ),
7538         '$number@sms.mycricket.com',
7539         TRUE
7540     ),
7541     (
7542         45,
7543         oils_i18n_gettext(
7544             45,
7545             'USA',
7546             'csc',
7547             'region'
7548         ),
7549         oils_i18n_gettext(
7550             45,
7551             'Cleartalk Wireless',
7552             'csc',
7553             'name'
7554         ),
7555         '$number@sms.cleartalk.us',
7556         TRUE
7557     ),
7558     (
7559         46,
7560         oils_i18n_gettext(
7561             46,
7562             'USA',
7563             'csc',
7564             'region'
7565         ),
7566         oils_i18n_gettext(
7567             46,
7568             'Edge Wireless',
7569             'csc',
7570             'name'
7571         ),
7572         '$number@sms.edgewireless.com',
7573         TRUE
7574     ),
7575     (
7576         47,
7577         oils_i18n_gettext(
7578             47,
7579             'USA',
7580             'csc',
7581             'region'
7582         ),
7583         oils_i18n_gettext(
7584             47,
7585             'Syringa Wireless',
7586             'csc',
7587             'name'
7588         ),
7589         '$number@rinasms.com',
7590         TRUE
7591     ),
7592     (
7593         48,
7594         oils_i18n_gettext(
7595             48,
7596             'USA',
7597             'csc',
7598             'region'
7599         ),
7600         oils_i18n_gettext(
7601             48,
7602             'T-Mobile',
7603             'csc',
7604             'name'
7605         ),
7606         '$number@tmomail.net',
7607         TRUE
7608     ),
7609     (
7610         49,
7611         oils_i18n_gettext(
7612             49,
7613             'USA',
7614             'csc',
7615             'region'
7616         ),
7617         oils_i18n_gettext(
7618             49,
7619             'Straight Talk / PagePlus Cellular',
7620             'csc',
7621             'name'
7622         ),
7623         '$number@vtext.com',
7624         TRUE
7625     ),
7626     (
7627         50,
7628         oils_i18n_gettext(
7629             50,
7630             'USA',
7631             'csc',
7632             'region'
7633         ),
7634         oils_i18n_gettext(
7635             50,
7636             'South Central Communications',
7637             'csc',
7638             'name'
7639         ),
7640         '$number@rinasms.com',
7641         TRUE
7642     ),
7643     (
7644         51,
7645         oils_i18n_gettext(
7646             51,
7647             'USA',
7648             'csc',
7649             'region'
7650         ),
7651         oils_i18n_gettext(
7652             51,
7653             'Simple Mobile',
7654             'csc',
7655             'name'
7656         ),
7657         '$number@smtext.com',
7658         TRUE
7659     ),
7660     (
7661         52,
7662         oils_i18n_gettext(
7663             52,
7664             'USA',
7665             'csc',
7666             'region'
7667         ),
7668         oils_i18n_gettext(
7669             52,
7670             'Sprint (PCS)',
7671             'csc',
7672             'name'
7673         ),
7674         '$number@messaging.sprintpcs.com',
7675         TRUE
7676     ),
7677     (
7678         53,
7679         oils_i18n_gettext(
7680             53,
7681             'USA',
7682             'csc',
7683             'region'
7684         ),
7685         oils_i18n_gettext(
7686             53,
7687             'Nextel',
7688             'csc',
7689             'name'
7690         ),
7691         '$number@messaging.nextel.com',
7692         TRUE
7693     ),
7694     (
7695         54,
7696         oils_i18n_gettext(
7697             54,
7698             'USA',
7699             'csc',
7700             'region'
7701         ),
7702         oils_i18n_gettext(
7703             54,
7704             'Pioneer Cellular',
7705             'csc',
7706             'name'
7707         ),
7708         '$number@zsend.com', -- nine digit number
7709         TRUE
7710     ),
7711     (
7712         55,
7713         oils_i18n_gettext(
7714             55,
7715             'USA',
7716             'csc',
7717             'region'
7718         ),
7719         oils_i18n_gettext(
7720             55,
7721             'Qwest Wireless',
7722             'csc',
7723             'name'
7724         ),
7725         '$number@qwestmp.com',
7726         TRUE
7727     ),
7728     (
7729         56,
7730         oils_i18n_gettext(
7731             56,
7732             'USA',
7733             'csc',
7734             'region'
7735         ),
7736         oils_i18n_gettext(
7737             56,
7738             'US Cellular',
7739             'csc',
7740             'name'
7741         ),
7742         '$number@email.uscc.net',
7743         TRUE
7744     ),
7745     (
7746         57,
7747         oils_i18n_gettext(
7748             57,
7749             'USA',
7750             'csc',
7751             'region'
7752         ),
7753         oils_i18n_gettext(
7754             57,
7755             'Unicel',
7756             'csc',
7757             'name'
7758         ),
7759         '$number@utext.com',
7760         TRUE
7761     ),
7762     (
7763         58,
7764         oils_i18n_gettext(
7765             58,
7766             'USA',
7767             'csc',
7768             'region'
7769         ),
7770         oils_i18n_gettext(
7771             58,
7772             'Teleflip',
7773             'csc',
7774             'name'
7775         ),
7776         '$number@teleflip.com',
7777         TRUE
7778     ),
7779     (
7780         59,
7781         oils_i18n_gettext(
7782             59,
7783             'USA',
7784             'csc',
7785             'region'
7786         ),
7787         oils_i18n_gettext(
7788             59,
7789             'Virgin Mobile',
7790             'csc',
7791             'name'
7792         ),
7793         '$number@vmobl.com',
7794         TRUE
7795     ),
7796     (
7797         60,
7798         oils_i18n_gettext(
7799             60,
7800             'USA',
7801             'csc',
7802             'region'
7803         ),
7804         oils_i18n_gettext(
7805             60,
7806             'Verizon Wireless',
7807             'csc',
7808             'name'
7809         ),
7810         '$number@vtext.com',
7811         TRUE
7812     ),
7813     (
7814         61,
7815         oils_i18n_gettext(
7816             61,
7817             'USA',
7818             'csc',
7819             'region'
7820         ),
7821         oils_i18n_gettext(
7822             61,
7823             'USA Mobility',
7824             'csc',
7825             'name'
7826         ),
7827         '$number@usamobility.net',
7828         TRUE
7829     ),
7830     (
7831         62,
7832         oils_i18n_gettext(
7833             62,
7834             'USA',
7835             'csc',
7836             'region'
7837         ),
7838         oils_i18n_gettext(
7839             62,
7840             'Viaero',
7841             'csc',
7842             'name'
7843         ),
7844         '$number@viaerosms.com',
7845         TRUE
7846     ),
7847     (
7848         63,
7849         oils_i18n_gettext(
7850             63,
7851             'USA',
7852             'csc',
7853             'region'
7854         ),
7855         oils_i18n_gettext(
7856             63,
7857             'TracFone',
7858             'csc',
7859             'name'
7860         ),
7861         '$number@mmst5.tracfone.com',
7862         TRUE
7863     ),
7864     (
7865         64,
7866         oils_i18n_gettext(
7867             64,
7868             'USA',
7869             'csc',
7870             'region'
7871         ),
7872         oils_i18n_gettext(
7873             64,
7874             'Centennial Wireless',
7875             'csc',
7876             'name'
7877         ),
7878         '$number@cwemail.com',
7879         TRUE
7880     ),
7881
7882     -- South Korea and USA
7883     (
7884         65,
7885         oils_i18n_gettext(
7886             65,
7887             'South Korea and USA',
7888             'csc',
7889             'region'
7890         ),
7891         oils_i18n_gettext(
7892             65,
7893             'Helio',
7894             'csc',
7895             'name'
7896         ),
7897         '$number@myhelio.com',
7898         TRUE
7899     )
7900 ;
7901
7902 INSERT INTO permission.perm_list ( id, code, description ) VALUES
7903     (
7904         519,
7905         'ADMIN_SMS_CARRIER',
7906         oils_i18n_gettext(
7907             519,
7908             'Allows a user to add/create/delete SMS Carrier entries.',
7909             'ppl',
7910             'description'
7911         )
7912     )
7913 ;
7914
7915 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
7916     SELECT
7917         pgt.id, perm.id, aout.depth, TRUE
7918     FROM
7919         permission.grp_tree pgt,
7920         permission.perm_list perm,
7921         actor.org_unit_type aout
7922     WHERE
7923         pgt.name = 'Global Administrator' AND
7924         aout.name = 'Consortium' AND
7925         perm.code = 'ADMIN_SMS_CARRIER';
7926
7927 INSERT INTO action_trigger.reactor (
7928     module,
7929     description
7930 ) VALUES (
7931     'SendSMS',
7932     'Send an SMS text message based on a user-defined template'
7933 );
7934
7935 INSERT INTO action_trigger.event_definition (
7936     active,
7937     owner,
7938     name,
7939     hook,
7940     validator,
7941     reactor,
7942     cleanup_success,
7943     delay,
7944     delay_field,
7945     group_field,
7946     template
7947 ) VALUES (
7948     true,
7949     1, -- admin
7950     'Hold Ready for Pickup SMS Notification',
7951     'hold.available',
7952     'HoldIsAvailable',
7953     'SendSMS',
7954     'CreateHoldNotification',
7955     '00:30:00',
7956     'shelf_time',
7957     'sms_notify',
7958     '[%- USE date -%]
7959 [%- user = target.0.usr -%]
7960 From: [%- params.sender_email || default_sender %]
7961 To: [%- params.recipient_email || helpers.get_sms_gateway_email(target.0.sms_carrier,target.0.sms_notify) %]
7962 Subject: [% target.size %] hold(s) ready
7963
7964 [% FOR hold IN target %][%-
7965   bibxml = helpers.xml_doc( hold.current_copy.call_number.record.marc );
7966   title = "";
7967   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a"]'');
7968     title = title _ part.textContent;
7969   END;
7970   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
7971 %][% hold.usr.first_given_name %]:[% title %] @ [% hold.pickup_lib.name %]
7972 [% END %]
7973 '
7974 );
7975
7976 INSERT INTO action_trigger.environment (
7977     event_def,
7978     path
7979 ) VALUES (
7980     currval('action_trigger.event_definition_id_seq'),
7981     'current_copy.call_number.record.simple_record'
7982 ), (
7983     currval('action_trigger.event_definition_id_seq'),
7984     'usr'
7985 ), (
7986     currval('action_trigger.event_definition_id_seq'),
7987     'pickup_lib.billing_address'
7988 );
7989
7990 INSERT INTO action_trigger.hook(
7991     key,
7992     core_type,
7993     description,
7994     passive
7995 ) VALUES (
7996     'acn.format.sms_text',
7997     'acn',
7998     oils_i18n_gettext(
7999         'acn.format.sms_text',
8000         'A text message has been requested for a call number.',
8001         'ath',
8002         'description'
8003     ),
8004     FALSE
8005 );
8006
8007 INSERT INTO action_trigger.event_definition (
8008     active,
8009     owner,
8010     name,
8011     hook,
8012     validator,
8013     reactor,
8014     template
8015 ) VALUES (
8016     true,
8017     1, -- admin
8018     'SMS Call Number',
8019     'acn.format.sms_text',
8020     'NOOP_True',
8021     'SendSMS',
8022     '[%- USE date -%]
8023 From: [%- params.sender_email || default_sender %]
8024 To: [%- params.recipient_email || helpers.get_sms_gateway_email(user_data.sms_carrier,user_data.sms_notify) %]
8025 Subject: Call Number
8026
8027 [%-
8028   bibxml = helpers.xml_doc( target.record.marc );
8029   title = "";
8030   FOR part IN bibxml.findnodes(''//*[@tag="245"]/*[@code="a" or @code="b"]'');
8031     title = title _ part.textContent;
8032   END;
8033   author = bibxml.findnodes(''//*[@tag="100"]/*[@code="a"]'').textContent;
8034 %]
8035 Call Number: [% target.label %]
8036 Location: [% helpers.get_most_populous_location( target.id ).name %]
8037 Library: [% target.owning_lib.name %]
8038 [%- IF title %]
8039 Title: [% title %]
8040 [%- END %]
8041 [%- IF author %]
8042 Author: [% author %]
8043 [%- END %]
8044 '
8045 );
8046
8047 INSERT INTO action_trigger.environment (
8048     event_def,
8049     path
8050 ) VALUES (
8051     currval('action_trigger.event_definition_id_seq'),
8052     'record.simple_record'
8053 ), (
8054     currval('action_trigger.event_definition_id_seq'),
8055     'owning_lib.billing_address'
8056 );
8057
8058
8059 -- 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';
8060
8061
8062 SELECT evergreen.upgrade_deps_block_check('0667', :eg_version);
8063
8064 ALTER TABLE config.standing_penalty ADD staff_alert BOOL NOT NULL DEFAULT FALSE;
8065
8066 -- 20 is ALERT_NOTE
8067 -- for backwards compat, set all blocking penalties to alerts
8068 UPDATE config.standing_penalty SET staff_alert = TRUE 
8069     WHERE id = 20 OR block_list IS NOT NULL;
8070
8071 -- Evergreen DB patch 0668.schema.fix_indb_hold_permit.sql
8072 --
8073 -- FIXME: insert description of change, if needed
8074 --
8075
8076
8077 -- check whether patch can be applied
8078 SELECT evergreen.upgrade_deps_block_check('0668', :eg_version);
8079
8080 -- FIXME: add/check SQL statements to perform the upgrade
8081 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$
8082 DECLARE
8083     matchpoint_id        INT;
8084     user_object        actor.usr%ROWTYPE;
8085     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
8086     standing_penalty    config.standing_penalty%ROWTYPE;
8087     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
8088     transit_source        actor.org_unit%ROWTYPE;
8089     item_object        asset.copy%ROWTYPE;
8090     item_cn_object     asset.call_number%ROWTYPE;
8091     item_status_object  config.copy_status%ROWTYPE;
8092     item_location_object    asset.copy_location%ROWTYPE;
8093     ou_skip              actor.org_unit_setting%ROWTYPE;
8094     result            action.matrix_test_result;
8095     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
8096     use_active_date   TEXT;
8097     age_protect_date  TIMESTAMP WITH TIME ZONE;
8098     hold_count        INT;
8099     hold_transit_prox    INT;
8100     frozen_hold_count    INT;
8101     context_org_list    INT[];
8102     done            BOOL := FALSE;
8103 BEGIN
8104     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8105     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
8106
8107     result.success := TRUE;
8108
8109     -- Fail if we couldn't find a user
8110     IF user_object.id IS NULL THEN
8111         result.fail_part := 'no_user';
8112         result.success := FALSE;
8113         done := TRUE;
8114         RETURN NEXT result;
8115         RETURN;
8116     END IF;
8117
8118     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8119
8120     -- Fail if we couldn't find a copy
8121     IF item_object.id IS NULL THEN
8122         result.fail_part := 'no_item';
8123         result.success := FALSE;
8124         done := TRUE;
8125         RETURN NEXT result;
8126         RETURN;
8127     END IF;
8128
8129     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
8130     result.matchpoint := matchpoint_id;
8131
8132     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
8133
8134     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
8135     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
8136         result.fail_part := 'circ.holds.target_skip_me';
8137         result.success := FALSE;
8138         done := TRUE;
8139         RETURN NEXT result;
8140         RETURN;
8141     END IF;
8142
8143     -- Fail if user is barred
8144     IF user_object.barred IS TRUE THEN
8145         result.fail_part := 'actor.usr.barred';
8146         result.success := FALSE;
8147         done := TRUE;
8148         RETURN NEXT result;
8149         RETURN;
8150     END IF;
8151
8152     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8153     SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status;
8154     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8155
8156     -- Fail if we couldn't find any matchpoint (requires a default)
8157     IF matchpoint_id IS NULL THEN
8158         result.fail_part := 'no_matchpoint';
8159         result.success := FALSE;
8160         done := TRUE;
8161         RETURN NEXT result;
8162         RETURN;
8163     END IF;
8164
8165     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
8166
8167     IF hold_test.holdable IS FALSE THEN
8168         result.fail_part := 'config.hold_matrix_test.holdable';
8169         result.success := FALSE;
8170         done := TRUE;
8171         RETURN NEXT result;
8172     END IF;
8173
8174     IF item_object.holdable IS FALSE THEN
8175         result.fail_part := 'item.holdable';
8176         result.success := FALSE;
8177         done := TRUE;
8178         RETURN NEXT result;
8179     END IF;
8180
8181     IF item_status_object.holdable IS FALSE THEN
8182         result.fail_part := 'status.holdable';
8183         result.success := FALSE;
8184         done := TRUE;
8185         RETURN NEXT result;
8186     END IF;
8187
8188     IF item_location_object.holdable IS FALSE THEN
8189         result.fail_part := 'location.holdable';
8190         result.success := FALSE;
8191         done := TRUE;
8192         RETURN NEXT result;
8193     END IF;
8194
8195     IF hold_test.transit_range IS NOT NULL THEN
8196         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
8197         IF hold_test.distance_is_from_owner THEN
8198             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;
8199         ELSE
8200             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
8201         END IF;
8202
8203         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
8204
8205         IF NOT FOUND THEN
8206             result.fail_part := 'transit_range';
8207             result.success := FALSE;
8208             done := TRUE;
8209             RETURN NEXT result;
8210         END IF;
8211     END IF;
8212  
8213     FOR standing_penalty IN
8214         SELECT  DISTINCT csp.*
8215           FROM  actor.usr_standing_penalty usp
8216                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8217           WHERE usr = match_user
8218                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8219                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8220                 AND csp.block_list LIKE '%HOLD%' LOOP
8221
8222         result.fail_part := standing_penalty.name;
8223         result.success := FALSE;
8224         done := TRUE;
8225         RETURN NEXT result;
8226     END LOOP;
8227
8228     IF hold_test.stop_blocked_user IS TRUE THEN
8229         FOR standing_penalty IN
8230             SELECT  DISTINCT csp.*
8231               FROM  actor.usr_standing_penalty usp
8232                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8233               WHERE usr = match_user
8234                     AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8235                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8236                     AND csp.block_list LIKE '%CIRC%' LOOP
8237     
8238             result.fail_part := standing_penalty.name;
8239             result.success := FALSE;
8240             done := TRUE;
8241             RETURN NEXT result;
8242         END LOOP;
8243     END IF;
8244
8245     IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
8246         SELECT    INTO hold_count COUNT(*)
8247           FROM    action.hold_request
8248           WHERE    usr = match_user
8249             AND fulfillment_time IS NULL
8250             AND cancel_time IS NULL
8251             AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
8252
8253         IF hold_count >= hold_test.max_holds THEN
8254             result.fail_part := 'config.hold_matrix_test.max_holds';
8255             result.success := FALSE;
8256             done := TRUE;
8257             RETURN NEXT result;
8258         END IF;
8259     END IF;
8260
8261     IF item_object.age_protect IS NOT NULL THEN
8262         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
8263         IF hold_test.distance_is_from_owner THEN
8264             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
8265         ELSE
8266             SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
8267         END IF;
8268         IF use_active_date = 'true' THEN
8269             age_protect_date := COALESCE(item_object.active_date, NOW());
8270         ELSE
8271             age_protect_date := item_object.create_date;
8272         END IF;
8273         IF age_protect_date + age_protect_object.age > NOW() THEN
8274             IF hold_test.distance_is_from_owner THEN
8275                 SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
8276                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
8277             ELSE
8278                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
8279             END IF;
8280
8281             IF hold_transit_prox > age_protect_object.prox THEN
8282                 result.fail_part := 'config.rule_age_hold_protect.prox';
8283                 result.success := FALSE;
8284                 done := TRUE;
8285                 RETURN NEXT result;
8286             END IF;
8287         END IF;
8288     END IF;
8289
8290     IF NOT done THEN
8291         RETURN NEXT result;
8292     END IF;
8293
8294     RETURN;
8295 END;
8296 $func$ LANGUAGE plpgsql;
8297
8298
8299 -- Evergreen DB patch 0669.data.recall_and_force_holds.sql
8300 --
8301 -- FIXME: insert description of change, if needed
8302 --
8303
8304
8305 -- check whether patch can be applied
8306 SELECT evergreen.upgrade_deps_block_check('0669', :eg_version);
8307
8308 -- FIXME: add/check SQL statements to perform the upgrade
8309 INSERT INTO permission.perm_list ( id, code, description ) VALUES
8310  ( 517, 'COPY_HOLDS_FORCE', oils_i18n_gettext( 517, 
8311     'Allow a user to place a force hold on a specific copy', 'ppl', 'description' )),
8312  ( 518, 'COPY_HOLDS_RECALL', oils_i18n_gettext( 518, 
8313     'Allow a user to place a cataloging recall on a specific copy', 'ppl', 'description' ));
8314
8315
8316 -- Evergreen DB patch 0670.data.mark-email-and-phone-invalid.sql
8317 --
8318 -- Add org unit settings and standing penalty types to support
8319 -- the mark email/phone invalid features.
8320 --
8321
8322 -- check whether patch can be applied
8323 SELECT evergreen.upgrade_deps_block_check('0670', :eg_version);
8324
8325
8326 INSERT INTO config.standing_penalty (id, name, label, staff_alert, org_depth) VALUES
8327     (
8328         31,
8329         'INVALID_PATRON_EMAIL_ADDRESS',
8330         oils_i18n_gettext(
8331             31,
8332             'Patron had an invalid email address',
8333             'csp',
8334             'label'
8335         ),
8336         TRUE,
8337         0
8338     ),
8339     (
8340         32,
8341         'INVALID_PATRON_DAY_PHONE',
8342         oils_i18n_gettext(
8343             32,
8344             'Patron had an invalid daytime phone number',
8345             'csp',
8346             'label'
8347         ),
8348         TRUE,
8349         0
8350     ),
8351     (
8352         33,
8353         'INVALID_PATRON_EVENING_PHONE',
8354         oils_i18n_gettext(
8355             33,
8356             'Patron had an invalid evening phone number',
8357             'csp',
8358             'label'
8359         ),
8360         TRUE,
8361         0
8362     ),
8363     (
8364         34,
8365         'INVALID_PATRON_OTHER_PHONE',
8366         oils_i18n_gettext(
8367             34,
8368             'Patron had an invalid other phone number',
8369             'csp',
8370             'label'
8371         ),
8372         TRUE,
8373         0
8374     );
8375
8376
8377
8378 SELECT evergreen.upgrade_deps_block_check('0671', :eg_version);
8379
8380 ALTER TABLE asset.copy_location
8381     ADD COLUMN checkin_alert BOOL NOT NULL DEFAULT FALSE;
8382
8383 -- Evergreen DB patch 0672.fix-nonfiling-titles.sql
8384 --
8385 -- Titles that begin with non-filing articles using apostrophes
8386 -- (for example, "L'armée") get spaces injected between the article
8387 -- and the subsequent text, which then breaks searching for titles
8388 -- beginning with those articles.
8389 --
8390 -- This patch adds a nonfiling title element to MODS32 that can then
8391 -- be used to retrieve the title proper without affecting the spaces
8392 -- in the title. It's what we want, what we really really want, for
8393 -- title searches.
8394 --
8395
8396
8397 -- check whether patch can be applied
8398 SELECT evergreen.upgrade_deps_block_check('0672', :eg_version);
8399
8400 -- Update the XPath definition before the titleNonfiling element exists;
8401 -- but are you really going to read through the whole XSL below before
8402 -- seeing this important bit?
8403 UPDATE config.metabib_field
8404     SET xpath = $$//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]$$,
8405         format = 'mods32'
8406     WHERE field_class = 'title' AND name = 'proper';
8407
8408 UPDATE config.xml_transform SET xslt=$$<?xml version="1.0" encoding="UTF-8"?>
8409 <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">
8410         <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
8411 <!--
8412 Revision 1.14 - Fixed template isValid and fields 010, 020, 022, 024, 028, and 037 to output additional identifier elements 
8413   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
8414
8415 Revision 1.13 - Changed order of output under cartographics to reflect schema  2006/11/28 tmee
8416         
8417 Revision 1.12 - Updated to reflect MODS 3.2 Mapping  2006/10/11 tmee
8418                 
8419 Revision 1.11 - The attribute objectPart moved from <languageTerm> to <language>
8420       2006/04/08  jrad
8421
8422 Revision 1.10 MODS 3.1 revisions to language and classification elements  
8423                                 (plus ability to find marc:collection embedded in wrapper elements such as SRU zs: wrappers)
8424                                 2006/02/06  ggar
8425
8426 Revision 1.9 subfield $y was added to field 242 2004/09/02 10:57 jrad
8427
8428 Revision 1.8 Subject chopPunctuation expanded and attribute fixes 2004/08/12 jrad
8429
8430 Revision 1.7 2004/03/25 08:29 jrad
8431
8432 Revision 1.6 various validation fixes 2004/02/20 ntra
8433
8434 Revision 1.5  2003/10/02 16:18:58  ntra
8435 MODS2 to MODS3 updates, language unstacking and 
8436 de-duping, chopPunctuation expanded
8437
8438 Revision 1.3  2003/04/03 00:07:19  ntra
8439 Revision 1.3 Additional Changes not related to MODS Version 2.0 by ntra
8440
8441 Revision 1.2  2003/03/24 19:37:42  ckeith
8442 Added Log Comment
8443
8444 -->
8445         <xsl:template match="/">
8446                 <xsl:choose>
8447                         <xsl:when test="//marc:collection">
8448                                 <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">
8449                                         <xsl:for-each select="//marc:collection/marc:record">
8450                                                 <mods version="3.2">
8451                                                         <xsl:call-template name="marcRecord"/>
8452                                                 </mods>
8453                                         </xsl:for-each>
8454                                 </modsCollection>
8455                         </xsl:when>
8456                         <xsl:otherwise>
8457                                 <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">
8458                                         <xsl:for-each select="//marc:record">
8459                                                 <xsl:call-template name="marcRecord"/>
8460                                         </xsl:for-each>
8461                                 </mods>
8462                         </xsl:otherwise>
8463                 </xsl:choose>
8464         </xsl:template>
8465         <xsl:template name="marcRecord">
8466                 <xsl:variable name="leader" select="marc:leader"/>
8467                 <xsl:variable name="leader6" select="substring($leader,7,1)"/>
8468                 <xsl:variable name="leader7" select="substring($leader,8,1)"/>
8469                 <xsl:variable name="controlField008" select="marc:controlfield[@tag='008']"/>
8470                 <xsl:variable name="typeOf008">
8471                         <xsl:choose>
8472                                 <xsl:when test="$leader6='a'">
8473                                         <xsl:choose>
8474                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">BK</xsl:when>
8475                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">SE</xsl:when>
8476                                         </xsl:choose>
8477                                 </xsl:when>
8478                                 <xsl:when test="$leader6='t'">BK</xsl:when>
8479                                 <xsl:when test="$leader6='p'">MM</xsl:when>
8480                                 <xsl:when test="$leader6='m'">CF</xsl:when>
8481                                 <xsl:when test="$leader6='e' or $leader6='f'">MP</xsl:when>
8482                                 <xsl:when test="$leader6='g' or $leader6='k' or $leader6='o' or $leader6='r'">VM</xsl:when>
8483                                 <xsl:when test="$leader6='c' or $leader6='d' or $leader6='i' or $leader6='j'">MU</xsl:when>
8484                         </xsl:choose>
8485                 </xsl:variable>
8486                 <xsl:for-each select="marc:datafield[@tag='245']">
8487                         <titleInfo>
8488                                 <xsl:variable name="title">
8489                                         <xsl:choose>
8490                                                 <xsl:when test="marc:subfield[@code='b']">
8491                                                         <xsl:call-template name="specialSubfieldSelect">
8492                                                                 <xsl:with-param name="axis">b</xsl:with-param>
8493                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
8494                                                         </xsl:call-template>
8495                                                 </xsl:when>
8496                                                 <xsl:otherwise>
8497                                                         <xsl:call-template name="subfieldSelect">
8498                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
8499                                                         </xsl:call-template>
8500                                                 </xsl:otherwise>
8501                                         </xsl:choose>
8502                                 </xsl:variable>
8503                                 <xsl:variable name="titleChop">
8504                                         <xsl:call-template name="chopPunctuation">
8505                                                 <xsl:with-param name="chopString">
8506                                                         <xsl:value-of select="$title"/>
8507                                                 </xsl:with-param>
8508                                         </xsl:call-template>
8509                                 </xsl:variable>
8510                                 <xsl:choose>
8511                                         <xsl:when test="@ind2>0">
8512                                                 <nonSort>
8513                                                         <xsl:value-of select="substring($titleChop,1,@ind2)"/>
8514                                                 </nonSort>
8515                                                 <title>
8516                                                         <xsl:value-of select="substring($titleChop,@ind2+1)"/>
8517                                                 </title>
8518                                         </xsl:when>
8519                                         <xsl:otherwise>
8520                                                 <title>
8521                                                         <xsl:value-of select="$titleChop"/>
8522                                                 </title>
8523                                         </xsl:otherwise>
8524                                 </xsl:choose>
8525                                 <xsl:if test="marc:subfield[@code='b']">
8526                                         <subTitle>
8527                                                 <xsl:call-template name="chopPunctuation">
8528                                                         <xsl:with-param name="chopString">
8529                                                                 <xsl:call-template name="specialSubfieldSelect">
8530                                                                         <xsl:with-param name="axis">b</xsl:with-param>
8531                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
8532                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
8533                                                                 </xsl:call-template>
8534                                                         </xsl:with-param>
8535                                                 </xsl:call-template>
8536                                         </subTitle>
8537                                 </xsl:if>
8538                                 <xsl:call-template name="part"></xsl:call-template>
8539                         </titleInfo>
8540                         <!-- A form of title that ignores non-filing characters; useful
8541                                  for not converting "L'Oreal" into "L' Oreal" at index time -->
8542                         <titleNonfiling>
8543                                 <xsl:variable name="title">
8544                                         <xsl:choose>
8545                                                 <xsl:when test="marc:subfield[@code='b']">
8546                                                         <xsl:call-template name="specialSubfieldSelect">
8547                                                                 <xsl:with-param name="axis">b</xsl:with-param>
8548                                                                 <xsl:with-param name="beforeCodes">afgk</xsl:with-param>
8549                                                         </xsl:call-template>
8550                                                 </xsl:when>
8551                                                 <xsl:otherwise>
8552                                                         <xsl:call-template name="subfieldSelect">
8553                                                                 <xsl:with-param name="codes">abfgk</xsl:with-param>
8554                                                         </xsl:call-template>
8555                                                 </xsl:otherwise>
8556                                         </xsl:choose>
8557                                 </xsl:variable>
8558                                 <title>
8559                                         <xsl:value-of select="$title"/>
8560                                 </title>
8561                                 <xsl:if test="marc:subfield[@code='b']">
8562                                         <subTitle>
8563                                                 <xsl:call-template name="chopPunctuation">
8564                                                         <xsl:with-param name="chopString">
8565                                                                 <xsl:call-template name="specialSubfieldSelect">
8566                                                                         <xsl:with-param name="axis">b</xsl:with-param>
8567                                                                         <xsl:with-param name="anyCodes">b</xsl:with-param>
8568                                                                         <xsl:with-param name="afterCodes">afgk</xsl:with-param>
8569                                                                 </xsl:call-template>
8570                                                         </xsl:with-param>
8571                                                 </xsl:call-template>
8572                                         </subTitle>
8573                                 </xsl:if>
8574                                 <xsl:call-template name="part"></xsl:call-template>
8575                         </titleNonfiling>
8576                 </xsl:for-each>
8577                 <xsl:for-each select="marc:datafield[@tag='210']">
8578                         <titleInfo type="abbreviated">
8579                                 <title>
8580                                         <xsl:call-template name="chopPunctuation">
8581                                                 <xsl:with-param name="chopString">
8582                                                         <xsl:call-template name="subfieldSelect">
8583                                                                 <xsl:with-param name="codes">a</xsl:with-param>
8584                                                         </xsl:call-template>
8585                                                 </xsl:with-param>
8586                                         </xsl:call-template>
8587                                 </title>
8588                                 <xsl:call-template name="subtitle"/>
8589                         </titleInfo>
8590                 </xsl:for-each>
8591                 <xsl:for-each select="marc:datafield[@tag='242']">
8592                         <titleInfo type="translated">
8593                                 <!--09/01/04 Added subfield $y-->
8594                                 <xsl:for-each select="marc:subfield[@code='y']">
8595                                         <xsl:attribute name="lang">
8596                                                 <xsl:value-of select="text()"/>
8597                                         </xsl:attribute>
8598                                 </xsl:for-each>
8599                                 <title>
8600                                         <xsl:call-template name="chopPunctuation">
8601                                                 <xsl:with-param name="chopString">
8602                                                         <xsl:call-template name="subfieldSelect">
8603                                                                 <!-- 1/04 removed $h, b -->
8604                                                                 <xsl:with-param name="codes">a</xsl:with-param>
8605                                                         </xsl:call-template>
8606                                                 </xsl:with-param>
8607                                         </xsl:call-template>
8608                                 </title>
8609                                 <!-- 1/04 fix -->
8610                                 <xsl:call-template name="subtitle"/>
8611                                 <xsl:call-template name="part"/>
8612                         </titleInfo>
8613                 </xsl:for-each>
8614                 <xsl:for-each select="marc:datafield[@tag='246']">
8615                         <titleInfo type="alternative">
8616                                 <xsl:for-each select="marc:subfield[@code='i']">
8617                                         <xsl:attribute name="displayLabel">
8618                                                 <xsl:value-of select="text()"/>
8619                                         </xsl:attribute>
8620                                 </xsl:for-each>
8621                                 <title>
8622                                         <xsl:call-template name="chopPunctuation">
8623                                                 <xsl:with-param name="chopString">
8624                                                         <xsl:call-template name="subfieldSelect">
8625                                                                 <!-- 1/04 removed $h, $b -->
8626                                                                 <xsl:with-param name="codes">af</xsl:with-param>
8627                                                         </xsl:call-template>
8628                                                 </xsl:with-param>
8629                                         </xsl:call-template>
8630                                 </title>
8631                                 <xsl:call-template name="subtitle"/>
8632                                 <xsl:call-template name="part"/>
8633                         </titleInfo>
8634                 </xsl:for-each>
8635                 <xsl:for-each select="marc:datafield[@tag='130']|marc:datafield[@tag='240']|marc:datafield[@tag='730'][@ind2!='2']">
8636                         <titleInfo type="uniform">
8637                                 <title>
8638                                         <xsl:variable name="str">
8639                                                 <xsl:for-each select="marc:subfield">
8640                                                         <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'])))">
8641                                                                 <xsl:value-of select="text()"/>
8642                                                                 <xsl:text> </xsl:text>
8643                                                         </xsl:if>
8644                                                 </xsl:for-each>
8645                                         </xsl:variable>
8646                                         <xsl:call-template name="chopPunctuation">
8647                                                 <xsl:with-param name="chopString">
8648                                                         <xsl:value-of select="substring($str,1,string-length($str)-1)"/>
8649                                                 </xsl:with-param>
8650                                         </xsl:call-template>
8651                                 </title>
8652                                 <xsl:call-template name="part"/>
8653                         </titleInfo>
8654                 </xsl:for-each>
8655                 <xsl:for-each select="marc:datafield[@tag='740'][@ind2!='2']">
8656                         <titleInfo type="alternative">
8657                                 <title>
8658                                         <xsl:call-template name="chopPunctuation">
8659                                                 <xsl:with-param name="chopString">
8660                                                         <xsl:call-template name="subfieldSelect">
8661                                                                 <xsl:with-param name="codes">ah</xsl:with-param>
8662                                                         </xsl:call-template>
8663                                                 </xsl:with-param>
8664                                         </xsl:call-template>
8665                                 </title>
8666                                 <xsl:call-template name="part"/>
8667                         </titleInfo>
8668                 </xsl:for-each>
8669                 <xsl:for-each select="marc:datafield[@tag='100']">
8670                         <name type="personal">
8671                                 <xsl:call-template name="nameABCDQ"/>
8672                                 <xsl:call-template name="affiliation"/>
8673                                 <role>
8674                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8675                                 </role>
8676                                 <xsl:call-template name="role"/>
8677                         </name>
8678                 </xsl:for-each>
8679                 <xsl:for-each select="marc:datafield[@tag='110']">
8680                         <name type="corporate">
8681                                 <xsl:call-template name="nameABCDN"/>
8682                                 <role>
8683                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8684                                 </role>
8685                                 <xsl:call-template name="role"/>
8686                         </name>
8687                 </xsl:for-each>
8688                 <xsl:for-each select="marc:datafield[@tag='111']">
8689                         <name type="conference">
8690                                 <xsl:call-template name="nameACDEQ"/>
8691                                 <role>
8692                                         <roleTerm authority="marcrelator" type="text">creator</roleTerm>
8693                                 </role>
8694                                 <xsl:call-template name="role"/>
8695                         </name>
8696                 </xsl:for-each>
8697                 <xsl:for-each select="marc:datafield[@tag='700'][not(marc:subfield[@code='t'])]">
8698                         <name type="personal">
8699                                 <xsl:call-template name="nameABCDQ"/>
8700                                 <xsl:call-template name="affiliation"/>
8701                                 <xsl:call-template name="role"/>
8702                         </name>
8703                 </xsl:for-each>
8704                 <xsl:for-each select="marc:datafield[@tag='710'][not(marc:subfield[@code='t'])]">
8705                         <name type="corporate">
8706                                 <xsl:call-template name="nameABCDN"/>
8707                                 <xsl:call-template name="role"/>
8708                         </name>
8709                 </xsl:for-each>
8710                 <xsl:for-each select="marc:datafield[@tag='711'][not(marc:subfield[@code='t'])]">
8711                         <name type="conference">
8712                                 <xsl:call-template name="nameACDEQ"/>
8713                                 <xsl:call-template name="role"/>
8714                         </name>
8715                 </xsl:for-each>
8716                 <xsl:for-each select="marc:datafield[@tag='720'][not(marc:subfield[@code='t'])]">
8717                         <name>
8718                                 <xsl:if test="@ind1=1">
8719                                         <xsl:attribute name="type">
8720                                                 <xsl:text>personal</xsl:text>
8721                                         </xsl:attribute>
8722                                 </xsl:if>
8723                                 <namePart>
8724                                         <xsl:value-of select="marc:subfield[@code='a']"/>
8725                                 </namePart>
8726                                 <xsl:call-template name="role"/>
8727                         </name>
8728                 </xsl:for-each>
8729                 <typeOfResource>
8730                         <xsl:if test="$leader7='c'">
8731                                 <xsl:attribute name="collection">yes</xsl:attribute>
8732                         </xsl:if>
8733                         <xsl:if test="$leader6='d' or $leader6='f' or $leader6='p' or $leader6='t'">
8734                                 <xsl:attribute name="manuscript">yes</xsl:attribute>
8735                         </xsl:if>
8736                         <xsl:choose>
8737                                 <xsl:when test="$leader6='a' or $leader6='t'">text</xsl:when>
8738                                 <xsl:when test="$leader6='e' or $leader6='f'">cartographic</xsl:when>
8739                                 <xsl:when test="$leader6='c' or $leader6='d'">notated music</xsl:when>
8740                                 <xsl:when test="$leader6='i'">sound recording-nonmusical</xsl:when>
8741                                 <xsl:when test="$leader6='j'">sound recording-musical</xsl:when>
8742                                 <xsl:when test="$leader6='k'">still image</xsl:when>
8743                                 <xsl:when test="$leader6='g'">moving image</xsl:when>
8744                                 <xsl:when test="$leader6='r'">three dimensional object</xsl:when>
8745                                 <xsl:when test="$leader6='m'">software, multimedia</xsl:when>
8746                                 <xsl:when test="$leader6='p'">mixed material</xsl:when>
8747                         </xsl:choose>
8748                 </typeOfResource>
8749                 <xsl:if test="substring($controlField008,26,1)='d'">
8750                         <genre authority="marc">globe</genre>
8751                 </xsl:if>
8752                 <xsl:if test="marc:controlfield[@tag='007'][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
8753                         <genre authority="marc">remote sensing image</genre>
8754                 </xsl:if>
8755                 <xsl:if test="$typeOf008='MP'">
8756                         <xsl:variable name="controlField008-25" select="substring($controlField008,26,1)"></xsl:variable>
8757                         <xsl:choose>
8758                                 <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']">
8759                                         <genre authority="marc">map</genre>
8760                                 </xsl:when>
8761                                 <xsl:when test="$controlField008-25='e' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
8762                                         <genre authority="marc">atlas</genre>
8763                                 </xsl:when>
8764                         </xsl:choose>
8765                 </xsl:if>
8766                 <xsl:if test="$typeOf008='SE'">
8767                         <xsl:variable name="controlField008-21" select="substring($controlField008,22,1)"></xsl:variable>
8768                         <xsl:choose>
8769                                 <xsl:when test="$controlField008-21='d'">
8770                                         <genre authority="marc">database</genre>
8771                                 </xsl:when>
8772                                 <xsl:when test="$controlField008-21='l'">
8773                                         <genre authority="marc">loose-leaf</genre>
8774                                 </xsl:when>
8775                                 <xsl:when test="$controlField008-21='m'">
8776                                         <genre authority="marc">series</genre>
8777                                 </xsl:when>
8778                                 <xsl:when test="$controlField008-21='n'">
8779                                         <genre authority="marc">newspaper</genre>
8780                                 </xsl:when>
8781                                 <xsl:when test="$controlField008-21='p'">
8782                                         <genre authority="marc">periodical</genre>
8783                                 </xsl:when>
8784                                 <xsl:when test="$controlField008-21='w'">
8785                                         <genre authority="marc">web site</genre>
8786                                 </xsl:when>
8787                         </xsl:choose>
8788                 </xsl:if>
8789                 <xsl:if test="$typeOf008='BK' or $typeOf008='SE'">
8790                         <xsl:variable name="controlField008-24" select="substring($controlField008,25,4)"></xsl:variable>
8791                         <xsl:choose>
8792                                 <xsl:when test="contains($controlField008-24,'a')">
8793                                         <genre authority="marc">abstract or summary</genre>
8794                                 </xsl:when>
8795                                 <xsl:when test="contains($controlField008-24,'b')">
8796                                         <genre authority="marc">bibliography</genre>
8797                                 </xsl:when>
8798                                 <xsl:when test="contains($controlField008-24,'c')">
8799                                         <genre authority="marc">catalog</genre>
8800                                 </xsl:when>
8801                                 <xsl:when test="contains($controlField008-24,'d')">
8802                                         <genre authority="marc">dictionary</genre>
8803                                 </xsl:when>
8804                                 <xsl:when test="contains($controlField008-24,'e')">
8805                                         <genre authority="marc">encyclopedia</genre>
8806                                 </xsl:when>
8807                                 <xsl:when test="contains($controlField008-24,'f')">
8808                                         <genre authority="marc">handbook</genre>
8809                                 </xsl:when>
8810                                 <xsl:when test="contains($controlField008-24,'g')">
8811                                         <genre authority="marc">legal article</genre>
8812                                 </xsl:when>
8813                                 <xsl:when test="contains($controlField008-24,'i')">
8814                                         <genre authority="marc">index</genre>
8815                                 </xsl:when>
8816                                 <xsl:when test="contains($controlField008-24,'k')">
8817                                         <genre authority="marc">discography</genre>
8818                                 </xsl:when>
8819                                 <xsl:when test="contains($controlField008-24,'l')">
8820                                         <genre authority="marc">legislation</genre>
8821                                 </xsl:when>
8822                                 <xsl:when test="contains($controlField008-24,'m')">
8823                                         <genre authority="marc">theses</genre>
8824                                 </xsl:when>
8825                                 <xsl:when test="contains($controlField008-24,'n')">
8826                                         <genre authority="marc">survey of literature</genre>
8827                                 </xsl:when>
8828                                 <xsl:when test="contains($controlField008-24,'o')">
8829                                         <genre authority="marc">review</genre>
8830                                 </xsl:when>
8831                                 <xsl:when test="contains($controlField008-24,'p')">
8832                                         <genre authority="marc">programmed text</genre>
8833                                 </xsl:when>
8834                                 <xsl:when test="contains($controlField008-24,'q')">
8835                                         <genre authority="marc">filmography</genre>
8836                                 </xsl:when>
8837                                 <xsl:when test="contains($controlField008-24,'r')">
8838                                         <genre authority="marc">directory</genre>
8839                                 </xsl:when>
8840                                 <xsl:when test="contains($controlField008-24,'s')">
8841                                         <genre authority="marc">statistics</genre>
8842                                 </xsl:when>
8843                                 <xsl:when test="contains($controlField008-24,'t')">
8844                                         <genre authority="marc">technical report</genre>
8845                                 </xsl:when>
8846                                 <xsl:when test="contains($controlField008-24,'v')">
8847                                         <genre authority="marc">legal case and case notes</genre>
8848                                 </xsl:when>
8849                                 <xsl:when test="contains($controlField008-24,'w')">
8850                                         <genre authority="marc">law report or digest</genre>
8851                                 </xsl:when>
8852                                 <xsl:when test="contains($controlField008-24,'z')">
8853                                         <genre authority="marc">treaty</genre>
8854                                 </xsl:when>
8855                         </xsl:choose>
8856                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
8857                         <xsl:choose>
8858                                 <xsl:when test="$controlField008-29='1'">
8859                                         <genre authority="marc">conference publication</genre>
8860                                 </xsl:when>
8861                         </xsl:choose>
8862                 </xsl:if>
8863                 <xsl:if test="$typeOf008='CF'">
8864                         <xsl:variable name="controlField008-26" select="substring($controlField008,27,1)"></xsl:variable>
8865                         <xsl:choose>
8866                                 <xsl:when test="$controlField008-26='a'">
8867                                         <genre authority="marc">numeric data</genre>
8868                                 </xsl:when>
8869                                 <xsl:when test="$controlField008-26='e'">
8870                                         <genre authority="marc">database</genre>
8871                                 </xsl:when>
8872                                 <xsl:when test="$controlField008-26='f'">
8873                                         <genre authority="marc">font</genre>
8874                                 </xsl:when>
8875                                 <xsl:when test="$controlField008-26='g'">
8876                                         <genre authority="marc">game</genre>
8877                                 </xsl:when>
8878                         </xsl:choose>
8879                 </xsl:if>
8880                 <xsl:if test="$typeOf008='BK'">
8881                         <xsl:if test="substring($controlField008,25,1)='j'">
8882                                 <genre authority="marc">patent</genre>
8883                         </xsl:if>
8884                         <xsl:if test="substring($controlField008,31,1)='1'">
8885                                 <genre authority="marc">festschrift</genre>
8886                         </xsl:if>
8887                         <xsl:variable name="controlField008-34" select="substring($controlField008,35,1)"></xsl:variable>
8888                         <xsl:if test="$controlField008-34='a' or $controlField008-34='b' or $controlField008-34='c' or $controlField008-34='d'">
8889                                 <genre authority="marc">biography</genre>
8890                         </xsl:if>
8891                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
8892                         <xsl:choose>
8893                                 <xsl:when test="$controlField008-33='e'">
8894                                         <genre authority="marc">essay</genre>
8895                                 </xsl:when>
8896                                 <xsl:when test="$controlField008-33='d'">
8897                                         <genre authority="marc">drama</genre>
8898                                 </xsl:when>
8899                                 <xsl:when test="$controlField008-33='c'">
8900                                         <genre authority="marc">comic strip</genre>
8901                                 </xsl:when>
8902                                 <xsl:when test="$controlField008-33='l'">
8903                                         <genre authority="marc">fiction</genre>
8904                                 </xsl:when>
8905                                 <xsl:when test="$controlField008-33='h'">
8906                                         <genre authority="marc">humor, satire</genre>
8907                                 </xsl:when>
8908                                 <xsl:when test="$controlField008-33='i'">
8909                                         <genre authority="marc">letter</genre>
8910                                 </xsl:when>
8911                                 <xsl:when test="$controlField008-33='f'">
8912                                         <genre authority="marc">novel</genre>
8913                                 </xsl:when>
8914                                 <xsl:when test="$controlField008-33='j'">
8915                                         <genre authority="marc">short story</genre>
8916                                 </xsl:when>
8917                                 <xsl:when test="$controlField008-33='s'">
8918                                         <genre authority="marc">speech</genre>
8919                                 </xsl:when>
8920                         </xsl:choose>
8921                 </xsl:if>
8922                 <xsl:if test="$typeOf008='MU'">
8923                         <xsl:variable name="controlField008-30-31" select="substring($controlField008,31,2)"></xsl:variable>
8924                         <xsl:if test="contains($controlField008-30-31,'b')">
8925                                 <genre authority="marc">biography</genre>
8926                         </xsl:if>
8927                         <xsl:if test="contains($controlField008-30-31,'c')">
8928                                 <genre authority="marc">conference publication</genre>
8929                         </xsl:if>
8930                         <xsl:if test="contains($controlField008-30-31,'d')">
8931                                 <genre authority="marc">drama</genre>
8932                         </xsl:if>
8933                         <xsl:if test="contains($controlField008-30-31,'e')">
8934                                 <genre authority="marc">essay</genre>
8935                         </xsl:if>
8936                         <xsl:if test="contains($controlField008-30-31,'f')">
8937                                 <genre authority="marc">fiction</genre>
8938                         </xsl:if>
8939                         <xsl:if test="contains($controlField008-30-31,'o')">
8940                                 <genre authority="marc">folktale</genre>
8941                         </xsl:if>
8942                         <xsl:if test="contains($controlField008-30-31,'h')">
8943                                 <genre authority="marc">history</genre>
8944                         </xsl:if>
8945                         <xsl:if test="contains($controlField008-30-31,'k')">
8946                                 <genre authority="marc">humor, satire</genre>
8947                         </xsl:if>
8948                         <xsl:if test="contains($controlField008-30-31,'m')">
8949                                 <genre authority="marc">memoir</genre>
8950                         </xsl:if>
8951                         <xsl:if test="contains($controlField008-30-31,'p')">
8952                                 <genre authority="marc">poetry</genre>
8953                         </xsl:if>
8954                         <xsl:if test="contains($controlField008-30-31,'r')">
8955                                 <genre authority="marc">rehearsal</genre>
8956                         </xsl:if>
8957                         <xsl:if test="contains($controlField008-30-31,'g')">
8958                                 <genre authority="marc">reporting</genre>
8959                         </xsl:if>
8960                         <xsl:if test="contains($controlField008-30-31,'s')">
8961                                 <genre authority="marc">sound</genre>
8962                         </xsl:if>
8963                         <xsl:if test="contains($controlField008-30-31,'l')">
8964                                 <genre authority="marc">speech</genre>
8965                         </xsl:if>
8966                 </xsl:if>
8967                 <xsl:if test="$typeOf008='VM'">
8968                         <xsl:variable name="controlField008-33" select="substring($controlField008,34,1)"></xsl:variable>
8969                         <xsl:choose>
8970                                 <xsl:when test="$controlField008-33='a'">
8971                                         <genre authority="marc">art original</genre>
8972                                 </xsl:when>
8973                                 <xsl:when test="$controlField008-33='b'">
8974                                         <genre authority="marc">kit</genre>
8975                                 </xsl:when>
8976                                 <xsl:when test="$controlField008-33='c'">
8977                                         <genre authority="marc">art reproduction</genre>
8978                                 </xsl:when>
8979                                 <xsl:when test="$controlField008-33='d'">
8980                                         <genre authority="marc">diorama</genre>
8981                                 </xsl:when>
8982                                 <xsl:when test="$controlField008-33='f'">
8983                                         <genre authority="marc">filmstrip</genre>
8984                                 </xsl:when>
8985                                 <xsl:when test="$controlField008-33='g'">
8986                                         <genre authority="marc">legal article</genre>
8987                                 </xsl:when>
8988                                 <xsl:when test="$controlField008-33='i'">
8989                                         <genre authority="marc">picture</genre>
8990                                 </xsl:when>
8991                                 <xsl:when test="$controlField008-33='k'">
8992                                         <genre authority="marc">graphic</genre>
8993                                 </xsl:when>
8994                                 <xsl:when test="$controlField008-33='l'">
8995                                         <genre authority="marc">technical drawing</genre>
8996                                 </xsl:when>
8997                                 <xsl:when test="$controlField008-33='m'">
8998                                         <genre authority="marc">motion picture</genre>
8999                                 </xsl:when>
9000                                 <xsl:when test="$controlField008-33='n'">
9001                                         <genre authority="marc">chart</genre>
9002                                 </xsl:when>
9003                                 <xsl:when test="$controlField008-33='o'">
9004                                         <genre authority="marc">flash card</genre>
9005                                 </xsl:when>
9006                                 <xsl:when test="$controlField008-33='p'">
9007                                         <genre authority="marc">microscope slide</genre>
9008                                 </xsl:when>
9009                                 <xsl:when test="$controlField008-33='q' or marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
9010                                         <genre authority="marc">model</genre>
9011                                 </xsl:when>
9012                                 <xsl:when test="$controlField008-33='r'">
9013                                         <genre authority="marc">realia</genre>
9014                                 </xsl:when>
9015                                 <xsl:when test="$controlField008-33='s'">
9016                                         <genre authority="marc">slide</genre>
9017                                 </xsl:when>
9018                                 <xsl:when test="$controlField008-33='t'">
9019                                         <genre authority="marc">transparency</genre>
9020                                 </xsl:when>
9021                                 <xsl:when test="$controlField008-33='v'">
9022                                         <genre authority="marc">videorecording</genre>
9023                                 </xsl:when>
9024                                 <xsl:when test="$controlField008-33='w'">
9025                                         <genre authority="marc">toy</genre>
9026                                 </xsl:when>
9027                         </xsl:choose>
9028                 </xsl:if>
9029                 <xsl:for-each select="marc:datafield[@tag=655]">
9030                         <genre authority="marc">
9031                                 <xsl:attribute name="authority">
9032                                         <xsl:value-of select="marc:subfield[@code='2']"/>
9033                                 </xsl:attribute>
9034                                 <xsl:call-template name="subfieldSelect">
9035                                         <xsl:with-param name="codes">abvxyz</xsl:with-param>
9036                                         <xsl:with-param name="delimeter">-</xsl:with-param>
9037                                 </xsl:call-template>
9038                         </genre>
9039                 </xsl:for-each>
9040                 <originInfo>
9041                         <xsl:variable name="MARCpublicationCode" select="normalize-space(substring($controlField008,16,3))"></xsl:variable>
9042                         <xsl:if test="translate($MARCpublicationCode,'|','')">
9043                                 <place>
9044                                         <placeTerm>
9045                                                 <xsl:attribute name="type">code</xsl:attribute>
9046                                                 <xsl:attribute name="authority">marccountry</xsl:attribute>
9047                                                 <xsl:value-of select="$MARCpublicationCode"/>
9048                                         </placeTerm>
9049                                 </place>
9050                         </xsl:if>
9051                         <xsl:for-each select="marc:datafield[@tag=044]/marc:subfield[@code='c']">
9052                                 <place>
9053                                         <placeTerm>
9054                                                 <xsl:attribute name="type">code</xsl:attribute>
9055                                                 <xsl:attribute name="authority">iso3166</xsl:attribute>
9056                                                 <xsl:value-of select="."/>
9057                                         </placeTerm>
9058                                 </place>
9059                         </xsl:for-each>
9060                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='a']">
9061                                 <place>
9062                                         <placeTerm>
9063                                                 <xsl:attribute name="type">text</xsl:attribute>
9064                                                 <xsl:call-template name="chopPunctuationFront">
9065                                                         <xsl:with-param name="chopString">
9066                                                                 <xsl:call-template name="chopPunctuation">
9067                                                                         <xsl:with-param name="chopString" select="."/>
9068                                                                 </xsl:call-template>
9069                                                         </xsl:with-param>
9070                                                 </xsl:call-template>
9071                                         </placeTerm>
9072                                 </place>
9073                         </xsl:for-each>
9074                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='m']">
9075                                 <dateValid point="start">
9076                                         <xsl:value-of select="."/>
9077                                 </dateValid>
9078                         </xsl:for-each>
9079                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='n']">
9080                                 <dateValid point="end">
9081                                         <xsl:value-of select="."/>
9082                                 </dateValid>
9083                         </xsl:for-each>
9084                         <xsl:for-each select="marc:datafield[@tag=046]/marc:subfield[@code='j']">
9085                                 <dateModified>
9086                                         <xsl:value-of select="."/>
9087                                 </dateModified>
9088                         </xsl:for-each>
9089                         <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='b' or @code='c' or @code='g']">
9090                                 <xsl:choose>
9091                                         <xsl:when test="@code='b'">
9092                                                 <publisher>
9093                                                         <xsl:call-template name="chopPunctuation">
9094                                                                 <xsl:with-param name="chopString" select="."/>
9095                                                                 <xsl:with-param name="punctuation">
9096                                                                         <xsl:text>:,;/ </xsl:text>
9097                                                                 </xsl:with-param>
9098                                                         </xsl:call-template>
9099                                                 </publisher>
9100                                         </xsl:when>
9101                                         <xsl:when test="@code='c'">
9102                                                 <dateIssued>
9103                                                         <xsl:call-template name="chopPunctuation">
9104                                                                 <xsl:with-param name="chopString" select="."/>
9105                                                         </xsl:call-template>
9106                                                 </dateIssued>
9107                                         </xsl:when>
9108                                         <xsl:when test="@code='g'">
9109                                                 <dateCreated>
9110                                                         <xsl:value-of select="."/>
9111                                                 </dateCreated>
9112                                         </xsl:when>
9113                                 </xsl:choose>
9114                         </xsl:for-each>
9115                         <xsl:variable name="dataField260c">
9116                                 <xsl:call-template name="chopPunctuation">
9117                                         <xsl:with-param name="chopString" select="marc:datafield[@tag=260]/marc:subfield[@code='c']"></xsl:with-param>
9118                                 </xsl:call-template>
9119                         </xsl:variable>
9120                         <xsl:variable name="controlField008-7-10" select="normalize-space(substring($controlField008, 8, 4))"></xsl:variable>
9121                         <xsl:variable name="controlField008-11-14" select="normalize-space(substring($controlField008, 12, 4))"></xsl:variable>
9122                         <xsl:variable name="controlField008-6" select="normalize-space(substring($controlField008, 7, 1))"></xsl:variable>
9123                         <xsl:if test="$controlField008-6='e' or $controlField008-6='p' or $controlField008-6='r' or $controlField008-6='t' or $controlField008-6='s'">
9124                                 <xsl:if test="$controlField008-7-10 and ($controlField008-7-10 != $dataField260c)">
9125                                         <dateIssued encoding="marc">
9126                                                 <xsl:value-of select="$controlField008-7-10"/>
9127                                         </dateIssued>
9128                                 </xsl:if>
9129                         </xsl:if>
9130                         <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'">
9131                                 <xsl:if test="$controlField008-7-10">
9132                                         <dateIssued encoding="marc" point="start">
9133                                                 <xsl:value-of select="$controlField008-7-10"/>
9134                                         </dateIssued>
9135                                 </xsl:if>
9136                         </xsl:if>
9137                         <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'">
9138                                 <xsl:if test="$controlField008-11-14">
9139                                         <dateIssued encoding="marc" point="end">
9140                                                 <xsl:value-of select="$controlField008-11-14"/>
9141                                         </dateIssued>
9142                                 </xsl:if>
9143                         </xsl:if>
9144                         <xsl:if test="$controlField008-6='q'">
9145                                 <xsl:if test="$controlField008-7-10">
9146                                         <dateIssued encoding="marc" point="start" qualifier="questionable">
9147                                                 <xsl:value-of select="$controlField008-7-10"/>
9148                                         </dateIssued>
9149                                 </xsl:if>
9150                         </xsl:if>
9151                         <xsl:if test="$controlField008-6='q'">
9152                                 <xsl:if test="$controlField008-11-14">
9153                                         <dateIssued encoding="marc" point="end" qualifier="questionable">
9154                                                 <xsl:value-of select="$controlField008-11-14"/>
9155                                         </dateIssued>
9156                                 </xsl:if>
9157                         </xsl:if>
9158                         <xsl:if test="$controlField008-6='t'">
9159                                 <xsl:if test="$controlField008-11-14">
9160                                         <copyrightDate encoding="marc">
9161                                                 <xsl:value-of select="$controlField008-11-14"/>
9162                                         </copyrightDate>
9163                                 </xsl:if>
9164                         </xsl:if>
9165                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=0 or @ind1=1]/marc:subfield[@code='a']">
9166                                 <dateCaptured encoding="iso8601">
9167                                         <xsl:value-of select="."/>
9168                                 </dateCaptured>
9169                         </xsl:for-each>
9170                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][1]">
9171                                 <dateCaptured encoding="iso8601" point="start">
9172                                         <xsl:value-of select="."/>
9173                                 </dateCaptured>
9174                         </xsl:for-each>
9175                         <xsl:for-each select="marc:datafield[@tag=033][@ind1=2]/marc:subfield[@code='a'][2]">
9176                                 <dateCaptured encoding="iso8601" point="end">
9177                                         <xsl:value-of select="."/>
9178                                 </dateCaptured>
9179                         </xsl:for-each>
9180                         <xsl:for-each select="marc:datafield[@tag=250]/marc:subfield[@code='a']">
9181                                 <edition>
9182                                         <xsl:value-of select="."/>
9183                                 </edition>
9184                         </xsl:for-each>
9185                         <xsl:for-each select="marc:leader">
9186                                 <issuance>
9187                                         <xsl:choose>
9188                                                 <xsl:when test="$leader7='a' or $leader7='c' or $leader7='d' or $leader7='m'">monographic</xsl:when>
9189                                                 <xsl:when test="$leader7='b' or $leader7='i' or $leader7='s'">continuing</xsl:when>
9190                                         </xsl:choose>
9191                                 </issuance>
9192                         </xsl:for-each>
9193                         <xsl:for-each select="marc:datafield[@tag=310]|marc:datafield[@tag=321]">
9194                                 <frequency>
9195                                         <xsl:call-template name="subfieldSelect">
9196                                                 <xsl:with-param name="codes">ab</xsl:with-param>
9197                                         </xsl:call-template>
9198                                 </frequency>
9199                         </xsl:for-each>
9200                 </originInfo>
9201                 <xsl:variable name="controlField008-35-37" select="normalize-space(translate(substring($controlField008,36,3),'|#',''))"></xsl:variable>
9202                 <xsl:if test="$controlField008-35-37">
9203                         <language>
9204                                 <languageTerm authority="iso639-2b" type="code">
9205                                         <xsl:value-of select="substring($controlField008,36,3)"/>
9206                                 </languageTerm>
9207                         </language>
9208                 </xsl:if>
9209                 <xsl:for-each select="marc:datafield[@tag=041]">
9210                         <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']">
9211                                 <xsl:variable name="langCodes" select="."/>
9212                                 <xsl:choose>
9213                                         <xsl:when test="../marc:subfield[@code='2']='rfc3066'">
9214                                                 <!-- not stacked but could be repeated -->
9215                                                 <xsl:call-template name="rfcLanguages">
9216                                                         <xsl:with-param name="nodeNum">
9217                                                                 <xsl:value-of select="1"/>
9218                                                         </xsl:with-param>
9219                                                         <xsl:with-param name="usedLanguages">
9220                                                                 <xsl:text></xsl:text>
9221                                                         </xsl:with-param>
9222                                                         <xsl:with-param name="controlField008-35-37">
9223                                                                 <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
9224                                                         </xsl:with-param>
9225                                                 </xsl:call-template>
9226                                         </xsl:when>
9227                                         <xsl:otherwise>
9228                                                 <!-- iso -->
9229                                                 <xsl:variable name="allLanguages">
9230                                                         <xsl:copy-of select="$langCodes"></xsl:copy-of>
9231                                                 </xsl:variable>
9232                                                 <xsl:variable name="currentLanguage">
9233                                                         <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
9234                                                 </xsl:variable>
9235                                                 <xsl:call-template name="isoLanguage">
9236                                                         <xsl:with-param name="currentLanguage">
9237                                                                 <xsl:value-of select="substring($allLanguages,1,3)"></xsl:value-of>
9238                                                         </xsl:with-param>
9239                                                         <xsl:with-param name="remainingLanguages">
9240                                                                 <xsl:value-of select="substring($allLanguages,4,string-length($allLanguages)-3)"></xsl:value-of>
9241                                                         </xsl:with-param>
9242                                                         <xsl:with-param name="usedLanguages">
9243                                                                 <xsl:if test="$controlField008-35-37">
9244                                                                         <xsl:value-of select="$controlField008-35-37"></xsl:value-of>
9245                                                                 </xsl:if>
9246                                                         </xsl:with-param>
9247                                                 </xsl:call-template>
9248                                         </xsl:otherwise>
9249                                 </xsl:choose>
9250                         </xsl:for-each>
9251                 </xsl:for-each>
9252                 <xsl:variable name="physicalDescription">
9253                         <!--3.2 change tmee 007/11 -->
9254                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='a']">
9255                                 <digitalOrigin>reformatted digital</digitalOrigin>
9256                         </xsl:if>
9257                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='b']">
9258                                 <digitalOrigin>digitized microfilm</digitalOrigin>
9259                         </xsl:if>
9260                         <xsl:if test="$typeOf008='CF' and marc:controlfield[@tag=007][substring(.,12,1)='d']">
9261                                 <digitalOrigin>digitized other analog</digitalOrigin>
9262                         </xsl:if>
9263                         <xsl:variable name="controlField008-23" select="substring($controlField008,24,1)"></xsl:variable>
9264                         <xsl:variable name="controlField008-29" select="substring($controlField008,30,1)"></xsl:variable>
9265                         <xsl:variable name="check008-23">
9266                                 <xsl:if test="$typeOf008='BK' or $typeOf008='MU' or $typeOf008='SE' or $typeOf008='MM'">
9267                                         <xsl:value-of select="true()"></xsl:value-of>
9268                                 </xsl:if>
9269                         </xsl:variable>
9270                         <xsl:variable name="check008-29">
9271                                 <xsl:if test="$typeOf008='MP' or $typeOf008='VM'">
9272                                         <xsl:value-of select="true()"></xsl:value-of>
9273                                 </xsl:if>
9274                         </xsl:variable>
9275                         <xsl:choose>
9276                                 <xsl:when test="($check008-23 and $controlField008-23='f') or ($check008-29 and $controlField008-29='f')">
9277                                         <form authority="marcform">braille</form>
9278                                 </xsl:when>
9279                                 <xsl:when test="($controlField008-23=' ' and ($leader6='c' or $leader6='d')) or (($typeOf008='BK' or $typeOf008='SE') and ($controlField008-23=' ' or $controlField008='r'))">
9280                                         <form authority="marcform">print</form>
9281                                 </xsl:when>
9282                                 <xsl:when test="$leader6 = 'm' or ($check008-23 and $controlField008-23='s') or ($check008-29 and $controlField008-29='s')">
9283                                         <form authority="marcform">electronic</form>
9284                                 </xsl:when>
9285                                 <xsl:when test="($check008-23 and $controlField008-23='b') or ($check008-29 and $controlField008-29='b')">
9286                                         <form authority="marcform">microfiche</form>
9287                                 </xsl:when>
9288                                 <xsl:when test="($check008-23 and $controlField008-23='a') or ($check008-29 and $controlField008-29='a')">
9289                                         <form authority="marcform">microfilm</form>
9290                                 </xsl:when>
9291                         </xsl:choose>
9292                         <!-- 1/04 fix -->
9293                         <xsl:if test="marc:datafield[@tag=130]/marc:subfield[@code='h']">
9294                                 <form authority="gmd">
9295                                         <xsl:call-template name="chopBrackets">
9296                                                 <xsl:with-param name="chopString">
9297                                                         <xsl:value-of select="marc:datafield[@tag=130]/marc:subfield[@code='h']"></xsl:value-of>
9298                                                 </xsl:with-param>
9299                                         </xsl:call-template>
9300                                 </form>
9301                         </xsl:if>
9302                         <xsl:if test="marc:datafield[@tag=240]/marc:subfield[@code='h']">
9303                                 <form authority="gmd">
9304                                         <xsl:call-template name="chopBrackets">
9305                                                 <xsl:with-param name="chopString">
9306                                                         <xsl:value-of select="marc:datafield[@tag=240]/marc:subfield[@code='h']"></xsl:value-of>
9307                                                 </xsl:with-param>
9308                                         </xsl:call-template>
9309                                 </form>
9310                         </xsl:if>
9311                         <xsl:if test="marc:datafield[@tag=242]/marc:subfield[@code='h']">
9312                                 <form authority="gmd">
9313                                         <xsl:call-template name="chopBrackets">
9314                                                 <xsl:with-param name="chopString">
9315                                                         <xsl:value-of select="marc:datafield[@tag=242]/marc:subfield[@code='h']"></xsl:value-of>
9316                                                 </xsl:with-param>
9317                                         </xsl:call-template>
9318                                 </form>
9319                         </xsl:if>
9320                         <xsl:if test="marc:datafield[@tag=245]/marc:subfield[@code='h']">
9321                                 <form authority="gmd">
9322                                         <xsl:call-template name="chopBrackets">
9323                                                 <xsl:with-param name="chopString">
9324                                                         <xsl:value-of select="marc:datafield[@tag=245]/marc:subfield[@code='h']"></xsl:value-of>
9325                                                 </xsl:with-param>
9326                                         </xsl:call-template>
9327                                 </form>
9328                         </xsl:if>
9329                         <xsl:if test="marc:datafield[@tag=246]/marc:subfield[@code='h']">
9330                                 <form authority="gmd">
9331                                         <xsl:call-template name="chopBrackets">
9332                                                 <xsl:with-param name="chopString">
9333                                                         <xsl:value-of select="marc:datafield[@tag=246]/marc:subfield[@code='h']"></xsl:value-of>
9334                                                 </xsl:with-param>
9335                                         </xsl:call-template>
9336                                 </form>
9337                         </xsl:if>
9338                         <xsl:if test="marc:datafield[@tag=730]/marc:subfield[@code='h']">
9339                                 <form authority="gmd">
9340                                         <xsl:call-template name="chopBrackets">
9341                                                 <xsl:with-param name="chopString">
9342                                                         <xsl:value-of select="marc:datafield[@tag=730]/marc:subfield[@code='h']"></xsl:value-of>
9343                                                 </xsl:with-param>
9344                                         </xsl:call-template>
9345                                 </form>
9346                         </xsl:if>
9347                         <xsl:for-each select="marc:datafield[@tag=256]/marc:subfield[@code='a']">
9348                                 <form>
9349                                         <xsl:value-of select="."></xsl:value-of>
9350                                 </form>
9351                         </xsl:for-each>
9352                         <xsl:for-each select="marc:controlfield[@tag=007][substring(text(),1,1)='c']">
9353                                 <xsl:choose>
9354                                         <xsl:when test="substring(text(),14,1)='a'">
9355                                                 <reformattingQuality>access</reformattingQuality>
9356                                         </xsl:when>
9357                                         <xsl:when test="substring(text(),14,1)='p'">
9358                                                 <reformattingQuality>preservation</reformattingQuality>
9359                                         </xsl:when>
9360                                         <xsl:when test="substring(text(),14,1)='r'">
9361                                                 <reformattingQuality>replacement</reformattingQuality>
9362                                         </xsl:when>
9363                                 </xsl:choose>
9364                         </xsl:for-each>
9365                         <!--3.2 change tmee 007/01 -->
9366                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='b']">
9367                                 <form authority="smd">chip cartridge</form>
9368                         </xsl:if>
9369                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='c']">
9370                                 <form authority="smd">computer optical disc cartridge</form>
9371                         </xsl:if>
9372                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='j']">
9373                                 <form authority="smd">magnetic disc</form>
9374                         </xsl:if>
9375                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='m']">
9376                                 <form authority="smd">magneto-optical disc</form>
9377                         </xsl:if>
9378                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='o']">
9379                                 <form authority="smd">optical disc</form>
9380                         </xsl:if>
9381                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='r']">
9382                                 <form authority="smd">remote</form>
9383                         </xsl:if>
9384                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='a']">
9385                                 <form authority="smd">tape cartridge</form>
9386                         </xsl:if>
9387                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='f']">
9388                                 <form authority="smd">tape cassette</form>
9389                         </xsl:if>
9390                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='c'][substring(text(),2,1)='h']">
9391                                 <form authority="smd">tape reel</form>
9392                         </xsl:if>
9393                         
9394                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='a']">
9395                                 <form authority="smd">celestial globe</form>
9396                         </xsl:if>
9397                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='e']">
9398                                 <form authority="smd">earth moon globe</form>
9399                         </xsl:if>
9400                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='b']">
9401                                 <form authority="smd">planetary or lunar globe</form>
9402                         </xsl:if>
9403                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='d'][substring(text(),2,1)='c']">
9404                                 <form authority="smd">terrestrial globe</form>
9405                         </xsl:if>
9406                         
9407                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='o'][substring(text(),2,1)='o']">
9408                                 <form authority="smd">kit</form>
9409                         </xsl:if>
9410                         
9411                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='d']">
9412                                 <form authority="smd">atlas</form>
9413                         </xsl:if>
9414                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='g']">
9415                                 <form authority="smd">diagram</form>
9416                         </xsl:if>
9417                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='j']">
9418                                 <form authority="smd">map</form>
9419                         </xsl:if>
9420                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='q']">
9421                                 <form authority="smd">model</form>
9422                         </xsl:if>
9423                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='k']">
9424                                 <form authority="smd">profile</form>
9425                         </xsl:if>
9426                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='r']">
9427                                 <form authority="smd">remote-sensing image</form>
9428                         </xsl:if>
9429                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='s']">
9430                                 <form authority="smd">section</form>
9431                         </xsl:if>
9432                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='a'][substring(text(),2,1)='y']">
9433                                 <form authority="smd">view</form>
9434                         </xsl:if>
9435                         
9436                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='a']">
9437                                 <form authority="smd">aperture card</form>
9438                         </xsl:if>
9439                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='e']">
9440                                 <form authority="smd">microfiche</form>
9441                         </xsl:if>
9442                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='f']">
9443                                 <form authority="smd">microfiche cassette</form>
9444                         </xsl:if>
9445                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='b']">
9446                                 <form authority="smd">microfilm cartridge</form>
9447                         </xsl:if>
9448                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='c']">
9449                                 <form authority="smd">microfilm cassette</form>
9450                         </xsl:if>
9451                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='d']">
9452                                 <form authority="smd">microfilm reel</form>
9453                         </xsl:if>
9454                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='h'][substring(text(),2,1)='g']">
9455                                 <form authority="smd">microopaque</form>
9456                         </xsl:if>
9457                         
9458                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='c']">
9459                                 <form authority="smd">film cartridge</form>
9460                         </xsl:if>
9461                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='f']">
9462                                 <form authority="smd">film cassette</form>
9463                         </xsl:if>
9464                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='m'][substring(text(),2,1)='r']">
9465                                 <form authority="smd">film reel</form>
9466                         </xsl:if>
9467                         
9468                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='n']">
9469                                 <form authority="smd">chart</form>
9470                         </xsl:if>
9471                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='c']">
9472                                 <form authority="smd">collage</form>
9473                         </xsl:if>
9474                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='d']">
9475                                 <form authority="smd">drawing</form>
9476                         </xsl:if>
9477                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='o']">
9478                                 <form authority="smd">flash card</form>
9479                         </xsl:if>
9480                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='e']">
9481                                 <form authority="smd">painting</form>
9482                         </xsl:if>
9483                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='f']">
9484                                 <form authority="smd">photomechanical print</form>
9485                         </xsl:if>
9486                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='g']">
9487                                 <form authority="smd">photonegative</form>
9488                         </xsl:if>
9489                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='h']">
9490                                 <form authority="smd">photoprint</form>
9491                         </xsl:if>
9492                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='i']">
9493                                 <form authority="smd">picture</form>
9494                         </xsl:if>
9495                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='j']">
9496                                 <form authority="smd">print</form>
9497                         </xsl:if>
9498                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='k'][substring(text(),2,1)='l']">
9499                                 <form authority="smd">technical drawing</form>
9500                         </xsl:if>
9501                         
9502                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='q'][substring(text(),2,1)='q']">
9503                                 <form authority="smd">notated music</form>
9504                         </xsl:if>
9505                         
9506                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='d']">
9507                                 <form authority="smd">filmslip</form>
9508                         </xsl:if>
9509                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='c']">
9510                                 <form authority="smd">filmstrip cartridge</form>
9511                         </xsl:if>
9512                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='o']">
9513                                 <form authority="smd">filmstrip roll</form>
9514                         </xsl:if>
9515                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='f']">
9516                                 <form authority="smd">other filmstrip type</form>
9517                         </xsl:if>
9518                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='s']">
9519                                 <form authority="smd">slide</form>
9520                         </xsl:if>
9521                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='g'][substring(text(),2,1)='t']">
9522                                 <form authority="smd">transparency</form>
9523                         </xsl:if>
9524                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='r'][substring(text(),2,1)='r']">
9525                                 <form authority="smd">remote-sensing image</form>
9526                         </xsl:if>
9527                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='e']">
9528                                 <form authority="smd">cylinder</form>
9529                         </xsl:if>
9530                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='q']">
9531                                 <form authority="smd">roll</form>
9532                         </xsl:if>
9533                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='g']">
9534                                 <form authority="smd">sound cartridge</form>
9535                         </xsl:if>
9536                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='s']">
9537                                 <form authority="smd">sound cassette</form>
9538                         </xsl:if>
9539                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='d']">
9540                                 <form authority="smd">sound disc</form>
9541                         </xsl:if>
9542                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='t']">
9543                                 <form authority="smd">sound-tape reel</form>
9544                         </xsl:if>
9545                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='i']">
9546                                 <form authority="smd">sound-track film</form>
9547                         </xsl:if>
9548                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='s'][substring(text(),2,1)='w']">
9549                                 <form authority="smd">wire recording</form>
9550                         </xsl:if>
9551                         
9552                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='c']">
9553                                 <form authority="smd">braille</form>
9554                         </xsl:if>
9555                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='b']">
9556                                 <form authority="smd">combination</form>
9557                         </xsl:if>
9558                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='a']">
9559                                 <form authority="smd">moon</form>
9560                         </xsl:if>
9561                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='f'][substring(text(),2,1)='d']">
9562                                 <form authority="smd">tactile, with no writing system</form>
9563                         </xsl:if>
9564                         
9565                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='c']">
9566                                 <form authority="smd">braille</form>
9567                         </xsl:if>
9568                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='b']">
9569                                 <form authority="smd">large print</form>
9570                         </xsl:if>
9571                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='a']">
9572                                 <form authority="smd">regular print</form>
9573                         </xsl:if>
9574                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='t'][substring(text(),2,1)='d']">
9575                                 <form authority="smd">text in looseleaf binder</form>
9576                         </xsl:if>
9577                         
9578                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='c']">
9579                                 <form authority="smd">videocartridge</form>
9580                         </xsl:if>
9581                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='f']">
9582                                 <form authority="smd">videocassette</form>
9583                         </xsl:if>
9584                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='d']">
9585                                 <form authority="smd">videodisc</form>
9586                         </xsl:if>
9587                         <xsl:if test="marc:controlfield[@tag=007][substring(text(),1,1)='v'][substring(text(),2,1)='r']">
9588                                 <form authority="smd">videoreel</form>
9589                         </xsl:if>
9590                         
9591                         <xsl:for-each select="marc:datafield[@tag=856]/marc:subfield[@code='q'][string-length(.)>1]">
9592                                 <internetMediaType>
9593                                         <xsl:value-of select="."></xsl:value-of>
9594                                 </internetMediaType>
9595                         </xsl:for-each>
9596                         <xsl:for-each select="marc:datafield[@tag=300]">
9597                                 <extent>
9598                                         <xsl:call-template name="subfieldSelect">
9599                                                 <xsl:with-param name="codes">abce</xsl:with-param>
9600                                         </xsl:call-template>
9601                                 </extent>
9602                         </xsl:for-each>
9603                 </xsl:variable>
9604                 <xsl:if test="string-length(normalize-space($physicalDescription))">
9605                         <physicalDescription>
9606                                 <xsl:copy-of select="$physicalDescription"></xsl:copy-of>
9607                         </physicalDescription>
9608                 </xsl:if>
9609                 <xsl:for-each select="marc:datafield[@tag=520]">
9610                         <abstract>
9611                                 <xsl:call-template name="uri"></xsl:call-template>
9612                                 <xsl:call-template name="subfieldSelect">
9613                                         <xsl:with-param name="codes">ab</xsl:with-param>
9614                                 </xsl:call-template>
9615                         </abstract>
9616                 </xsl:for-each>
9617                 <xsl:for-each select="marc:datafield[@tag=505]">
9618                         <tableOfContents>
9619                                 <xsl:call-template name="uri"></xsl:call-template>
9620                                 <xsl:call-template name="subfieldSelect">
9621                                         <xsl:with-param name="codes">agrt</xsl:with-param>
9622                                 </xsl:call-template>
9623                         </tableOfContents>
9624                 </xsl:for-each>
9625                 <xsl:for-each select="marc:datafield[@tag=521]">
9626                         <targetAudience>
9627                                 <xsl:call-template name="subfieldSelect">
9628                                         <xsl:with-param name="codes">ab</xsl:with-param>
9629                                 </xsl:call-template>
9630                         </targetAudience>
9631                 </xsl:for-each>
9632                 <xsl:if test="$typeOf008='BK' or $typeOf008='CF' or $typeOf008='MU' or $typeOf008='VM'">
9633                         <xsl:variable name="controlField008-22" select="substring($controlField008,23,1)"></xsl:variable>
9634                         <xsl:choose>
9635                                 <!-- 01/04 fix -->
9636                                 <xsl:when test="$controlField008-22='d'">
9637                                         <targetAudience authority="marctarget">adolescent</targetAudience>
9638                                 </xsl:when>
9639                                 <xsl:when test="$controlField008-22='e'">
9640                                         <targetAudience authority="marctarget">adult</targetAudience>
9641                                 </xsl:when>
9642                                 <xsl:when test="$controlField008-22='g'">
9643                                         <targetAudience authority="marctarget">general</targetAudience>
9644                                 </xsl:when>
9645                                 <xsl:when test="$controlField008-22='b' or $controlField008-22='c' or $controlField008-22='j'">
9646                                         <targetAudience authority="marctarget">juvenile</targetAudience>
9647                                 </xsl:when>
9648                                 <xsl:when test="$controlField008-22='a'">
9649                                         <targetAudience authority="marctarget">preschool</targetAudience>
9650                                 </xsl:when>
9651                                 <xsl:when test="$controlField008-22='f'">
9652                                         <targetAudience authority="marctarget">specialized</targetAudience>
9653                                 </xsl:when>
9654                         </xsl:choose>
9655                 </xsl:if>
9656                 <xsl:for-each select="marc:datafield[@tag=245]/marc:subfield[@code='c']">
9657                         <note type="statement of responsibility">
9658                                 <xsl:value-of select="."></xsl:value-of>
9659                         </note>
9660                 </xsl:for-each>
9661                 <xsl:for-each select="marc:datafield[@tag=500]">
9662                         <note>
9663                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9664                                 <xsl:call-template name="uri"></xsl:call-template>
9665                         </note>
9666                 </xsl:for-each>
9667                 
9668                 <!--3.2 change tmee additional note fields-->
9669                 
9670                 <xsl:for-each select="marc:datafield[@tag=506]">
9671                         <note type="restrictions">
9672                                 <xsl:call-template name="uri"></xsl:call-template>
9673                                 <xsl:variable name="str">
9674                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9675                                                 <xsl:value-of select="."></xsl:value-of>
9676                                                 <xsl:text> </xsl:text>
9677                                         </xsl:for-each>
9678                                 </xsl:variable>
9679                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9680                         </note>
9681                 </xsl:for-each>
9682                 
9683                 <xsl:for-each select="marc:datafield[@tag=510]">
9684                         <note  type="citation/reference">
9685                                 <xsl:call-template name="uri"></xsl:call-template>
9686                                 <xsl:variable name="str">
9687                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9688                                                 <xsl:value-of select="."></xsl:value-of>
9689                                                 <xsl:text> </xsl:text>
9690                                         </xsl:for-each>
9691                                 </xsl:variable>
9692                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9693                         </note>
9694                 </xsl:for-each>
9695                 
9696                         
9697                 <xsl:for-each select="marc:datafield[@tag=511]">
9698                         <note type="performers">
9699                                 <xsl:call-template name="uri"></xsl:call-template>
9700                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9701                         </note>
9702                 </xsl:for-each>
9703                 <xsl:for-each select="marc:datafield[@tag=518]">
9704                         <note type="venue">
9705                                 <xsl:call-template name="uri"></xsl:call-template>
9706                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9707                         </note>
9708                 </xsl:for-each>
9709                 
9710                 <xsl:for-each select="marc:datafield[@tag=530]">
9711                         <note  type="additional physical form">
9712                                 <xsl:call-template name="uri"></xsl:call-template>
9713                                 <xsl:variable name="str">
9714                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9715                                                 <xsl:value-of select="."></xsl:value-of>
9716                                                 <xsl:text> </xsl:text>
9717                                         </xsl:for-each>
9718                                 </xsl:variable>
9719                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9720                         </note>
9721                 </xsl:for-each>
9722                 
9723                 <xsl:for-each select="marc:datafield[@tag=533]">
9724                         <note  type="reproduction">
9725                                 <xsl:call-template name="uri"></xsl:call-template>
9726                                 <xsl:variable name="str">
9727                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9728                                                 <xsl:value-of select="."></xsl:value-of>
9729                                                 <xsl:text> </xsl:text>
9730                                         </xsl:for-each>
9731                                 </xsl:variable>
9732                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9733                         </note>
9734                 </xsl:for-each>
9735                 
9736                 <xsl:for-each select="marc:datafield[@tag=534]">
9737                         <note  type="original version">
9738                                 <xsl:call-template name="uri"></xsl:call-template>
9739                                 <xsl:variable name="str">
9740                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9741                                                 <xsl:value-of select="."></xsl:value-of>
9742                                                 <xsl:text> </xsl:text>
9743                                         </xsl:for-each>
9744                                 </xsl:variable>
9745                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9746                         </note>
9747                 </xsl:for-each>
9748                 
9749                 <xsl:for-each select="marc:datafield[@tag=538]">
9750                         <note  type="system details">
9751                                 <xsl:call-template name="uri"></xsl:call-template>
9752                                 <xsl:variable name="str">
9753                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9754                                                 <xsl:value-of select="."></xsl:value-of>
9755                                                 <xsl:text> </xsl:text>
9756                                         </xsl:for-each>
9757                                 </xsl:variable>
9758                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9759                         </note>
9760                 </xsl:for-each>
9761                 
9762                 <xsl:for-each select="marc:datafield[@tag=583]">
9763                         <note type="action">
9764                                 <xsl:call-template name="uri"></xsl:call-template>
9765                                 <xsl:variable name="str">
9766                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9767                                                 <xsl:value-of select="."></xsl:value-of>
9768                                                 <xsl:text> </xsl:text>
9769                                         </xsl:for-each>
9770                                 </xsl:variable>
9771                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9772                         </note>
9773                 </xsl:for-each>
9774                 
9775
9776                 
9777                 
9778                 
9779                 <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]">
9780                         <note>
9781                                 <xsl:call-template name="uri"></xsl:call-template>
9782                                 <xsl:variable name="str">
9783                                         <xsl:for-each select="marc:subfield[@code!='6' or @code!='8']">
9784                                                 <xsl:value-of select="."></xsl:value-of>
9785                                                 <xsl:text> </xsl:text>
9786                                         </xsl:for-each>
9787                                 </xsl:variable>
9788                                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
9789                         </note>
9790                 </xsl:for-each>
9791                 <xsl:for-each select="marc:datafield[@tag=034][marc:subfield[@code='d' or @code='e' or @code='f' or @code='g']]">
9792                         <subject>
9793                                 <cartographics>
9794                                         <coordinates>
9795                                                 <xsl:call-template name="subfieldSelect">
9796                                                         <xsl:with-param name="codes">defg</xsl:with-param>
9797                                                 </xsl:call-template>
9798                                         </coordinates>
9799                                 </cartographics>
9800                         </subject>
9801                 </xsl:for-each>
9802                 <xsl:for-each select="marc:datafield[@tag=043]">
9803                         <subject>
9804                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
9805                                         <geographicCode>
9806                                                 <xsl:attribute name="authority">
9807                                                         <xsl:if test="@code='a'">
9808                                                                 <xsl:text>marcgac</xsl:text>
9809                                                         </xsl:if>
9810                                                         <xsl:if test="@code='b'">
9811                                                                 <xsl:value-of select="following-sibling::marc:subfield[@code=2]"></xsl:value-of>
9812                                                         </xsl:if>
9813                                                         <xsl:if test="@code='c'">
9814                                                                 <xsl:text>iso3166</xsl:text>
9815                                                         </xsl:if>
9816                                                 </xsl:attribute>
9817                                                 <xsl:value-of select="self::marc:subfield"></xsl:value-of>
9818                                         </geographicCode>
9819                                 </xsl:for-each>
9820                         </subject>
9821                 </xsl:for-each>
9822                 <!-- tmee 2006/11/27 -->
9823                 <xsl:for-each select="marc:datafield[@tag=255]">
9824                         <subject>
9825                                 <xsl:for-each select="marc:subfield[@code='a' or @code='b' or @code='c']">
9826                                 <cartographics>
9827                                         <xsl:if test="@code='a'">
9828                                                 <scale>
9829                                                         <xsl:value-of select="."></xsl:value-of>
9830                                                 </scale>
9831                                         </xsl:if>
9832                                         <xsl:if test="@code='b'">
9833                                                 <projection>
9834                                                         <xsl:value-of select="."></xsl:value-of>
9835                                                 </projection>
9836                                         </xsl:if>
9837                                         <xsl:if test="@code='c'">
9838                                                 <coordinates>
9839                                                         <xsl:value-of select="."></xsl:value-of>
9840                                                 </coordinates>
9841                                         </xsl:if>
9842                                 </cartographics>
9843                                 </xsl:for-each>
9844                         </subject>
9845                 </xsl:for-each>
9846                                 
9847                 <xsl:apply-templates select="marc:datafield[653 >= @tag and @tag >= 600]"></xsl:apply-templates>
9848                 <xsl:apply-templates select="marc:datafield[@tag=656]"></xsl:apply-templates>
9849                 <xsl:for-each select="marc:datafield[@tag=752]">
9850                         <subject>
9851                                 <hierarchicalGeographic>
9852                                         <xsl:for-each select="marc:subfield[@code='a']">
9853                                                 <country>
9854                                                         <xsl:call-template name="chopPunctuation">
9855                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9856                                                         </xsl:call-template>
9857                                                 </country>
9858                                         </xsl:for-each>
9859                                         <xsl:for-each select="marc:subfield[@code='b']">
9860                                                 <state>
9861                                                         <xsl:call-template name="chopPunctuation">
9862                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9863                                                         </xsl:call-template>
9864                                                 </state>
9865                                         </xsl:for-each>
9866                                         <xsl:for-each select="marc:subfield[@code='c']">
9867                                                 <county>
9868                                                         <xsl:call-template name="chopPunctuation">
9869                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9870                                                         </xsl:call-template>
9871                                                 </county>
9872                                         </xsl:for-each>
9873                                         <xsl:for-each select="marc:subfield[@code='d']">
9874                                                 <city>
9875                                                         <xsl:call-template name="chopPunctuation">
9876                                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
9877                                                         </xsl:call-template>
9878                                                 </city>
9879                                         </xsl:for-each>
9880                                 </hierarchicalGeographic>
9881                         </subject>
9882                 </xsl:for-each>
9883                 <xsl:for-each select="marc:datafield[@tag=045][marc:subfield[@code='b']]">
9884                         <subject>
9885                                 <xsl:choose>
9886                                         <xsl:when test="@ind1=2">
9887                                                 <temporal encoding="iso8601" point="start">
9888                                                         <xsl:call-template name="chopPunctuation">
9889                                                                 <xsl:with-param name="chopString">
9890                                                                         <xsl:value-of select="marc:subfield[@code='b'][1]"></xsl:value-of>
9891                                                                 </xsl:with-param>
9892                                                         </xsl:call-template>
9893                                                 </temporal>
9894                                                 <temporal encoding="iso8601" point="end">
9895                                                         <xsl:call-template name="chopPunctuation">
9896                                                                 <xsl:with-param name="chopString">
9897                                                                         <xsl:value-of select="marc:subfield[@code='b'][2]"></xsl:value-of>
9898                                                                 </xsl:with-param>
9899                                                         </xsl:call-template>
9900                                                 </temporal>
9901                                         </xsl:when>
9902                                         <xsl:otherwise>
9903                                                 <xsl:for-each select="marc:subfield[@code='b']">
9904                                                         <temporal encoding="iso8601">
9905                                                                 <xsl:call-template name="chopPunctuation">
9906                                                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
9907                                                                 </xsl:call-template>
9908                                                         </temporal>
9909                                                 </xsl:for-each>
9910                                         </xsl:otherwise>
9911                                 </xsl:choose>
9912                         </subject>
9913                 </xsl:for-each>
9914                 <xsl:for-each select="marc:datafield[@tag=050]">
9915                         <xsl:for-each select="marc:subfield[@code='b']">
9916                                 <classification authority="lcc">
9917                                         <xsl:if test="../marc:subfield[@code='3']">
9918                                                 <xsl:attribute name="displayLabel">
9919                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
9920                                                 </xsl:attribute>
9921                                         </xsl:if>
9922                                         <xsl:value-of select="preceding-sibling::marc:subfield[@code='a'][1]"></xsl:value-of>
9923                                         <xsl:text> </xsl:text>
9924                                         <xsl:value-of select="text()"></xsl:value-of>
9925                                 </classification>
9926                         </xsl:for-each>
9927                         <xsl:for-each select="marc:subfield[@code='a'][not(following-sibling::marc:subfield[@code='b'])]">
9928                                 <classification authority="lcc">
9929                                         <xsl:if test="../marc:subfield[@code='3']">
9930                                                 <xsl:attribute name="displayLabel">
9931                                                         <xsl:value-of select="../marc:subfield[@code='3']"></xsl:value-of>
9932                                                 </xsl:attribute>
9933                                         </xsl:if>
9934                                         <xsl:value-of select="text()"></xsl:value-of>
9935                                 </classification>
9936                         </xsl:for-each>
9937                 </xsl:for-each>
9938                 <xsl:for-each select="marc:datafield[@tag=082]">
9939                         <classification authority="ddc">
9940                                 <xsl:if test="marc:subfield[@code='2']">
9941                                         <xsl:attribute name="edition">
9942                                                 <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9943                                         </xsl:attribute>
9944                                 </xsl:if>
9945                                 <xsl:call-template name="subfieldSelect">
9946                                         <xsl:with-param name="codes">ab</xsl:with-param>
9947                                 </xsl:call-template>
9948                         </classification>
9949                 </xsl:for-each>
9950                 <xsl:for-each select="marc:datafield[@tag=080]">
9951                         <classification authority="udc">
9952                                 <xsl:call-template name="subfieldSelect">
9953                                         <xsl:with-param name="codes">abx</xsl:with-param>
9954                                 </xsl:call-template>
9955                         </classification>
9956                 </xsl:for-each>
9957                 <xsl:for-each select="marc:datafield[@tag=060]">
9958                         <classification authority="nlm">
9959                                 <xsl:call-template name="subfieldSelect">
9960                                         <xsl:with-param name="codes">ab</xsl:with-param>
9961                                 </xsl:call-template>
9962                         </classification>
9963                 </xsl:for-each>
9964                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=0]">
9965                         <classification authority="sudocs">
9966                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9967                         </classification>
9968                 </xsl:for-each>
9969                 <xsl:for-each select="marc:datafield[@tag=086][@ind1=1]">
9970                         <classification authority="candoc">
9971                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9972                         </classification>
9973                 </xsl:for-each>
9974                 <xsl:for-each select="marc:datafield[@tag=086]">
9975                         <classification>
9976                                 <xsl:attribute name="authority">
9977                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9978                                 </xsl:attribute>
9979                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
9980                         </classification>
9981                 </xsl:for-each>
9982                 <xsl:for-each select="marc:datafield[@tag=084]">
9983                         <classification>
9984                                 <xsl:attribute name="authority">
9985                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
9986                                 </xsl:attribute>
9987                                 <xsl:call-template name="subfieldSelect">
9988                                         <xsl:with-param name="codes">ab</xsl:with-param>
9989                                 </xsl:call-template>
9990                         </classification>
9991                 </xsl:for-each>
9992                 <xsl:for-each select="marc:datafield[@tag=440]">
9993                         <relatedItem type="series">
9994                                 <titleInfo>
9995                                         <title>
9996                                                 <xsl:call-template name="chopPunctuation">
9997                                                         <xsl:with-param name="chopString">
9998                                                                 <xsl:call-template name="subfieldSelect">
9999                                                                         <xsl:with-param name="codes">av</xsl:with-param>
10000                                                                 </xsl:call-template>
10001                                                         </xsl:with-param>
10002                                                 </xsl:call-template>
10003                                         </title>
10004                                         <xsl:call-template name="part"></xsl:call-template>
10005                                 </titleInfo>
10006                         </relatedItem>
10007                 </xsl:for-each>
10008                 <xsl:for-each select="marc:datafield[@tag=490][@ind1=0]">
10009                         <relatedItem type="series">
10010                                 <titleInfo>
10011                                         <title>
10012                                                 <xsl:call-template name="chopPunctuation">
10013                                                         <xsl:with-param name="chopString">
10014                                                                 <xsl:call-template name="subfieldSelect">
10015                                                                         <xsl:with-param name="codes">av</xsl:with-param>
10016                                                                 </xsl:call-template>
10017                                                         </xsl:with-param>
10018                                                 </xsl:call-template>
10019                                         </title>
10020                                         <xsl:call-template name="part"></xsl:call-template>
10021                                 </titleInfo>
10022                         </relatedItem>
10023                 </xsl:for-each>
10024                 <xsl:for-each select="marc:datafield[@tag=510]">
10025                         <relatedItem type="isReferencedBy">
10026                                 <note>
10027                                         <xsl:call-template name="subfieldSelect">
10028                                                 <xsl:with-param name="codes">abcx3</xsl:with-param>
10029                                         </xsl:call-template>
10030                                 </note>
10031                         </relatedItem>
10032                 </xsl:for-each>
10033                 <xsl:for-each select="marc:datafield[@tag=534]">
10034                         <relatedItem type="original">
10035                                 <xsl:call-template name="relatedTitle"></xsl:call-template>
10036                                 <xsl:call-template name="relatedName"></xsl:call-template>
10037                                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
10038                                         <originInfo>
10039                                                 <xsl:for-each select="marc:subfield[@code='c']">
10040                                                         <publisher>
10041                                                                 <xsl:value-of select="."></xsl:value-of>
10042                                                         </publisher>
10043                                                 </xsl:for-each>
10044                                                 <xsl:for-each select="marc:subfield[@code='b']">
10045                                                         <edition>
10046                                                                 <xsl:value-of select="."></xsl:value-of>
10047                                                         </edition>
10048                                                 </xsl:for-each>
10049                                         </originInfo>
10050                                 </xsl:if>
10051                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10052                                 <xsl:for-each select="marc:subfield[@code='z']">
10053                                         <identifier type="isbn">
10054                                                 <xsl:value-of select="."></xsl:value-of>
10055                                         </identifier>
10056                                 </xsl:for-each>
10057                                 <xsl:call-template name="relatedNote"></xsl:call-template>
10058                         </relatedItem>
10059                 </xsl:for-each>
10060                 <xsl:for-each select="marc:datafield[@tag=700][marc:subfield[@code='t']]">
10061                         <relatedItem>
10062                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10063                                 <titleInfo>
10064                                         <title>
10065                                                 <xsl:call-template name="chopPunctuation">
10066                                                         <xsl:with-param name="chopString">
10067                                                                 <xsl:call-template name="specialSubfieldSelect">
10068                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10069                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10070                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10071                                                                 </xsl:call-template>
10072                                                         </xsl:with-param>
10073                                                 </xsl:call-template>
10074                                         </title>
10075                                         <xsl:call-template name="part"></xsl:call-template>
10076                                 </titleInfo>
10077                                 <name type="personal">
10078                                         <namePart>
10079                                                 <xsl:call-template name="specialSubfieldSelect">
10080                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
10081                                                         <xsl:with-param name="axis">t</xsl:with-param>
10082                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
10083                                                 </xsl:call-template>
10084                                         </namePart>
10085                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
10086                                         <xsl:call-template name="nameDate"></xsl:call-template>
10087                                         <xsl:call-template name="role"></xsl:call-template>
10088                                 </name>
10089                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10090                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10091                         </relatedItem>
10092                 </xsl:for-each>
10093                 <xsl:for-each select="marc:datafield[@tag=710][marc:subfield[@code='t']]">
10094                         <relatedItem>
10095                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10096                                 <titleInfo>
10097                                         <title>
10098                                                 <xsl:call-template name="chopPunctuation">
10099                                                         <xsl:with-param name="chopString">
10100                                                                 <xsl:call-template name="specialSubfieldSelect">
10101                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10102                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10103                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
10104                                                                 </xsl:call-template>
10105                                                         </xsl:with-param>
10106                                                 </xsl:call-template>
10107                                         </title>
10108                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10109                                 </titleInfo>
10110                                 <name type="corporate">
10111                                         <xsl:for-each select="marc:subfield[@code='a']">
10112                                                 <namePart>
10113                                                         <xsl:value-of select="."></xsl:value-of>
10114                                                 </namePart>
10115                                         </xsl:for-each>
10116                                         <xsl:for-each select="marc:subfield[@code='b']">
10117                                                 <namePart>
10118                                                         <xsl:value-of select="."></xsl:value-of>
10119                                                 </namePart>
10120                                         </xsl:for-each>
10121                                         <xsl:variable name="tempNamePart">
10122                                                 <xsl:call-template name="specialSubfieldSelect">
10123                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
10124                                                         <xsl:with-param name="axis">t</xsl:with-param>
10125                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
10126                                                 </xsl:call-template>
10127                                         </xsl:variable>
10128                                         <xsl:if test="normalize-space($tempNamePart)">
10129                                                 <namePart>
10130                                                         <xsl:value-of select="$tempNamePart"></xsl:value-of>
10131                                                 </namePart>
10132                                         </xsl:if>
10133                                         <xsl:call-template name="role"></xsl:call-template>
10134                                 </name>
10135                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10136                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10137                         </relatedItem>
10138                 </xsl:for-each>
10139                 <xsl:for-each select="marc:datafield[@tag=711][marc:subfield[@code='t']]">
10140                         <relatedItem>
10141                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10142                                 <titleInfo>
10143                                         <title>
10144                                                 <xsl:call-template name="chopPunctuation">
10145                                                         <xsl:with-param name="chopString">
10146                                                                 <xsl:call-template name="specialSubfieldSelect">
10147                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
10148                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10149                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10150                                                                 </xsl:call-template>
10151                                                         </xsl:with-param>
10152                                                 </xsl:call-template>
10153                                         </title>
10154                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10155                                 </titleInfo>
10156                                 <name type="conference">
10157                                         <namePart>
10158                                                 <xsl:call-template name="specialSubfieldSelect">
10159                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
10160                                                         <xsl:with-param name="axis">t</xsl:with-param>
10161                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
10162                                                 </xsl:call-template>
10163                                         </namePart>
10164                                 </name>
10165                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10166                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10167                         </relatedItem>
10168                 </xsl:for-each>
10169                 <xsl:for-each select="marc:datafield[@tag=730][@ind2=2]">
10170                         <relatedItem>
10171                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10172                                 <titleInfo>
10173                                         <title>
10174                                                 <xsl:call-template name="chopPunctuation">
10175                                                         <xsl:with-param name="chopString">
10176                                                                 <xsl:call-template name="subfieldSelect">
10177                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
10178                                                                 </xsl:call-template>
10179                                                         </xsl:with-param>
10180                                                 </xsl:call-template>
10181                                         </title>
10182                                         <xsl:call-template name="part"></xsl:call-template>
10183                                 </titleInfo>
10184                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10185                                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10186                         </relatedItem>
10187                 </xsl:for-each>
10188                 <xsl:for-each select="marc:datafield[@tag=740][@ind2=2]">
10189                         <relatedItem>
10190                                 <xsl:call-template name="constituentOrRelatedType"></xsl:call-template>
10191                                 <titleInfo>
10192                                         <title>
10193                                                 <xsl:call-template name="chopPunctuation">
10194                                                         <xsl:with-param name="chopString">
10195                                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
10196                                                         </xsl:with-param>
10197                                                 </xsl:call-template>
10198                                         </title>
10199                                         <xsl:call-template name="part"></xsl:call-template>
10200                                 </titleInfo>
10201                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10202                         </relatedItem>
10203                 </xsl:for-each>
10204                 <xsl:for-each select="marc:datafield[@tag=760]|marc:datafield[@tag=762]">
10205                         <relatedItem type="series">
10206                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10207                         </relatedItem>
10208                 </xsl:for-each>
10209                 <xsl:for-each select="marc:datafield[@tag=765]|marc:datafield[@tag=767]|marc:datafield[@tag=777]|marc:datafield[@tag=787]">
10210                         <relatedItem>
10211                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10212                         </relatedItem>
10213                 </xsl:for-each>
10214                 <xsl:for-each select="marc:datafield[@tag=775]">
10215                         <relatedItem type="otherVersion">
10216                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10217                         </relatedItem>
10218                 </xsl:for-each>
10219                 <xsl:for-each select="marc:datafield[@tag=770]|marc:datafield[@tag=774]">
10220                         <relatedItem type="constituent">
10221                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10222                         </relatedItem>
10223                 </xsl:for-each>
10224                 <xsl:for-each select="marc:datafield[@tag=772]|marc:datafield[@tag=773]">
10225                         <relatedItem type="host">
10226                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10227                         </relatedItem>
10228                 </xsl:for-each>
10229                 <xsl:for-each select="marc:datafield[@tag=776]">
10230                         <relatedItem type="otherFormat">
10231                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10232                         </relatedItem>
10233                 </xsl:for-each>
10234                 <xsl:for-each select="marc:datafield[@tag=780]">
10235                         <relatedItem type="preceding">
10236                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10237                         </relatedItem>
10238                 </xsl:for-each>
10239                 <xsl:for-each select="marc:datafield[@tag=785]">
10240                         <relatedItem type="succeeding">
10241                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10242                         </relatedItem>
10243                 </xsl:for-each>
10244                 <xsl:for-each select="marc:datafield[@tag=786]">
10245                         <relatedItem type="original">
10246                                 <xsl:call-template name="relatedItem76X-78X"></xsl:call-template>
10247                         </relatedItem>
10248                 </xsl:for-each>
10249                 <xsl:for-each select="marc:datafield[@tag=800]">
10250                         <relatedItem type="series">
10251                                 <titleInfo>
10252                                         <title>
10253                                                 <xsl:call-template name="chopPunctuation">
10254                                                         <xsl:with-param name="chopString">
10255                                                                 <xsl:call-template name="specialSubfieldSelect">
10256                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10257                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10258                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10259                                                                 </xsl:call-template>
10260                                                         </xsl:with-param>
10261                                                 </xsl:call-template>
10262                                         </title>
10263                                         <xsl:call-template name="part"></xsl:call-template>
10264                                 </titleInfo>
10265                                 <name type="personal">
10266                                         <namePart>
10267                                                 <xsl:call-template name="chopPunctuation">
10268                                                         <xsl:with-param name="chopString">
10269                                                                 <xsl:call-template name="specialSubfieldSelect">
10270                                                                         <xsl:with-param name="anyCodes">aq</xsl:with-param>
10271                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10272                                                                         <xsl:with-param name="beforeCodes">g</xsl:with-param>
10273                                                                 </xsl:call-template>
10274                                                         </xsl:with-param>
10275                                                 </xsl:call-template>
10276                                         </namePart>
10277                                         <xsl:call-template name="termsOfAddress"></xsl:call-template>
10278                                         <xsl:call-template name="nameDate"></xsl:call-template>
10279                                         <xsl:call-template name="role"></xsl:call-template>
10280                                 </name>
10281                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10282                         </relatedItem>
10283                 </xsl:for-each>
10284                 <xsl:for-each select="marc:datafield[@tag=810]">
10285                         <relatedItem type="series">
10286                                 <titleInfo>
10287                                         <title>
10288                                                 <xsl:call-template name="chopPunctuation">
10289                                                         <xsl:with-param name="chopString">
10290                                                                 <xsl:call-template name="specialSubfieldSelect">
10291                                                                         <xsl:with-param name="anyCodes">tfklmorsv</xsl:with-param>
10292                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10293                                                                         <xsl:with-param name="afterCodes">dg</xsl:with-param>
10294                                                                 </xsl:call-template>
10295                                                         </xsl:with-param>
10296                                                 </xsl:call-template>
10297                                         </title>
10298                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10299                                 </titleInfo>
10300                                 <name type="corporate">
10301                                         <xsl:for-each select="marc:subfield[@code='a']">
10302                                                 <namePart>
10303                                                         <xsl:value-of select="."></xsl:value-of>
10304                                                 </namePart>
10305                                         </xsl:for-each>
10306                                         <xsl:for-each select="marc:subfield[@code='b']">
10307                                                 <namePart>
10308                                                         <xsl:value-of select="."></xsl:value-of>
10309                                                 </namePart>
10310                                         </xsl:for-each>
10311                                         <namePart>
10312                                                 <xsl:call-template name="specialSubfieldSelect">
10313                                                         <xsl:with-param name="anyCodes">c</xsl:with-param>
10314                                                         <xsl:with-param name="axis">t</xsl:with-param>
10315                                                         <xsl:with-param name="beforeCodes">dgn</xsl:with-param>
10316                                                 </xsl:call-template>
10317                                         </namePart>
10318                                         <xsl:call-template name="role"></xsl:call-template>
10319                                 </name>
10320                                 <xsl:call-template name="relatedForm"></xsl:call-template>
10321                         </relatedItem>
10322                 </xsl:for-each>
10323                 <xsl:for-each select="marc:datafield[@tag=811]">
10324                         <relatedItem type="series">
10325                                 <titleInfo>
10326                                         <title>
10327                                                 <xsl:call-template name="chopPunctuation">
10328                                                         <xsl:with-param name="chopString">
10329                                                                 <xsl:call-template name="specialSubfieldSelect">
10330                                                                         <xsl:with-param name="anyCodes">tfklsv</xsl:with-param>
10331                                                                         <xsl:with-param name="axis">t</xsl:with-param>
10332                                                                         <xsl:with-param name="afterCodes">g</xsl:with-param>
10333                                                                 </xsl:call-template>
10334                                                         </xsl:with-param>
10335                                                 </xsl:call-template>
10336                                         </title>
10337                                         <xsl:call-template name="relatedPartNumName"/>
10338                                 </titleInfo>
10339                                 <name type="conference">
10340                                         <namePart>
10341                                                 <xsl:call-template name="specialSubfieldSelect">
10342                                                         <xsl:with-param name="anyCodes">aqdc</xsl:with-param>
10343                                                         <xsl:with-param name="axis">t</xsl:with-param>
10344                                                         <xsl:with-param name="beforeCodes">gn</xsl:with-param>
10345                                                 </xsl:call-template>
10346                                         </namePart>
10347                                         <xsl:call-template name="role"/>
10348                                 </name>
10349                                 <xsl:call-template name="relatedForm"/>
10350                         </relatedItem>
10351                 </xsl:for-each>
10352                 <xsl:for-each select="marc:datafield[@tag='830']">
10353                         <relatedItem type="series">
10354                                 <titleInfo>
10355                                         <title>
10356                                                 <xsl:call-template name="chopPunctuation">
10357                                                         <xsl:with-param name="chopString">
10358                                                                 <xsl:call-template name="subfieldSelect">
10359                                                                         <xsl:with-param name="codes">adfgklmorsv</xsl:with-param>
10360                                                                 </xsl:call-template>
10361                                                         </xsl:with-param>
10362                                                 </xsl:call-template>
10363                                         </title>
10364                                         <xsl:call-template name="part"/>
10365                                 </titleInfo>
10366                                 <xsl:call-template name="relatedForm"/>
10367                         </relatedItem>
10368                 </xsl:for-each>
10369                 <xsl:for-each select="marc:datafield[@tag='856'][@ind2='2']/marc:subfield[@code='q']">
10370                         <relatedItem>
10371                                 <internetMediaType>
10372                                         <xsl:value-of select="."/>
10373                                 </internetMediaType>
10374                         </relatedItem>
10375                 </xsl:for-each>
10376                 <xsl:for-each select="marc:datafield[@tag='020']">
10377                         <xsl:call-template name="isInvalid">
10378                                 <xsl:with-param name="type">isbn</xsl:with-param>
10379                         </xsl:call-template>
10380                         <xsl:if test="marc:subfield[@code='a']">
10381                                 <identifier type="isbn">
10382                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10383                                 </identifier>
10384                         </xsl:if>
10385                 </xsl:for-each>
10386                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='0']">
10387                         <xsl:call-template name="isInvalid">
10388                                 <xsl:with-param name="type">isrc</xsl:with-param>
10389                         </xsl:call-template>
10390                         <xsl:if test="marc:subfield[@code='a']">
10391                                 <identifier type="isrc">
10392                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10393                                 </identifier>
10394                         </xsl:if>
10395                 </xsl:for-each>
10396                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='2']">
10397                         <xsl:call-template name="isInvalid">
10398                                 <xsl:with-param name="type">ismn</xsl:with-param>
10399                         </xsl:call-template>
10400                         <xsl:if test="marc:subfield[@code='a']">
10401                                 <identifier type="ismn">
10402                                         <xsl:value-of select="marc:subfield[@code='a']"/>
10403                                 </identifier>
10404                         </xsl:if>
10405                 </xsl:for-each>
10406                 <xsl:for-each select="marc:datafield[@tag='024'][@ind1='4']">
10407                         <xsl:call-template name="isInvalid">
10408                                 <xsl:with-param name="type">sici</xsl:with-param>
10409                         </xsl:call-template>
10410                         <identifier type="sici">
10411                                 <xsl:call-template name="subfieldSelect">
10412                                         <xsl:with-param name="codes">ab</xsl:with-param>
10413                                 </xsl:call-template>
10414                         </identifier>
10415                 </xsl:for-each>
10416                 <xsl:for-each select="marc:datafield[@tag='022']">
10417                         <xsl:call-template name="isInvalid">
10418                                 <xsl:with-param name="type">issn</xsl:with-param>
10419                         </xsl:call-template>
10420                         <identifier type="issn">
10421                                 <xsl:value-of select="marc:subfield[@code='a']"/>
10422                         </identifier>
10423                 </xsl:for-each>
10424                 <xsl:for-each select="marc:datafield[@tag='010']">
10425                         <xsl:call-template name="isInvalid">
10426                                 <xsl:with-param name="type">lccn</xsl:with-param>
10427                         </xsl:call-template>
10428                         <identifier type="lccn">
10429                                 <xsl:value-of select="normalize-space(marc:subfield[@code='a'])"/>
10430                         </identifier>
10431                 </xsl:for-each>
10432                 <xsl:for-each select="marc:datafield[@tag='028']">
10433                         <identifier>
10434                                 <xsl:attribute name="type">
10435                                         <xsl:choose>
10436                                                 <xsl:when test="@ind1='0'">issue number</xsl:when>
10437                                                 <xsl:when test="@ind1='1'">matrix number</xsl:when>
10438                                                 <xsl:when test="@ind1='2'">music plate</xsl:when>
10439                                                 <xsl:when test="@ind1='3'">music publisher</xsl:when>
10440                                                 <xsl:when test="@ind1='4'">videorecording identifier</xsl:when>
10441                                         </xsl:choose>
10442                                 </xsl:attribute>
10443                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 028 -->
10444                                 <xsl:call-template name="subfieldSelect">
10445                                         <xsl:with-param name="codes">
10446                                                 <xsl:choose>
10447                                                         <xsl:when test="@ind1='0'">ba</xsl:when>
10448                                                         <xsl:otherwise>ab</xsl:otherwise>
10449                                                 </xsl:choose>
10450                                         </xsl:with-param>
10451                                 </xsl:call-template>
10452                         </identifier>
10453                 </xsl:for-each>
10454                 <xsl:for-each select="marc:datafield[@tag='037']">
10455                         <identifier type="stock number">
10456                                 <!--<xsl:call-template name="isInvalid"/>--> <!-- no $z in 037 -->
10457                                 <xsl:call-template name="subfieldSelect">
10458                                         <xsl:with-param name="codes">ab</xsl:with-param>
10459                                 </xsl:call-template>
10460                         </identifier>
10461                 </xsl:for-each>
10462                 <xsl:for-each select="marc:datafield[@tag='856'][marc:subfield[@code='u']]">
10463                         <identifier>
10464                                 <xsl:attribute name="type">
10465                                         <xsl:choose>
10466                                                 <xsl:when test="starts-with(marc:subfield[@code='u'],'urn:doi') or starts-with(marc:subfield[@code='u'],'doi')">doi</xsl:when>
10467                                                 <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>
10468                                                 <xsl:otherwise>uri</xsl:otherwise>
10469                                         </xsl:choose>
10470                                 </xsl:attribute>
10471                                 <xsl:choose>
10472                                         <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') ">
10473                                                 <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
10474                                         </xsl:when>
10475                                         <xsl:otherwise>
10476                                                 <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
10477                                         </xsl:otherwise>
10478                                 </xsl:choose>
10479                         </identifier>
10480                         <xsl:if test="starts-with(marc:subfield[@code='u'],'urn:hdl') or starts-with(marc:subfield[@code='u'],'hdl')">
10481                                 <identifier type="hdl">
10482                                         <xsl:if test="marc:subfield[@code='y' or @code='3' or @code='z']">
10483                                                 <xsl:attribute name="displayLabel">
10484                                                         <xsl:call-template name="subfieldSelect">
10485                                                                 <xsl:with-param name="codes">y3z</xsl:with-param>
10486                                                         </xsl:call-template>
10487                                                 </xsl:attribute>
10488                                         </xsl:if>
10489                                         <xsl:value-of select="concat('hdl:',substring-after(marc:subfield[@code='u'],'http://hdl.loc.gov/'))"></xsl:value-of>
10490                                 </identifier>
10491                         </xsl:if>
10492                 </xsl:for-each>
10493                 <xsl:for-each select="marc:datafield[@tag=024][@ind1=1]">
10494                         <identifier type="upc">
10495                                 <xsl:call-template name="isInvalid"/>
10496                                 <xsl:value-of select="marc:subfield[@code='a']"/>
10497                         </identifier>
10498                 </xsl:for-each>
10499                 <!-- 1/04 fix added $y -->
10500                 <xsl:for-each select="marc:datafield[@tag=856][marc:subfield[@code='u']]">
10501                         <location>
10502                                 <url>
10503                                         <xsl:if test="marc:subfield[@code='y' or @code='3']">
10504                                                 <xsl:attribute name="displayLabel">
10505                                                         <xsl:call-template name="subfieldSelect">
10506                                                                 <xsl:with-param name="codes">y3</xsl:with-param>
10507                                                         </xsl:call-template>
10508                                                 </xsl:attribute>
10509                                         </xsl:if>
10510                                         <xsl:if test="marc:subfield[@code='z' ]">
10511                                                 <xsl:attribute name="note">
10512                                                         <xsl:call-template name="subfieldSelect">
10513                                                                 <xsl:with-param name="codes">z</xsl:with-param>
10514                                                         </xsl:call-template>
10515                                                 </xsl:attribute>
10516                                         </xsl:if>
10517                                         <xsl:value-of select="marc:subfield[@code='u']"></xsl:value-of>
10518
10519                                 </url>
10520                         </location>
10521                 </xsl:for-each>
10522                         
10523                         <!-- 3.2 change tmee 856z  -->
10524
10525                 
10526                 <xsl:for-each select="marc:datafield[@tag=852]">
10527                         <location>
10528                                 <physicalLocation>
10529                                         <xsl:call-template name="displayLabel"></xsl:call-template>
10530                                         <xsl:call-template name="subfieldSelect">
10531                                                 <xsl:with-param name="codes">abje</xsl:with-param>
10532                                         </xsl:call-template>
10533                                 </physicalLocation>
10534                         </location>
10535                 </xsl:for-each>
10536                 <xsl:for-each select="marc:datafield[@tag=506]">
10537                         <accessCondition type="restrictionOnAccess">
10538                                 <xsl:call-template name="subfieldSelect">
10539                                         <xsl:with-param name="codes">abcd35</xsl:with-param>
10540                                 </xsl:call-template>
10541                         </accessCondition>
10542                 </xsl:for-each>
10543                 <xsl:for-each select="marc:datafield[@tag=540]">
10544                         <accessCondition type="useAndReproduction">
10545                                 <xsl:call-template name="subfieldSelect">
10546                                         <xsl:with-param name="codes">abcde35</xsl:with-param>
10547                                 </xsl:call-template>
10548                         </accessCondition>
10549                 </xsl:for-each>
10550                 <recordInfo>
10551                         <xsl:for-each select="marc:datafield[@tag=040]">
10552                                 <recordContentSource authority="marcorg">
10553                                         <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
10554                                 </recordContentSource>
10555                         </xsl:for-each>
10556                         <xsl:for-each select="marc:controlfield[@tag=008]">
10557                                 <recordCreationDate encoding="marc">
10558                                         <xsl:value-of select="substring(.,1,6)"></xsl:value-of>
10559                                 </recordCreationDate>
10560                         </xsl:for-each>
10561                         <xsl:for-each select="marc:controlfield[@tag=005]">
10562                                 <recordChangeDate encoding="iso8601">
10563                                         <xsl:value-of select="."></xsl:value-of>
10564                                 </recordChangeDate>
10565                         </xsl:for-each>
10566                         <xsl:for-each select="marc:controlfield[@tag=001]">
10567                                 <recordIdentifier>
10568                                         <xsl:if test="../marc:controlfield[@tag=003]">
10569                                                 <xsl:attribute name="source">
10570                                                         <xsl:value-of select="../marc:controlfield[@tag=003]"></xsl:value-of>
10571                                                 </xsl:attribute>
10572                                         </xsl:if>
10573                                         <xsl:value-of select="."></xsl:value-of>
10574                                 </recordIdentifier>
10575                         </xsl:for-each>
10576                         <xsl:for-each select="marc:datafield[@tag=040]/marc:subfield[@code='b']">
10577                                 <languageOfCataloging>
10578                                         <languageTerm authority="iso639-2b" type="code">
10579                                                 <xsl:value-of select="."></xsl:value-of>
10580                                         </languageTerm>
10581                                 </languageOfCataloging>
10582                         </xsl:for-each>
10583                 </recordInfo>
10584         </xsl:template>
10585         <xsl:template name="displayForm">
10586                 <xsl:for-each select="marc:subfield[@code='c']">
10587                         <displayForm>
10588                                 <xsl:value-of select="."></xsl:value-of>
10589                         </displayForm>
10590                 </xsl:for-each>
10591         </xsl:template>
10592         <xsl:template name="affiliation">
10593                 <xsl:for-each select="marc:subfield[@code='u']">
10594                         <affiliation>
10595                                 <xsl:value-of select="."></xsl:value-of>
10596                         </affiliation>
10597                 </xsl:for-each>
10598         </xsl:template>
10599         <xsl:template name="uri">
10600                 <xsl:for-each select="marc:subfield[@code='u']">
10601                         <xsl:attribute name="xlink:href">
10602                                 <xsl:value-of select="."></xsl:value-of>
10603                         </xsl:attribute>
10604                 </xsl:for-each>
10605         </xsl:template>
10606         <xsl:template name="role">
10607                 <xsl:for-each select="marc:subfield[@code='e']">
10608                         <role>
10609                                 <roleTerm type="text">
10610                                         <xsl:value-of select="."></xsl:value-of>
10611                                 </roleTerm>
10612                         </role>
10613                 </xsl:for-each>
10614                 <xsl:for-each select="marc:subfield[@code='4']">
10615                         <role>
10616                                 <roleTerm authority="marcrelator" type="code">
10617                                         <xsl:value-of select="."></xsl:value-of>
10618                                 </roleTerm>
10619                         </role>
10620                 </xsl:for-each>
10621         </xsl:template>
10622         <xsl:template name="part">
10623                 <xsl:variable name="partNumber">
10624                         <xsl:call-template name="specialSubfieldSelect">
10625                                 <xsl:with-param name="axis">n</xsl:with-param>
10626                                 <xsl:with-param name="anyCodes">n</xsl:with-param>
10627                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10628                         </xsl:call-template>
10629                 </xsl:variable>
10630                 <xsl:variable name="partName">
10631                         <xsl:call-template name="specialSubfieldSelect">
10632                                 <xsl:with-param name="axis">p</xsl:with-param>
10633                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
10634                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10635                         </xsl:call-template>
10636                 </xsl:variable>
10637                 <xsl:if test="string-length(normalize-space($partNumber))">
10638                         <partNumber>
10639                                 <xsl:call-template name="chopPunctuation">
10640                                         <xsl:with-param name="chopString" select="$partNumber"></xsl:with-param>
10641                                 </xsl:call-template>
10642                         </partNumber>
10643                 </xsl:if>
10644                 <xsl:if test="string-length(normalize-space($partName))">
10645                         <partName>
10646                                 <xsl:call-template name="chopPunctuation">
10647                                         <xsl:with-param name="chopString" select="$partName"></xsl:with-param>
10648                                 </xsl:call-template>
10649                         </partName>
10650                 </xsl:if>
10651         </xsl:template>
10652         <xsl:template name="relatedPart">
10653                 <xsl:if test="@tag=773">
10654                         <xsl:for-each select="marc:subfield[@code='g']">
10655                                 <part>
10656                                         <text>
10657                                                 <xsl:value-of select="."></xsl:value-of>
10658                                         </text>
10659                                 </part>
10660                         </xsl:for-each>
10661                         <xsl:for-each select="marc:subfield[@code='q']">
10662                                 <part>
10663                                         <xsl:call-template name="parsePart"></xsl:call-template>
10664                                 </part>
10665                         </xsl:for-each>
10666                 </xsl:if>
10667         </xsl:template>
10668         <xsl:template name="relatedPartNumName">
10669                 <xsl:variable name="partNumber">
10670                         <xsl:call-template name="specialSubfieldSelect">
10671                                 <xsl:with-param name="axis">g</xsl:with-param>
10672                                 <xsl:with-param name="anyCodes">g</xsl:with-param>
10673                                 <xsl:with-param name="afterCodes">pst</xsl:with-param>
10674                         </xsl:call-template>
10675                 </xsl:variable>
10676                 <xsl:variable name="partName">
10677                         <xsl:call-template name="specialSubfieldSelect">
10678                                 <xsl:with-param name="axis">p</xsl:with-param>
10679                                 <xsl:with-param name="anyCodes">p</xsl:with-param>
10680                                 <xsl:with-param name="afterCodes">fgkdlmor</xsl:with-param>
10681                         </xsl:call-template>
10682                 </xsl:variable>
10683                 <xsl:if test="string-length(normalize-space($partNumber))">
10684                         <partNumber>
10685                                 <xsl:value-of select="$partNumber"></xsl:value-of>
10686                         </partNumber>
10687                 </xsl:if>
10688                 <xsl:if test="string-length(normalize-space($partName))">
10689                         <partName>
10690                                 <xsl:value-of select="$partName"></xsl:value-of>
10691                         </partName>
10692                 </xsl:if>
10693         </xsl:template>
10694         <xsl:template name="relatedName">
10695                 <xsl:for-each select="marc:subfield[@code='a']">
10696                         <name>
10697                                 <namePart>
10698                                         <xsl:value-of select="."></xsl:value-of>
10699                                 </namePart>
10700                         </name>
10701                 </xsl:for-each>
10702         </xsl:template>
10703         <xsl:template name="relatedForm">
10704                 <xsl:for-each select="marc:subfield[@code='h']">
10705                         <physicalDescription>
10706                                 <form>
10707                                         <xsl:value-of select="."></xsl:value-of>
10708                                 </form>
10709                         </physicalDescription>
10710                 </xsl:for-each>
10711         </xsl:template>
10712         <xsl:template name="relatedExtent">
10713                 <xsl:for-each select="marc:subfield[@code='h']">
10714                         <physicalDescription>
10715                                 <extent>
10716                                         <xsl:value-of select="."></xsl:value-of>
10717                                 </extent>
10718                         </physicalDescription>
10719                 </xsl:for-each>
10720         </xsl:template>
10721         <xsl:template name="relatedNote">
10722                 <xsl:for-each select="marc:subfield[@code='n']">
10723                         <note>
10724                                 <xsl:value-of select="."></xsl:value-of>
10725                         </note>
10726                 </xsl:for-each>
10727         </xsl:template>
10728         <xsl:template name="relatedSubject">
10729                 <xsl:for-each select="marc:subfield[@code='j']">
10730                         <subject>
10731                                 <temporal encoding="iso8601">
10732                                         <xsl:call-template name="chopPunctuation">
10733                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10734                                         </xsl:call-template>
10735                                 </temporal>
10736                         </subject>
10737                 </xsl:for-each>
10738         </xsl:template>
10739         <xsl:template name="relatedIdentifierISSN">
10740                 <xsl:for-each select="marc:subfield[@code='x']">
10741                         <identifier type="issn">
10742                                 <xsl:value-of select="."></xsl:value-of>
10743                         </identifier>
10744                 </xsl:for-each>
10745         </xsl:template>
10746         <xsl:template name="relatedIdentifierLocal">
10747                 <xsl:for-each select="marc:subfield[@code='w']">
10748                         <identifier type="local">
10749                                 <xsl:value-of select="."></xsl:value-of>
10750                         </identifier>
10751                 </xsl:for-each>
10752         </xsl:template>
10753         <xsl:template name="relatedIdentifier">
10754                 <xsl:for-each select="marc:subfield[@code='o']">
10755                         <identifier>
10756                                 <xsl:value-of select="."></xsl:value-of>
10757                         </identifier>
10758                 </xsl:for-each>
10759         </xsl:template>
10760         <xsl:template name="relatedItem76X-78X">
10761                 <xsl:call-template name="displayLabel"></xsl:call-template>
10762                 <xsl:call-template name="relatedTitle76X-78X"></xsl:call-template>
10763                 <xsl:call-template name="relatedName"></xsl:call-template>
10764                 <xsl:call-template name="relatedOriginInfo"></xsl:call-template>
10765                 <xsl:call-template name="relatedLanguage"></xsl:call-template>
10766                 <xsl:call-template name="relatedExtent"></xsl:call-template>
10767                 <xsl:call-template name="relatedNote"></xsl:call-template>
10768                 <xsl:call-template name="relatedSubject"></xsl:call-template>
10769                 <xsl:call-template name="relatedIdentifier"></xsl:call-template>
10770                 <xsl:call-template name="relatedIdentifierISSN"></xsl:call-template>
10771                 <xsl:call-template name="relatedIdentifierLocal"></xsl:call-template>
10772                 <xsl:call-template name="relatedPart"></xsl:call-template>
10773         </xsl:template>
10774         <xsl:template name="subjectGeographicZ">
10775                 <geographic>
10776                         <xsl:call-template name="chopPunctuation">
10777                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10778                         </xsl:call-template>
10779                 </geographic>
10780         </xsl:template>
10781         <xsl:template name="subjectTemporalY">
10782                 <temporal>
10783                         <xsl:call-template name="chopPunctuation">
10784                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10785                         </xsl:call-template>
10786                 </temporal>
10787         </xsl:template>
10788         <xsl:template name="subjectTopic">
10789                 <topic>
10790                         <xsl:call-template name="chopPunctuation">
10791                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10792                         </xsl:call-template>
10793                 </topic>
10794         </xsl:template> 
10795         <!-- 3.2 change tmee 6xx $v genre -->
10796         <xsl:template name="subjectGenre">
10797                 <genre>
10798                         <xsl:call-template name="chopPunctuation">
10799                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
10800                         </xsl:call-template>
10801                 </genre>
10802         </xsl:template>
10803         
10804         <xsl:template name="nameABCDN">
10805                 <xsl:for-each select="marc:subfield[@code='a']">
10806                         <namePart>
10807                                 <xsl:call-template name="chopPunctuation">
10808                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
10809                                 </xsl:call-template>
10810                         </namePart>
10811                 </xsl:for-each>
10812                 <xsl:for-each select="marc:subfield[@code='b']">
10813                         <namePart>
10814                                 <xsl:value-of select="."></xsl:value-of>
10815                         </namePart>
10816                 </xsl:for-each>
10817                 <xsl:if test="marc:subfield[@code='c'] or marc:subfield[@code='d'] or marc:subfield[@code='n']">
10818                         <namePart>
10819                                 <xsl:call-template name="subfieldSelect">
10820                                         <xsl:with-param name="codes">cdn</xsl:with-param>
10821                                 </xsl:call-template>
10822                         </namePart>
10823                 </xsl:if>
10824         </xsl:template>
10825         <xsl:template name="nameABCDQ">
10826                 <namePart>
10827                         <xsl:call-template name="chopPunctuation">
10828                                 <xsl:with-param name="chopString">
10829                                         <xsl:call-template name="subfieldSelect">
10830                                                 <xsl:with-param name="codes">aq</xsl:with-param>
10831                                         </xsl:call-template>
10832                                 </xsl:with-param>
10833                                 <xsl:with-param name="punctuation">
10834                                         <xsl:text>:,;/ </xsl:text>
10835                                 </xsl:with-param>
10836                         </xsl:call-template>
10837                 </namePart>
10838                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
10839                 <xsl:call-template name="nameDate"></xsl:call-template>
10840         </xsl:template>
10841         <xsl:template name="nameACDEQ">
10842                 <namePart>
10843                         <xsl:call-template name="subfieldSelect">
10844                                 <xsl:with-param name="codes">acdeq</xsl:with-param>
10845                         </xsl:call-template>
10846                 </namePart>
10847         </xsl:template>
10848         <xsl:template name="constituentOrRelatedType">
10849                 <xsl:if test="@ind2=2">
10850                         <xsl:attribute name="type">constituent</xsl:attribute>
10851                 </xsl:if>
10852         </xsl:template>
10853         <xsl:template name="relatedTitle">
10854                 <xsl:for-each select="marc:subfield[@code='t']">
10855                         <titleInfo>
10856                                 <title>
10857                                         <xsl:call-template name="chopPunctuation">
10858                                                 <xsl:with-param name="chopString">
10859                                                         <xsl:value-of select="."></xsl:value-of>
10860                                                 </xsl:with-param>
10861                                         </xsl:call-template>
10862                                 </title>
10863                         </titleInfo>
10864                 </xsl:for-each>
10865         </xsl:template>
10866         <xsl:template name="relatedTitle76X-78X">
10867                 <xsl:for-each select="marc:subfield[@code='t']">
10868                         <titleInfo>
10869                                 <title>
10870                                         <xsl:call-template name="chopPunctuation">
10871                                                 <xsl:with-param name="chopString">
10872                                                         <xsl:value-of select="."></xsl:value-of>
10873                                                 </xsl:with-param>
10874                                         </xsl:call-template>
10875                                 </title>
10876                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10877                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10878                                 </xsl:if>
10879                         </titleInfo>
10880                 </xsl:for-each>
10881                 <xsl:for-each select="marc:subfield[@code='p']">
10882                         <titleInfo type="abbreviated">
10883                                 <title>
10884                                         <xsl:call-template name="chopPunctuation">
10885                                                 <xsl:with-param name="chopString">
10886                                                         <xsl:value-of select="."></xsl:value-of>
10887                                                 </xsl:with-param>
10888                                         </xsl:call-template>
10889                                 </title>
10890                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10891                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10892                                 </xsl:if>
10893                         </titleInfo>
10894                 </xsl:for-each>
10895                 <xsl:for-each select="marc:subfield[@code='s']">
10896                         <titleInfo type="uniform">
10897                                 <title>
10898                                         <xsl:call-template name="chopPunctuation">
10899                                                 <xsl:with-param name="chopString">
10900                                                         <xsl:value-of select="."></xsl:value-of>
10901                                                 </xsl:with-param>
10902                                         </xsl:call-template>
10903                                 </title>
10904                                 <xsl:if test="marc:datafield[@tag!=773]and marc:subfield[@code='g']">
10905                                         <xsl:call-template name="relatedPartNumName"></xsl:call-template>
10906                                 </xsl:if>
10907                         </titleInfo>
10908                 </xsl:for-each>
10909         </xsl:template>
10910         <xsl:template name="relatedOriginInfo">
10911                 <xsl:if test="marc:subfield[@code='b' or @code='d'] or marc:subfield[@code='f']">
10912                         <originInfo>
10913                                 <xsl:if test="@tag=775">
10914                                         <xsl:for-each select="marc:subfield[@code='f']">
10915                                                 <place>
10916                                                         <placeTerm>
10917                                                                 <xsl:attribute name="type">code</xsl:attribute>
10918                                                                 <xsl:attribute name="authority">marcgac</xsl:attribute>
10919                                                                 <xsl:value-of select="."></xsl:value-of>
10920                                                         </placeTerm>
10921                                                 </place>
10922                                         </xsl:for-each>
10923                                 </xsl:if>
10924                                 <xsl:for-each select="marc:subfield[@code='d']">
10925                                         <publisher>
10926                                                 <xsl:value-of select="."></xsl:value-of>
10927                                         </publisher>
10928                                 </xsl:for-each>
10929                                 <xsl:for-each select="marc:subfield[@code='b']">
10930                                         <edition>
10931                                                 <xsl:value-of select="."></xsl:value-of>
10932                                         </edition>
10933                                 </xsl:for-each>
10934                         </originInfo>
10935                 </xsl:if>
10936         </xsl:template>
10937         <xsl:template name="relatedLanguage">
10938                 <xsl:for-each select="marc:subfield[@code='e']">
10939                         <xsl:call-template name="getLanguage">
10940                                 <xsl:with-param name="langString">
10941                                         <xsl:value-of select="."></xsl:value-of>
10942                                 </xsl:with-param>
10943                         </xsl:call-template>
10944                 </xsl:for-each>
10945         </xsl:template>
10946         <xsl:template name="nameDate">
10947                 <xsl:for-each select="marc:subfield[@code='d']">
10948                         <namePart type="date">
10949                                 <xsl:call-template name="chopPunctuation">
10950                                         <xsl:with-param name="chopString" select="."></xsl:with-param>
10951                                 </xsl:call-template>
10952                         </namePart>
10953                 </xsl:for-each>
10954         </xsl:template>
10955         <xsl:template name="subjectAuthority">
10956                 <xsl:if test="@ind2!=4">
10957                         <xsl:if test="@ind2!=' '">
10958                                 <xsl:if test="@ind2!=8">
10959                                         <xsl:if test="@ind2!=9">
10960                                                 <xsl:attribute name="authority">
10961                                                         <xsl:choose>
10962                                                                 <xsl:when test="@ind2=0">lcsh</xsl:when>
10963                                                                 <xsl:when test="@ind2=1">lcshac</xsl:when>
10964                                                                 <xsl:when test="@ind2=2">mesh</xsl:when>
10965                                                                 <!-- 1/04 fix -->
10966                                                                 <xsl:when test="@ind2=3">nal</xsl:when>
10967                                                                 <xsl:when test="@ind2=5">csh</xsl:when>
10968                                                                 <xsl:when test="@ind2=6">rvm</xsl:when>
10969                                                                 <xsl:when test="@ind2=7">
10970                                                                         <xsl:value-of select="marc:subfield[@code='2']"></xsl:value-of>
10971                                                                 </xsl:when>
10972                                                         </xsl:choose>
10973                                                 </xsl:attribute>
10974                                         </xsl:if>
10975                                 </xsl:if>
10976                         </xsl:if>
10977                 </xsl:if>
10978         </xsl:template>
10979         <xsl:template name="subjectAnyOrder">
10980                 <xsl:for-each select="marc:subfield[@code='v' or @code='x' or @code='y' or @code='z']">
10981                         <xsl:choose>
10982                                 <xsl:when test="@code='v'">
10983                                         <xsl:call-template name="subjectGenre"></xsl:call-template>
10984                                 </xsl:when>
10985                                 <xsl:when test="@code='x'">
10986                                         <xsl:call-template name="subjectTopic"></xsl:call-template>
10987                                 </xsl:when>
10988                                 <xsl:when test="@code='y'">
10989                                         <xsl:call-template name="subjectTemporalY"></xsl:call-template>
10990                                 </xsl:when>
10991                                 <xsl:when test="@code='z'">
10992                                         <xsl:call-template name="subjectGeographicZ"></xsl:call-template>
10993                                 </xsl:when>
10994                         </xsl:choose>
10995                 </xsl:for-each>
10996         </xsl:template>
10997         <xsl:template name="specialSubfieldSelect">
10998                 <xsl:param name="anyCodes"></xsl:param>
10999                 <xsl:param name="axis"></xsl:param>
11000                 <xsl:param name="beforeCodes"></xsl:param>
11001                 <xsl:param name="afterCodes"></xsl:param>
11002                 <xsl:variable name="str">
11003                         <xsl:for-each select="marc:subfield">
11004                                 <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])">
11005                                         <xsl:value-of select="text()"></xsl:value-of>
11006                                         <xsl:text> </xsl:text>
11007                                 </xsl:if>
11008                         </xsl:for-each>
11009                 </xsl:variable>
11010                 <xsl:value-of select="substring($str,1,string-length($str)-1)"></xsl:value-of>
11011         </xsl:template>
11012         
11013         <!-- 3.2 change tmee 6xx $v genre -->
11014         <xsl:template match="marc:datafield[@tag=600]">
11015                 <subject>
11016                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11017                         <name type="personal">
11018                                 <xsl:call-template name="termsOfAddress"></xsl:call-template>
11019                                 <namePart>
11020                                         <xsl:call-template name="chopPunctuation">
11021                                                 <xsl:with-param name="chopString">
11022                                                         <xsl:call-template name="subfieldSelect">
11023                                                                 <xsl:with-param name="codes">aq</xsl:with-param>
11024                                                         </xsl:call-template>
11025                                                 </xsl:with-param>
11026                                         </xsl:call-template>
11027                                 </namePart>
11028                                 <xsl:call-template name="nameDate"></xsl:call-template>
11029                                 <xsl:call-template name="affiliation"></xsl:call-template>
11030                                 <xsl:call-template name="role"></xsl:call-template>
11031                         </name>
11032                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11033                 </subject>
11034         </xsl:template>
11035         <xsl:template match="marc:datafield[@tag=610]">
11036                 <subject>
11037                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11038                         <name type="corporate">
11039                                 <xsl:for-each select="marc:subfield[@code='a']">
11040                                         <namePart>
11041                                                 <xsl:value-of select="."></xsl:value-of>
11042                                         </namePart>
11043                                 </xsl:for-each>
11044                                 <xsl:for-each select="marc:subfield[@code='b']">
11045                                         <namePart>
11046                                                 <xsl:value-of select="."></xsl:value-of>
11047                                         </namePart>
11048                                 </xsl:for-each>
11049                                 <xsl:if test="marc:subfield[@code='c' or @code='d' or @code='n' or @code='p']">
11050                                         <namePart>
11051                                                 <xsl:call-template name="subfieldSelect">
11052                                                         <xsl:with-param name="codes">cdnp</xsl:with-param>
11053                                                 </xsl:call-template>
11054                                         </namePart>
11055                                 </xsl:if>
11056                                 <xsl:call-template name="role"></xsl:call-template>
11057                         </name>
11058                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11059                 </subject>
11060         </xsl:template>
11061         <xsl:template match="marc:datafield[@tag=611]">
11062                 <subject>
11063                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11064                         <name type="conference">
11065                                 <namePart>
11066                                         <xsl:call-template name="subfieldSelect">
11067                                                 <xsl:with-param name="codes">abcdeqnp</xsl:with-param>
11068                                         </xsl:call-template>
11069                                 </namePart>
11070                                 <xsl:for-each select="marc:subfield[@code='4']">
11071                                         <role>
11072                                                 <roleTerm authority="marcrelator" type="code">
11073                                                         <xsl:value-of select="."></xsl:value-of>
11074                                                 </roleTerm>
11075                                         </role>
11076                                 </xsl:for-each>
11077                         </name>
11078                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11079                 </subject>
11080         </xsl:template>
11081         <xsl:template match="marc:datafield[@tag=630]">
11082                 <subject>
11083                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11084                         <titleInfo>
11085                                 <title>
11086                                         <xsl:call-template name="chopPunctuation">
11087                                                 <xsl:with-param name="chopString">
11088                                                         <xsl:call-template name="subfieldSelect">
11089                                                                 <xsl:with-param name="codes">adfhklor</xsl:with-param>
11090                                                         </xsl:call-template>
11091                                                 </xsl:with-param>
11092                                         </xsl:call-template>
11093                                         <xsl:call-template name="part"></xsl:call-template>
11094                                 </title>
11095                         </titleInfo>
11096                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11097                 </subject>
11098         </xsl:template>
11099         <xsl:template match="marc:datafield[@tag=650]">
11100                 <subject>
11101                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11102                         <topic>
11103                                 <xsl:call-template name="chopPunctuation">
11104                                         <xsl:with-param name="chopString">
11105                                                 <xsl:call-template name="subfieldSelect">
11106                                                         <xsl:with-param name="codes">abcd</xsl:with-param>
11107                                                 </xsl:call-template>
11108                                         </xsl:with-param>
11109                                 </xsl:call-template>
11110                         </topic>
11111                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11112                 </subject>
11113         </xsl:template>
11114         <xsl:template match="marc:datafield[@tag=651]">
11115                 <subject>
11116                         <xsl:call-template name="subjectAuthority"></xsl:call-template>
11117                         <xsl:for-each select="marc:subfield[@code='a']">
11118                                 <geographic>
11119                                         <xsl:call-template name="chopPunctuation">
11120                                                 <xsl:with-param name="chopString" select="."></xsl:with-param>
11121                                         </xsl:call-template>
11122                                 </geographic>
11123                         </xsl:for-each>
11124                         <xsl:call-template name="subjectAnyOrder"></xsl:call-template>
11125                 </subject>
11126         </xsl:template>
11127         <xsl:template match="marc:datafield[@tag=653]">
11128                 <subject>
11129                         <xsl:for-each select="marc:subfield[@code='a']">
11130                                 <topic>
11131                                         <xsl:value-of select="."></xsl:value-of>
11132                                 </topic>
11133                         </xsl:for-each>
11134                 </subject>
11135         </xsl:template>
11136         <xsl:template match="marc:datafield[@tag=656]">
11137                 <subject>
11138                         <xsl:if test="marc:subfield[@code=2]">
11139                                 <xsl:attribute name="authority">
11140                                         <xsl:value-of select="marc:subfield[@code=2]"></xsl:value-of>
11141                                 </xsl:attribute>
11142                         </xsl:if>
11143                         <occupation>
11144                                 <xsl:call-template name="chopPunctuation">
11145                                         <xsl:with-param name="chopString">
11146                                                 <xsl:value-of select="marc:subfield[@code='a']"></xsl:value-of>
11147                                         </xsl:with-param>
11148                                 </xsl:call-template>
11149                         </occupation>
11150                 </subject>
11151         </xsl:template>
11152         <xsl:template name="termsOfAddress">
11153                 <xsl:if test="marc:subfield[@code='b' or @code='c']">
11154                         <namePart type="termsOfAddress">
11155                                 <xsl:call-template name="chopPunctuation">
11156                                         <xsl:with-param name="chopString">
11157                                                 <xsl:call-template name="subfieldSelect">
11158                                                         <xsl:with-param name="codes">bc</xsl:with-param>
11159                                                 </xsl:call-template>
11160                                         </xsl:with-param>
11161                                 </xsl:call-template>
11162                         </namePart>
11163                 </xsl:if>
11164         </xsl:template>
11165         <xsl:template name="displayLabel">
11166                 <xsl:if test="marc:subfield[@code='i']">
11167                         <xsl:attribute name="displayLabel">
11168                                 <xsl:value-of select="marc:subfield[@code='i']"></xsl:value-of>
11169                         </xsl:attribute>
11170                 </xsl:if>
11171                 <xsl:if test="marc:subfield[@code='3']">
11172                         <xsl:attribute name="displayLabel">
11173                                 <xsl:value-of select="marc:subfield[@code='3']"></xsl:value-of>
11174                         </xsl:attribute>
11175                 </xsl:if>
11176         </xsl:template>
11177         <xsl:template name="isInvalid">
11178                 <xsl:param name="type"/>
11179                 <xsl:if test="marc:subfield[@code='z'] or marc:subfield[@code='y']">
11180                         <identifier>
11181                                 <xsl:attribute name="type">
11182                                         <xsl:value-of select="$type"/>
11183                                 </xsl:attribute>
11184                                 <xsl:attribute name="invalid">
11185                                         <xsl:text>yes</xsl:text>
11186                                 </xsl:attribute>
11187                                 <xsl:if test="marc:subfield[@code='z']">
11188                                         <xsl:value-of select="marc:subfield[@code='z']"/>
11189                                 </xsl:if>
11190                                 <xsl:if test="marc:subfield[@code='y']">
11191                                         <xsl:value-of select="marc:subfield[@code='y']"/>
11192                                 </xsl:if>
11193                         </identifier>
11194                 </xsl:if>
11195         </xsl:template>
11196         <xsl:template name="subtitle">
11197                 <xsl:if test="marc:subfield[@code='b']">
11198                         <subTitle>
11199                                 <xsl:call-template name="chopPunctuation">
11200                                         <xsl:with-param name="chopString">
11201                                                 <xsl:value-of select="marc:subfield[@code='b']"/>
11202                                                 <!--<xsl:call-template name="subfieldSelect">
11203                                                         <xsl:with-param name="codes">b</xsl:with-param>                                                                 
11204                                                 </xsl:call-template>-->
11205                                         </xsl:with-param>
11206                                 </xsl:call-template>
11207                         </subTitle>
11208                 </xsl:if>
11209         </xsl:template>
11210         <xsl:template name="script">
11211                 <xsl:param name="scriptCode"></xsl:param>
11212                 <xsl:attribute name="script">
11213                         <xsl:choose>
11214                                 <xsl:when test="$scriptCode='(3'">Arabic</xsl:when>
11215                                 <xsl:when test="$scriptCode='(B'">Latin</xsl:when>
11216                                 <xsl:when test="$scriptCode='$1'">Chinese, Japanese, Korean</xsl:when>
11217                                 <xsl:when test="$scriptCode='(N'">Cyrillic</xsl:when>
11218                                 <xsl:when test="$scriptCode='(2'">Hebrew</xsl:when>
11219                                 <xsl:when test="$scriptCode='(S'">Greek</xsl:when>
11220                         </xsl:choose>
11221                 </xsl:attribute>
11222         </xsl:template>
11223         <xsl:template name="parsePart">
11224                 <!-- assumes 773$q= 1:2:3<4
11225                      with up to 3 levels and one optional start page
11226                 -->
11227                 <xsl:variable name="level1">
11228                         <xsl:choose>
11229                                 <xsl:when test="contains(text(),':')">
11230                                         <!-- 1:2 -->
11231                                         <xsl:value-of select="substring-before(text(),':')"></xsl:value-of>
11232                                 </xsl:when>
11233                                 <xsl:when test="not(contains(text(),':'))">
11234                                         <!-- 1 or 1<3 -->
11235                                         <xsl:if test="contains(text(),'&lt;')">
11236                                                 <!-- 1<3 -->
11237                                                 <xsl:value-of select="substring-before(text(),'&lt;')"></xsl:value-of>
11238                                         </xsl:if>
11239                                         <xsl:if test="not(contains(text(),'&lt;'))">
11240                                                 <!-- 1 -->
11241                                                 <xsl:value-of select="text()"></xsl:value-of>
11242                                         </xsl:if>
11243                                 </xsl:when>
11244                         </xsl:choose>
11245                 </xsl:variable>
11246                 <xsl:variable name="sici2">
11247                         <xsl:choose>
11248                                 <xsl:when test="starts-with(substring-after(text(),$level1),':')">
11249                                         <xsl:value-of select="substring(substring-after(text(),$level1),2)"></xsl:value-of>
11250                                 </xsl:when>
11251                                 <xsl:otherwise>
11252                                         <xsl:value-of select="substring-after(text(),$level1)"></xsl:value-of>
11253                                 </xsl:otherwise>
11254                         </xsl:choose>
11255                 </xsl:variable>
11256                 <xsl:variable name="level2">
11257                         <xsl:choose>
11258                                 <xsl:when test="contains($sici2,':')">
11259                                         <!--  2:3<4  -->
11260                                         <xsl:value-of select="substring-before($sici2,':')"></xsl:value-of>
11261                                 </xsl:when>
11262                                 <xsl:when test="contains($sici2,'&lt;')">
11263                                         <!-- 1: 2<4 -->
11264                                         <xsl:value-of select="substring-before($sici2,'&lt;')"></xsl:value-of>
11265                                 </xsl:when>
11266                                 <xsl:otherwise>
11267                                         <xsl:value-of select="$sici2"></xsl:value-of>
11268                                         <!-- 1:2 -->
11269                                 </xsl:otherwise>
11270                         </xsl:choose>
11271                 </xsl:variable>
11272                 <xsl:variable name="sici3">
11273                         <xsl:choose>
11274                                 <xsl:when test="starts-with(substring-after($sici2,$level2),':')">
11275                                         <xsl:value-of select="substring(substring-after($sici2,$level2),2)"></xsl:value-of>
11276                                 </xsl:when>
11277                                 <xsl:otherwise>
11278                                         <xsl:value-of select="substring-after($sici2,$level2)"></xsl:value-of>
11279                                 </xsl:otherwise>
11280                         </xsl:choose>
11281                 </xsl:variable>
11282                 <xsl:variable name="level3">
11283                         <xsl:choose>
11284                                 <xsl:when test="contains($sici3,'&lt;')">
11285                                         <!-- 2<4 -->
11286                                         <xsl:value-of select="substring-before($sici3,'&lt;')"></xsl:value-of>
11287                                 </xsl:when>
11288                                 <xsl:otherwise>
11289                                         <xsl:value-of select="$sici3"></xsl:value-of>
11290                                         <!-- 3 -->
11291                                 </xsl:otherwise>
11292                         </xsl:choose>
11293                 </xsl:variable>
11294                 <xsl:variable name="page">
11295                         <xsl:if test="contains(text(),'&lt;')">
11296                                 <xsl:value-of select="substring-after(text(),'&lt;')"></xsl:value-of>
11297                         </xsl:if>
11298                 </xsl:variable>
11299                 <xsl:if test="$level1">
11300                         <detail level="1">
11301                                 <number>
11302                                         <xsl:value-of select="$level1"></xsl:value-of>
11303                                 </number>
11304                         </detail>
11305                 </xsl:if>
11306                 <xsl:if test="$level2">
11307                         <detail level="2">
11308                                 <number>
11309                                         <xsl:value-of select="$level2"></xsl:value-of>
11310                                 </number>
11311                         </detail>
11312                 </xsl:if>
11313                 <xsl:if test="$level3">
11314                         <detail level="3">
11315                                 <number>
11316                                         <xsl:value-of select="$level3"></xsl:value-of>
11317                                 </number>
11318                         </detail>
11319                 </xsl:if>
11320                 <xsl:if test="$page">
11321                         <extent unit="page">
11322                                 <start>
11323                                         <xsl:value-of select="$page"></xsl:value-of>
11324                                 </start>
11325                         </extent>
11326                 </xsl:if>
11327         </xsl:template>
11328         <xsl:template name="getLanguage">
11329                 <xsl:param name="langString"></xsl:param>
11330                 <xsl:param name="controlField008-35-37"></xsl:param>
11331                 <xsl:variable name="length" select="string-length($langString)"></xsl:variable>
11332                 <xsl:choose>
11333                         <xsl:when test="$length=0"></xsl:when>
11334                         <xsl:when test="$controlField008-35-37=substring($langString,1,3)">
11335                                 <xsl:call-template name="getLanguage">
11336                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
11337                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
11338                                 </xsl:call-template>
11339                         </xsl:when>
11340                         <xsl:otherwise>
11341                                 <language>
11342                                         <languageTerm authority="iso639-2b" type="code">
11343                                                 <xsl:value-of select="substring($langString,1,3)"></xsl:value-of>
11344                                         </languageTerm>
11345                                 </language>
11346                                 <xsl:call-template name="getLanguage">
11347                                         <xsl:with-param name="langString" select="substring($langString,4,$length)"></xsl:with-param>
11348                                         <xsl:with-param name="controlField008-35-37" select="$controlField008-35-37"></xsl:with-param>
11349                                 </xsl:call-template>
11350                         </xsl:otherwise>
11351                 </xsl:choose>
11352         </xsl:template>
11353         <xsl:template name="isoLanguage">
11354                 <xsl:param name="currentLanguage"></xsl:param>
11355                 <xsl:param name="usedLanguages"></xsl:param>
11356                 <xsl:param name="remainingLanguages"></xsl:param>
11357                 <xsl:choose>
11358                         <xsl:when test="string-length($currentLanguage)=0"></xsl:when>
11359                         <xsl:when test="not(contains($usedLanguages, $currentLanguage))">
11360                                 <language>
11361                                         <xsl:if test="@code!='a'">
11362                                                 <xsl:attribute name="objectPart">
11363                                                         <xsl:choose>
11364                                                                 <xsl:when test="@code='b'">summary or subtitle</xsl:when>
11365                                                                 <xsl:when test="@code='d'">sung or spoken text</xsl:when>
11366                                                                 <xsl:when test="@code='e'">libretto</xsl:when>
11367                                                                 <xsl:when test="@code='f'">table of contents</xsl:when>
11368                                                                 <xsl:when test="@code='g'">accompanying material</xsl:when>
11369                                                                 <xsl:when test="@code='h'">translation</xsl:when>
11370                                                         </xsl:choose>
11371                                                 </xsl:attribute>
11372                                         </xsl:if>
11373                                         <languageTerm authority="iso639-2b" type="code">
11374                                                 <xsl:value-of select="$currentLanguage"></xsl:value-of>
11375                                         </languageTerm>
11376                                 </language>
11377                                 <xsl:call-template name="isoLanguage">
11378                                         <xsl:with-param name="currentLanguage">
11379                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
11380                                         </xsl:with-param>
11381                                         <xsl:with-param name="usedLanguages">
11382                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
11383                                         </xsl:with-param>
11384                                         <xsl:with-param name="remainingLanguages">
11385                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
11386                                         </xsl:with-param>
11387                                 </xsl:call-template>
11388                         </xsl:when>
11389                         <xsl:otherwise>
11390                                 <xsl:call-template name="isoLanguage">
11391                                         <xsl:with-param name="currentLanguage">
11392                                                 <xsl:value-of select="substring($remainingLanguages,1,3)"></xsl:value-of>
11393                                         </xsl:with-param>
11394                                         <xsl:with-param name="usedLanguages">
11395                                                 <xsl:value-of select="concat($usedLanguages,$currentLanguage)"></xsl:value-of>
11396                                         </xsl:with-param>
11397                                         <xsl:with-param name="remainingLanguages">
11398                                                 <xsl:value-of select="substring($remainingLanguages,4,string-length($remainingLanguages))"></xsl:value-of>
11399                                         </xsl:with-param>
11400                                 </xsl:call-template>
11401                         </xsl:otherwise>
11402                 </xsl:choose>
11403         </xsl:template>
11404         <xsl:template name="chopBrackets">
11405                 <xsl:param name="chopString"></xsl:param>
11406                 <xsl:variable name="string">
11407                         <xsl:call-template name="chopPunctuation">
11408                                 <xsl:with-param name="chopString" select="$chopString"></xsl:with-param>
11409                         </xsl:call-template>
11410                 </xsl:variable>
11411                 <xsl:if test="substring($string, 1,1)='['">
11412                         <xsl:value-of select="substring($string,2, string-length($string)-2)"></xsl:value-of>
11413                 </xsl:if>
11414                 <xsl:if test="substring($string, 1,1)!='['">
11415                         <xsl:value-of select="$string"></xsl:value-of>
11416                 </xsl:if>
11417         </xsl:template>
11418         <xsl:template name="rfcLanguages">
11419                 <xsl:param name="nodeNum"></xsl:param>
11420                 <xsl:param name="usedLanguages"></xsl:param>
11421                 <xsl:param name="controlField008-35-37"></xsl:param>
11422                 <xsl:variable name="currentLanguage" select="."></xsl:variable>
11423                 <xsl:choose>
11424                         <xsl:when test="not($currentLanguage)"></xsl:when>
11425                         <xsl:when test="$currentLanguage!=$controlField008-35-37 and $currentLanguage!='rfc3066'">
11426                                 <xsl:if test="not(contains($usedLanguages,$currentLanguage))">
11427                                         <language>
11428                                                 <xsl:if test="@code!='a'">
11429                                                         <xsl:attribute name="objectPart">
11430                                                                 <xsl:choose>
11431                                                                         <xsl:when test="@code='b'">summary or subtitle</xsl:when>
11432                                                                         <xsl:when test="@code='d'">sung or spoken text</xsl:when>
11433                                                                         <xsl:when test="@code='e'">libretto</xsl:when>
11434                                                                         <xsl:when test="@code='f'">table of contents</xsl:when>
11435                                                                         <xsl:when test="@code='g'">accompanying material</xsl:when>
11436                                                                         <xsl:when test="@code='h'">translation</xsl:when>
11437                                                                 </xsl:choose>
11438                                                         </xsl:attribute>
11439                                                 </xsl:if>
11440                                                 <languageTerm authority="rfc3066" type="code">
11441                                                         <xsl:value-of select="$currentLanguage"/>
11442                                                 </languageTerm>
11443                                         </language>
11444                                 </xsl:if>
11445                         </xsl:when>
11446                         <xsl:otherwise>
11447                         </xsl:otherwise>
11448                 </xsl:choose>
11449         </xsl:template>
11450         <xsl:template name="datafield">
11451                 <xsl:param name="tag"/>
11452                 <xsl:param name="ind1"><xsl:text> </xsl:text></xsl:param>
11453                 <xsl:param name="ind2"><xsl:text> </xsl:text></xsl:param>
11454                 <xsl:param name="subfields"/>
11455                 <xsl:element name="marc:datafield">
11456                         <xsl:attribute name="tag">
11457                                 <xsl:value-of select="$tag"/>
11458                         </xsl:attribute>
11459                         <xsl:attribute name="ind1">
11460                                 <xsl:value-of select="$ind1"/>
11461                         </xsl:attribute>
11462                         <xsl:attribute name="ind2">
11463                                 <xsl:value-of select="$ind2"/>
11464                         </xsl:attribute>
11465                         <xsl:copy-of select="$subfields"/>
11466                 </xsl:element>
11467         </xsl:template>
11468
11469         <xsl:template name="subfieldSelect">
11470                 <xsl:param name="codes"/>
11471                 <xsl:param name="delimeter"><xsl:text> </xsl:text></xsl:param>
11472                 <xsl:variable name="str">
11473                         <xsl:for-each select="marc:subfield">
11474                                 <xsl:if test="contains($codes, @code)">
11475                                         <xsl:value-of select="text()"/><xsl:value-of select="$delimeter"/>
11476                                 </xsl:if>
11477                         </xsl:for-each>
11478                 </xsl:variable>
11479                 <xsl:value-of select="substring($str,1,string-length($str)-string-length($delimeter))"/>
11480         </xsl:template>
11481
11482         <xsl:template name="buildSpaces">
11483                 <xsl:param name="spaces"/>
11484                 <xsl:param name="char"><xsl:text> </xsl:text></xsl:param>
11485                 <xsl:if test="$spaces>0">
11486                         <xsl:value-of select="$char"/>
11487                         <xsl:call-template name="buildSpaces">
11488                                 <xsl:with-param name="spaces" select="$spaces - 1"/>
11489                                 <xsl:with-param name="char" select="$char"/>
11490                         </xsl:call-template>
11491                 </xsl:if>
11492         </xsl:template>
11493
11494         <xsl:template name="chopPunctuation">
11495                 <xsl:param name="chopString"/>
11496                 <xsl:param name="punctuation"><xsl:text>.:,;/ </xsl:text></xsl:param>
11497                 <xsl:variable name="length" select="string-length($chopString)"/>
11498                 <xsl:choose>
11499                         <xsl:when test="$length=0"/>
11500                         <xsl:when test="contains($punctuation, substring($chopString,$length,1))">
11501                                 <xsl:call-template name="chopPunctuation">
11502                                         <xsl:with-param name="chopString" select="substring($chopString,1,$length - 1)"/>
11503                                         <xsl:with-param name="punctuation" select="$punctuation"/>
11504                                 </xsl:call-template>
11505                         </xsl:when>
11506                         <xsl:when test="not($chopString)"/>
11507                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
11508                 </xsl:choose>
11509         </xsl:template>
11510
11511         <xsl:template name="chopPunctuationFront">
11512                 <xsl:param name="chopString"/>
11513                 <xsl:variable name="length" select="string-length($chopString)"/>
11514                 <xsl:choose>
11515                         <xsl:when test="$length=0"/>
11516                         <xsl:when test="contains('.:,;/[ ', substring($chopString,1,1))">
11517                                 <xsl:call-template name="chopPunctuationFront">
11518                                         <xsl:with-param name="chopString" select="substring($chopString,2,$length - 1)"/>
11519                                 </xsl:call-template>
11520                         </xsl:when>
11521                         <xsl:when test="not($chopString)"/>
11522                         <xsl:otherwise><xsl:value-of select="$chopString"/></xsl:otherwise>
11523                 </xsl:choose>
11524         </xsl:template>
11525 </xsl:stylesheet>$$ WHERE name = 'mods32';
11526
11527 -- Currently, the only difference from naco_normalize is that search_normalize
11528 -- turns apostrophes into spaces, while naco_normalize collapses them.
11529 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
11530
11531     use strict;
11532     use Unicode::Normalize;
11533     use Encode;
11534
11535     my $str = decode_utf8(shift);
11536     my $sf = shift;
11537
11538     # Apply NACO normalization to input string; based on
11539     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
11540     #
11541     # Note that unlike a strict reading of the NACO normalization rules,
11542     # output is returned as lowercase instead of uppercase for compatibility
11543     # with previous versions of the Evergreen naco_normalize routine.
11544
11545     # Convert to upper-case first; even though final output will be lowercase, doing this will
11546     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
11547     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
11548     $str = uc $str;
11549
11550     # remove non-filing strings
11551     $str =~ s/\x{0098}.*?\x{009C}//g;
11552
11553     $str = NFKD($str);
11554
11555     # additional substitutions - 3.6.
11556     $str =~ s/\x{00C6}/AE/g;
11557     $str =~ s/\x{00DE}/TH/g;
11558     $str =~ s/\x{0152}/OE/g;
11559     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;
11560
11561     # transformations based on Unicode category codes
11562     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
11563
11564         if ($sf && $sf =~ /^a/o) {
11565                 my $commapos = index($str, ',');
11566                 if ($commapos > -1) {
11567                         if ($commapos != length($str) - 1) {
11568                 $str =~ s/,/\x07/; # preserve first comma
11569                         }
11570                 }
11571         }
11572
11573     # since we've stripped out the control characters, we can now
11574     # use a few as placeholders temporarily
11575     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
11576     $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;
11577     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
11578
11579     # decimal digits
11580     $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/;
11581
11582     # intentionally skipping step 8 of the NACO algorithm; if the string
11583     # gets normalized away, that's fine.
11584
11585     # leading and trailing spaces
11586     $str =~ s/\s+/ /g;
11587     $str =~ s/^\s+//;
11588     $str =~ s/\s+$//g;
11589
11590     return lc $str;
11591 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
11592
11593 CREATE OR REPLACE FUNCTION public.search_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
11594         SELECT public.search_normalize($1,'a');
11595 $func$ LANGUAGE SQL STRICT IMMUTABLE;
11596
11597 CREATE OR REPLACE FUNCTION public.search_normalize( TEXT ) RETURNS TEXT AS $func$
11598         SELECT public.search_normalize($1,'');
11599 $func$ LANGUAGE 'sql' STRICT IMMUTABLE;
11600
11601 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
11602         'Search Normalize',
11603         'Apply search normalization rules to the extracted text. A less extreme version of NACO normalization.',
11604         'search_normalize',
11605         0
11606 );
11607
11608 UPDATE config.metabib_field_index_norm_map
11609     SET norm = (
11610         SELECT id FROM config.index_normalizer WHERE func = 'search_normalize'
11611     )
11612     WHERE norm = (
11613         SELECT id FROM config.index_normalizer WHERE func = 'naco_normalize'
11614     )
11615 ;
11616
11617
11618 -- This could take a long time if you have a very non-English bib database
11619 -- Run it outside of a transaction to avoid lock escalation
11620 SELECT metabib.reingest_metabib_field_entries(record)
11621     FROM metabib.full_rec
11622     WHERE tag = '245'
11623     AND subfield = 'a'
11624     AND value LIKE '%''%'
11625 ;
11626 -- Evergreen DB patch 0673.data.acq-cancel-reason-cleanup.sql
11627 --
11628
11629 -- check whether patch can be applied
11630 SELECT evergreen.upgrade_deps_block_check('0673', :eg_version);
11631
11632 DELETE FROM
11633     acq.cancel_reason
11634 WHERE
11635     -- any entries with id >= 2000 were added locally.  
11636     id < 2000 
11637
11638     -- these cancel_reason's are actively used by the system
11639     AND id NOT IN (1, 2, 3, 1002, 1003, 1004, 1005, 1010, 1024, 1211, 1221, 1246, 1283)
11640
11641     -- don't delete any cancel_reason's that may be in use locally
11642     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.user_request WHERE cancel_reason IS NOT NULL)
11643     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.purchase_order WHERE cancel_reason IS NOT NULL)
11644     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem WHERE cancel_reason IS NOT NULL)
11645     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.lineitem_detail WHERE cancel_reason IS NOT NULL)
11646     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_lineitem_history WHERE cancel_reason IS NOT NULL)
11647     AND id NOT IN (SELECT DISTINCT(cancel_reason) FROM acq.acq_purchase_order_history WHERE cancel_reason IS NOT NULL);
11648
11649
11650 SELECT evergreen.upgrade_deps_block_check('0674', :eg_version);
11651
11652 ALTER TABLE config.copy_status
11653           ADD COLUMN restrict_copy_delete BOOL NOT NULL DEFAULT FALSE;
11654
11655 UPDATE config.copy_status
11656 SET restrict_copy_delete = TRUE
11657 WHERE id IN (1,3,6,8);
11658
11659 INSERT INTO permission.perm_list (id, code, description) VALUES (
11660     520,
11661     'COPY_DELETE_WARNING.override',
11662     'Allow a user to override warnings about deleting copies in problematic situations.'
11663 );
11664
11665
11666 SELECT evergreen.upgrade_deps_block_check('0675', :eg_version);
11667
11668 -- set expected row count to low value to avoid problem
11669 -- where use of this function by the circ tagging feature
11670 -- results in full scans of asset.call_number
11671 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
11672     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
11673 $$ LANGUAGE SQL ROWS 10;
11674
11675
11676 SELECT evergreen.upgrade_deps_block_check('0676', :eg_version);
11677
11678 INSERT INTO config.global_flag (name, label, enabled, value) VALUES (
11679     'opac.use_autosuggest',
11680     '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)',
11681     TRUE,
11682     'opac_visible'
11683 );
11684
11685 CREATE TABLE metabib.browse_entry (
11686     id BIGSERIAL PRIMARY KEY,
11687     value TEXT unique,
11688     index_vector tsvector
11689 );
11690 --Skip this, will be created differently later
11691 --CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIST (index_vector);
11692 CREATE TRIGGER metabib_browse_entry_fti_trigger
11693     BEFORE INSERT OR UPDATE ON metabib.browse_entry
11694     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
11695
11696
11697 CREATE TABLE metabib.browse_entry_def_map (
11698     id BIGSERIAL PRIMARY KEY,
11699     entry BIGINT REFERENCES metabib.browse_entry (id),
11700     def INT REFERENCES config.metabib_field (id),
11701     source BIGINT REFERENCES biblio.record_entry (id)
11702 );
11703
11704 ALTER TABLE config.metabib_field ADD COLUMN browse_field BOOLEAN DEFAULT TRUE NOT NULL;
11705 ALTER TABLE config.metabib_field ADD COLUMN browse_xpath TEXT;
11706
11707 ALTER TABLE config.metabib_class ADD COLUMN bouyant BOOLEAN DEFAULT FALSE NOT NULL;
11708 ALTER TABLE config.metabib_class ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
11709 ALTER TABLE config.metabib_field ADD COLUMN restrict BOOLEAN DEFAULT FALSE NOT NULL;
11710
11711 -- one good exception to default true:
11712 UPDATE config.metabib_field
11713     SET browse_field = FALSE
11714     WHERE (field_class = 'keyword' AND name = 'keyword') OR
11715         (field_class = 'subject' AND name = 'complete');
11716
11717 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11718 -- We're only touching it here to add a DELETE statement to the IF NEW.deleted
11719 -- block.
11720
11721 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11722 DECLARE
11723     transformed_xml TEXT;
11724     prev_xfrm       TEXT;
11725     normalizer      RECORD;
11726     xfrm            config.xml_transform%ROWTYPE;
11727     attr_value      TEXT;
11728     new_attrs       HSTORE := ''::HSTORE;
11729     attr_def        config.record_attr_definition%ROWTYPE;
11730 BEGIN
11731
11732     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11733         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11734         DELETE FROM metabib.record_attr WHERE id = NEW.id; -- Kill the attrs hash, useless on deleted records
11735         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11736         DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
11737         DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
11738         RETURN NEW; -- and we're done
11739     END IF;
11740
11741     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11742         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11743
11744         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11745             RETURN NEW;
11746         END IF;
11747     END IF;
11748
11749     -- Record authority linking
11750     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11751     IF NOT FOUND THEN
11752         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11753     END IF;
11754
11755     -- Flatten and insert the mfr data
11756     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11757     IF NOT FOUND THEN
11758         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11759
11760         -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
11761         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11762         IF NOT FOUND THEN
11763             FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
11764
11765                 IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
11766                     SELECT  ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
11767                       FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
11768                       WHERE record = NEW.id
11769                             AND tag LIKE attr_def.tag
11770                             AND CASE
11771                                 WHEN attr_def.sf_list IS NOT NULL 
11772                                     THEN POSITION(subfield IN attr_def.sf_list) > 0
11773                                 ELSE TRUE
11774                                 END
11775                       GROUP BY tag
11776                       ORDER BY tag
11777                       LIMIT 1;
11778
11779                 ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
11780                     attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
11781
11782                 ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
11783
11784                     SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
11785             
11786                     -- See if we can skip the XSLT ... it's expensive
11787                     IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
11788                         -- Can't skip the transform
11789                         IF xfrm.xslt <> '---' THEN
11790                             transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
11791                         ELSE
11792                             transformed_xml := NEW.marc;
11793                         END IF;
11794             
11795                         prev_xfrm := xfrm.name;
11796                     END IF;
11797
11798                     IF xfrm.name IS NULL THEN
11799                         -- just grab the marcxml (empty) transform
11800                         SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
11801                         prev_xfrm := xfrm.name;
11802                     END IF;
11803
11804                     attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
11805
11806                 ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
11807                     SELECT  m.value INTO attr_value
11808                       FROM  biblio.marc21_physical_characteristics(NEW.id) v
11809                             JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
11810                       WHERE v.subfield = attr_def.phys_char_sf
11811                       LIMIT 1; -- Just in case ...
11812
11813                 END IF;
11814
11815                 -- apply index normalizers to attr_value
11816                 FOR normalizer IN
11817                     SELECT  n.func AS func,
11818                             n.param_count AS param_count,
11819                             m.params AS params
11820                       FROM  config.index_normalizer n
11821                             JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
11822                       WHERE attr = attr_def.name
11823                       ORDER BY m.pos LOOP
11824                         EXECUTE 'SELECT ' || normalizer.func || '(' ||
11825                             COALESCE( quote_literal( attr_value ), 'NULL' ) ||
11826                             CASE
11827                                 WHEN normalizer.param_count > 0
11828                                     THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
11829                                     ELSE ''
11830                                 END ||
11831                             ')' INTO attr_value;
11832         
11833                 END LOOP;
11834
11835                 -- Add the new value to the hstore
11836                 new_attrs := new_attrs || hstore( attr_def.name, attr_value );
11837
11838             END LOOP;
11839
11840             IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
11841                 INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
11842             ELSE
11843                 UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
11844             END IF;
11845
11846         END IF;
11847     END IF;
11848
11849     -- Gather and insert the field entry data
11850     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11851
11852     -- Located URI magic
11853     IF TG_OP = 'INSERT' THEN
11854         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11855         IF NOT FOUND THEN
11856             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11857         END IF;
11858     ELSE
11859         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11860         IF NOT FOUND THEN
11861             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11862         END IF;
11863     END IF;
11864
11865     -- (re)map metarecord-bib linking
11866     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11867         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11868         IF NOT FOUND THEN
11869             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11870         END IF;
11871     ELSE -- we're doing an update, and we're not deleted, remap
11872         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11873         IF NOT FOUND THEN
11874             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11875         END IF;
11876     END IF;
11877
11878     RETURN NEW;
11879 END;
11880 $func$ LANGUAGE PLPGSQL;
11881
11882 CREATE OR REPLACE FUNCTION metabib.browse_normalize(facet_text TEXT, mapped_field INT) RETURNS TEXT AS $$
11883 DECLARE
11884     normalizer  RECORD;
11885 BEGIN
11886
11887     FOR normalizer IN
11888         SELECT  n.func AS func,
11889                 n.param_count AS param_count,
11890                 m.params AS params
11891           FROM  config.index_normalizer n
11892                 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
11893           WHERE m.field = mapped_field AND m.pos < 0
11894           ORDER BY m.pos LOOP
11895
11896             EXECUTE 'SELECT ' || normalizer.func || '(' ||
11897                 quote_literal( facet_text ) ||
11898                 CASE
11899                     WHEN normalizer.param_count > 0
11900                         THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
11901                         ELSE ''
11902                     END ||
11903                 ')' INTO facet_text;
11904
11905     END LOOP;
11906
11907     RETURN facet_text;
11908 END;
11909
11910 $$ LANGUAGE PLPGSQL;
11911
11912 DROP FUNCTION biblio.extract_metabib_field_entry(bigint, text);
11913 DROP FUNCTION biblio.extract_metabib_field_entry(bigint);
11914
11915 DROP TYPE metabib.field_entry_template;
11916 CREATE TYPE metabib.field_entry_template AS (
11917         field_class     TEXT,
11918         field           INT,
11919         facet_field     BOOL,
11920         search_field    BOOL,
11921         browse_field   BOOL,
11922         source          BIGINT,
11923         value           TEXT
11924 );
11925
11926
11927 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
11928 DECLARE
11929     bib     biblio.record_entry%ROWTYPE;
11930     idx     config.metabib_field%ROWTYPE;
11931     xfrm        config.xml_transform%ROWTYPE;
11932     prev_xfrm   TEXT;
11933     transformed_xml TEXT;
11934     xml_node    TEXT;
11935     xml_node_list   TEXT[];
11936     facet_text  TEXT;
11937     browse_text TEXT;
11938     raw_text    TEXT;
11939     curr_text   TEXT;
11940     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
11941     output_row  metabib.field_entry_template%ROWTYPE;
11942 BEGIN
11943
11944     -- Get the record
11945     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
11946
11947     -- Loop over the indexing entries
11948     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
11949
11950         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
11951
11952         -- See if we can skip the XSLT ... it's expensive
11953         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
11954             -- Can't skip the transform
11955             IF xfrm.xslt <> '---' THEN
11956                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
11957             ELSE
11958                 transformed_xml := bib.marc;
11959             END IF;
11960
11961             prev_xfrm := xfrm.name;
11962         END IF;
11963
11964         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
11965
11966         raw_text := NULL;
11967         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
11968             CONTINUE WHEN xml_node !~ E'^\\s*<';
11969
11970             curr_text := ARRAY_TO_STRING(
11971                 oils_xpath( '//text()',
11972                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
11973                         REGEXP_REPLACE( -- This escapes embeded <s
11974                             xml_node,
11975                             $re$(>[^<]+)(<)([^>]+<)$re$,
11976                             E'\\1&lt;\\3',
11977                             'g'
11978                         ),
11979                         '&(?!amp;)',
11980                         '&amp;',
11981                         'g'
11982                     )
11983                 ),
11984                 ' '
11985             );
11986
11987             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
11988
11989             IF raw_text IS NOT NULL THEN
11990                 raw_text := raw_text || joiner;
11991             END IF;
11992
11993             raw_text := COALESCE(raw_text,'') || curr_text;
11994
11995             -- autosuggest/metabib.browse_entry
11996             IF idx.browse_field THEN
11997
11998                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
11999                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
12000                 ELSE
12001                     browse_text := curr_text;
12002                 END IF;
12003
12004                 output_row.field_class = idx.field_class;
12005                 output_row.field = idx.id;
12006                 output_row.source = rid;
12007                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
12008
12009                 output_row.browse_field = TRUE;
12010                 RETURN NEXT output_row;
12011                 output_row.browse_field = FALSE;
12012             END IF;
12013
12014             -- insert raw node text for faceting
12015             IF idx.facet_field THEN
12016
12017                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
12018                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
12019                 ELSE
12020                     facet_text := curr_text;
12021                 END IF;
12022
12023                 output_row.field_class = idx.field_class;
12024                 output_row.field = -1 * idx.id;
12025                 output_row.source = rid;
12026                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
12027
12028                 output_row.facet_field = TRUE;
12029                 RETURN NEXT output_row;
12030                 output_row.facet_field = FALSE;
12031             END IF;
12032
12033         END LOOP;
12034
12035         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
12036
12037         -- insert combined node text for searching
12038         IF idx.search_field THEN
12039             output_row.field_class = idx.field_class;
12040             output_row.field = idx.id;
12041             output_row.source = rid;
12042             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
12043
12044             output_row.search_field = TRUE;
12045             RETURN NEXT output_row;
12046         END IF;
12047
12048     END LOOP;
12049
12050 END;
12051 $func$ LANGUAGE PLPGSQL;
12052
12053 -- default to a space joiner
12054 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
12055     SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
12056     $func$ LANGUAGE SQL;
12057
12058
12059 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
12060 DECLARE
12061     fclass          RECORD;
12062     ind_data        metabib.field_entry_template%ROWTYPE;
12063     mbe_row         metabib.browse_entry%ROWTYPE;
12064     mbe_id          BIGINT;
12065 BEGIN
12066     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
12067     IF NOT FOUND THEN
12068         FOR fclass IN SELECT * FROM config.metabib_class LOOP
12069             -- RAISE NOTICE 'Emptying out %', fclass.name;
12070             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
12071         END LOOP;
12072         DELETE FROM metabib.facet_entry WHERE source = bib_id;
12073         DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
12074     END IF;
12075
12076     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
12077         IF ind_data.field < 0 THEN
12078             ind_data.field = -1 * ind_data.field;
12079         END IF;
12080
12081         IF ind_data.facet_field THEN
12082             INSERT INTO metabib.facet_entry (field, source, value)
12083                 VALUES (ind_data.field, ind_data.source, ind_data.value);
12084         END IF;
12085
12086         IF ind_data.browse_field THEN
12087             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
12088             IF FOUND THEN
12089                 mbe_id := mbe_row.id;
12090             ELSE
12091                 INSERT INTO metabib.browse_entry (value) VALUES
12092                     (metabib.browse_normalize(ind_data.value, ind_data.field));
12093                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
12094             END IF;
12095
12096             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
12097                 VALUES (mbe_id, ind_data.field, ind_data.source);
12098         END IF;
12099
12100         IF ind_data.search_field THEN
12101             EXECUTE $$
12102                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
12103                     VALUES ($$ ||
12104                         quote_literal(ind_data.field) || $$, $$ ||
12105                         quote_literal(ind_data.source) || $$, $$ ||
12106                         quote_literal(ind_data.value) ||
12107                     $$);$$;
12108         END IF;
12109
12110     END LOOP;
12111
12112     RETURN;
12113 END;
12114 $func$ LANGUAGE PLPGSQL;
12115
12116 -- This mimics a specific part of QueryParser, turning the first part of a
12117 -- classed search (search_class) into a set of classes and possibly fields.
12118 -- search_class might look like "author" or "title|proper" or "ti|uniform"
12119 -- or "au" or "au|corporate|personal" or anything like that, where the first
12120 -- element of the list you get by separating on the "|" character is either
12121 -- a registered class (config.metabib_class) or an alias
12122 -- (config.metabib_search_alias), and the rest of any such elements are
12123 -- fields (config.metabib_field).
12124 CREATE OR REPLACE
12125     FUNCTION metabib.search_class_to_registered_components(search_class TEXT)
12126     RETURNS SETOF RECORD AS $func$
12127 DECLARE
12128     search_parts        TEXT[];
12129     field_name          TEXT;
12130     search_part_count   INTEGER;
12131     rec                 RECORD;
12132     registered_class    config.metabib_class%ROWTYPE;
12133     registered_alias    config.metabib_search_alias%ROWTYPE;
12134     registered_field    config.metabib_field%ROWTYPE;
12135 BEGIN
12136     search_parts := REGEXP_SPLIT_TO_ARRAY(search_class, E'\\|');
12137
12138     search_part_count := ARRAY_LENGTH(search_parts, 1);
12139     IF search_part_count = 0 THEN
12140         RETURN;
12141     ELSE
12142         SELECT INTO registered_class
12143             * FROM config.metabib_class WHERE name = search_parts[1];
12144         IF FOUND THEN
12145             IF search_part_count < 2 THEN   -- all fields
12146                 rec := (registered_class.name, NULL::INTEGER);
12147                 RETURN NEXT rec;
12148                 RETURN; -- done
12149             END IF;
12150             FOR field_name IN SELECT *
12151                 FROM UNNEST(search_parts[2:search_part_count]) LOOP
12152                 SELECT INTO registered_field
12153                     * FROM config.metabib_field
12154                     WHERE name = field_name AND
12155                         field_class = registered_class.name;
12156                 IF FOUND THEN
12157                     rec := (registered_class.name, registered_field.id);
12158                     RETURN NEXT rec;
12159                 END IF;
12160             END LOOP;
12161         ELSE
12162             -- maybe we have an alias?
12163             SELECT INTO registered_alias
12164                 * FROM config.metabib_search_alias WHERE alias=search_parts[1];
12165             IF NOT FOUND THEN
12166                 RETURN;
12167             ELSE
12168                 IF search_part_count < 2 THEN   -- return w/e the alias says
12169                     rec := (
12170                         registered_alias.field_class, registered_alias.field
12171                     );
12172                     RETURN NEXT rec;
12173                     RETURN; -- done
12174                 ELSE
12175                     FOR field_name IN SELECT *
12176                         FROM UNNEST(search_parts[2:search_part_count]) LOOP
12177                         SELECT INTO registered_field
12178                             * FROM config.metabib_field
12179                             WHERE name = field_name AND
12180                                 field_class = registered_alias.field_class;
12181                         IF FOUND THEN
12182                             rec := (
12183                                 registered_alias.field_class,
12184                                 registered_field.id
12185                             );
12186                             RETURN NEXT rec;
12187                         END IF;
12188                     END LOOP;
12189                 END IF;
12190             END IF;
12191         END IF;
12192     END IF;
12193 END;
12194 $func$ LANGUAGE PLPGSQL;
12195
12196
12197 CREATE OR REPLACE
12198     FUNCTION metabib.suggest_browse_entries(
12199         query_text      TEXT,   -- 'foo' or 'foo & ba:*',ready for to_tsquery()
12200         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
12201         headline_opts   TEXT,   -- markup options for ts_headline()
12202         visibility_org  INTEGER,-- null if you don't want opac visibility test
12203         query_limit     INTEGER,-- use in LIMIT clause of interal query
12204         normalization   INTEGER -- argument to TS_RANK_CD()
12205     ) RETURNS TABLE (
12206         value                   TEXT,   -- plain
12207         field                   INTEGER,
12208         bouyant_and_class_match BOOL,
12209         field_match             BOOL,
12210         field_weight            INTEGER,
12211         rank                    REAL,
12212         bouyant                 BOOL,
12213         match                   TEXT    -- marked up
12214     ) AS $func$
12215 DECLARE
12216     query                   TSQUERY;
12217     opac_visibility_join    TEXT;
12218     search_class_join       TEXT;
12219     r_fields                RECORD;
12220 BEGIN
12221     query := TO_TSQUERY('keyword', query_text);
12222
12223     IF visibility_org IS NOT NULL THEN
12224         opac_visibility_join := '
12225     JOIN asset.opac_visible_copies aovc ON (
12226         aovc.record = mbedm.source AND
12227         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
12228     )';
12229     ELSE
12230         opac_visibility_join := '';
12231     END IF;
12232
12233     -- The following determines whether we only provide suggestsons matching
12234     -- the user's selected search_class, or whether we show other suggestions
12235     -- too. The reason for MIN() is that for search_classes like
12236     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
12237     -- implication is that if title as a class doesn't have restrict,
12238     -- nor does the proper field, but the uniform field does, you're going
12239     -- to get 'false' for your overall evaluation of 'should we restrict?'
12240     -- To invert that, change from MIN() to MAX().
12241
12242     SELECT
12243         INTO r_fields
12244             MIN(cmc.restrict::INT) AS restrict_class,
12245             MIN(cmf.restrict::INT) AS restrict_field
12246         FROM metabib.search_class_to_registered_components(search_class)
12247             AS _registered (field_class TEXT, field INT)
12248         JOIN
12249             config.metabib_class cmc ON (cmc.name = _registered.field_class)
12250         LEFT JOIN
12251             config.metabib_field cmf ON (cmf.id = _registered.field);
12252
12253     -- evaluate 'should we restrict?'
12254     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
12255         search_class_join := '
12256     JOIN
12257         metabib.search_class_to_registered_components($2)
12258         AS _registered (field_class TEXT, field INT) ON (
12259             (_registered.field IS NULL AND
12260                 _registered.field_class = cmf.field_class) OR
12261             (_registered.field = cmf.id)
12262         )
12263     ';
12264     ELSE
12265         search_class_join := '
12266     LEFT JOIN
12267         metabib.search_class_to_registered_components($2)
12268         AS _registered (field_class TEXT, field INT) ON (
12269             _registered.field_class = cmc.name
12270         )
12271     ';
12272     END IF;
12273
12274     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $1, $3) FROM (SELECT DISTINCT
12275         mbe.value,
12276         cmf.id,
12277         cmc.bouyant AND _registered.field_class IS NOT NULL,
12278         _registered.field = cmf.id,
12279         cmf.weight,
12280         TS_RANK_CD(mbe.index_vector, $1, $6),
12281         cmc.bouyant
12282     FROM metabib.browse_entry_def_map mbedm
12283     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
12284     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
12285     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
12286     '  || search_class_join || opac_visibility_join ||
12287     ' WHERE $1 @@ mbe.index_vector
12288     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12289     LIMIT $5) x
12290     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12291     '   -- sic, repeat the order by clause in the outer select too
12292     USING
12293         query, search_class, headline_opts,
12294         visibility_org, query_limit, normalization
12295         ;
12296
12297     -- sort order:
12298     --  bouyant AND chosen class = match class
12299     --  chosen field = match field
12300     --  field weight
12301     --  rank
12302     --  bouyancy
12303     --  value itself
12304
12305 END;
12306 $func$ LANGUAGE PLPGSQL;
12307
12308 -- The advantage of this over the stock regexp_split_to_array() is that it
12309 -- won't degrade unicode strings.
12310 CREATE OR REPLACE FUNCTION evergreen.regexp_split_to_array(TEXT, TEXT)
12311 RETURNS TEXT[] AS $$
12312     return encode_array_literal([split $_[1], $_[0]]);
12313 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
12314
12315
12316 -- Adds some logic for browse_entry to split on non-word chars for index_vector, post-normalize
12317 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
12318 DECLARE
12319     normalizer      RECORD;
12320     value           TEXT := '';
12321 BEGIN
12322
12323     value := NEW.value;
12324
12325     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12326         FOR normalizer IN
12327             SELECT  n.func AS func,
12328                     n.param_count AS param_count,
12329                     m.params AS params
12330               FROM  config.index_normalizer n
12331                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12332               WHERE field = NEW.field AND m.pos < 0
12333               ORDER BY m.pos LOOP
12334                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12335                     quote_literal( value ) ||
12336                     CASE
12337                         WHEN normalizer.param_count > 0
12338                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12339                             ELSE ''
12340                         END ||
12341                     ')' INTO value;
12342
12343         END LOOP;
12344
12345         NEW.value := value;
12346     END IF;
12347
12348     IF NEW.index_vector = ''::tsvector THEN
12349         RETURN NEW;
12350     END IF;
12351
12352     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12353         FOR normalizer IN
12354             SELECT  n.func AS func,
12355                     n.param_count AS param_count,
12356                     m.params AS params
12357               FROM  config.index_normalizer n
12358                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12359               WHERE field = NEW.field AND m.pos >= 0
12360               ORDER BY m.pos LOOP
12361                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12362                     quote_literal( value ) ||
12363                     CASE
12364                         WHEN normalizer.param_count > 0
12365                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12366                             ELSE ''
12367                         END ||
12368                     ')' INTO value;
12369
12370         END LOOP;
12371     END IF;
12372
12373     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
12374         value :=  ARRAY_TO_STRING(
12375             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12376         );
12377     END IF;
12378
12379     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
12380
12381     RETURN NEW;
12382 END;
12383 $$ LANGUAGE PLPGSQL;
12384
12385 -- Evergreen DB patch 0677.schema.circ_limits.sql
12386 --
12387 -- FIXME: insert description of change, if needed
12388 --
12389
12390
12391 -- check whether patch can be applied
12392 SELECT evergreen.upgrade_deps_block_check('0677', :eg_version);
12393
12394 -- FIXME: add/check SQL statements to perform the upgrade
12395 -- Limit groups for circ counting
12396 CREATE TABLE config.circ_limit_group (
12397     id          SERIAL  PRIMARY KEY,
12398     name        TEXT    UNIQUE NOT NULL,
12399     description TEXT
12400 );
12401
12402 -- Limit sets
12403 CREATE TABLE config.circ_limit_set (
12404     id          SERIAL  PRIMARY KEY,
12405     name        TEXT    UNIQUE NOT NULL,
12406     owning_lib  INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
12407     items_out   INT     NOT NULL, -- Total current active circulations must be less than this. 0 means skip counting (always pass)
12408     depth       INT     NOT NULL DEFAULT 0, -- Depth count starts at
12409     global      BOOL    NOT NULL DEFAULT FALSE, -- If enabled, include everything below depth, otherwise ancestors/descendants only
12410     description TEXT
12411 );
12412
12413 -- Linkage between matchpoints and limit sets
12414 CREATE TABLE config.circ_matrix_limit_set_map (
12415     id          SERIAL  PRIMARY KEY,
12416     matchpoint  INT     NOT NULL REFERENCES config.circ_matrix_matchpoint (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12417     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12418     fallthrough BOOL    NOT NULL DEFAULT FALSE, -- If true fallthrough will grab this rule as it goes along
12419     active      BOOL    NOT NULL DEFAULT TRUE,
12420     CONSTRAINT circ_limit_set_once_per_matchpoint UNIQUE (matchpoint, limit_set)
12421 );
12422
12423 -- Linkage between limit sets and circ mods
12424 CREATE TABLE config.circ_limit_set_circ_mod_map (
12425     id          SERIAL  PRIMARY KEY,
12426     limit_set   INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12427     circ_mod    TEXT    NOT NULL REFERENCES config.circ_modifier (code) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
12428     CONSTRAINT cm_once_per_set UNIQUE (limit_set, circ_mod)
12429 );
12430
12431 -- Linkage between limit sets and limit groups
12432 CREATE TABLE config.circ_limit_set_group_map (
12433     id          SERIAL  PRIMARY KEY,
12434     limit_set    INT     NOT NULL REFERENCES config.circ_limit_set (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12435     limit_group INT     NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12436     check_only  BOOL    NOT NULL DEFAULT FALSE, -- If true, don't accumulate this limit_group for storing with the circulation
12437     CONSTRAINT clg_once_per_set UNIQUE (limit_set, limit_group)
12438 );
12439
12440 -- Linkage between limit groups and circulations
12441 CREATE TABLE action.circulation_limit_group_map (
12442     circ        BIGINT      NOT NULL REFERENCES action.circulation (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12443     limit_group INT         NOT NULL REFERENCES config.circ_limit_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
12444     PRIMARY KEY (circ, limit_group)
12445 );
12446
12447 -- Function for populating the circ/limit group mappings
12448 CREATE OR REPLACE FUNCTION action.link_circ_limit_groups ( BIGINT, INT[] ) RETURNS VOID AS $func$
12449     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));
12450 $func$ LANGUAGE SQL;
12451
12452 DROP TYPE IF EXISTS action.circ_matrix_test_result CASCADE;
12453 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[] );
12454
12455 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$
12456 DECLARE
12457     user_object             actor.usr%ROWTYPE;
12458     standing_penalty        config.standing_penalty%ROWTYPE;
12459     item_object             asset.copy%ROWTYPE;
12460     item_status_object      config.copy_status%ROWTYPE;
12461     item_location_object    asset.copy_location%ROWTYPE;
12462     result                  action.circ_matrix_test_result;
12463     circ_test               action.found_circ_matrix_matchpoint;
12464     circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
12465     circ_limit_set          config.circ_limit_set%ROWTYPE;
12466     hold_ratio              action.hold_stats%ROWTYPE;
12467     penalty_type            TEXT;
12468     items_out               INT;
12469     context_org_list        INT[];
12470     done                    BOOL := FALSE;
12471 BEGIN
12472     -- Assume success unless we hit a failure condition
12473     result.success := TRUE;
12474
12475     -- Need user info to look up matchpoints
12476     SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;
12477
12478     -- (Insta)Fail if we couldn't find the user
12479     IF user_object.id IS NULL THEN
12480         result.fail_part := 'no_user';
12481         result.success := FALSE;
12482         done := TRUE;
12483         RETURN NEXT result;
12484         RETURN;
12485     END IF;
12486
12487     -- Need item info to look up matchpoints
12488     SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;
12489
12490     -- (Insta)Fail if we couldn't find the item 
12491     IF item_object.id IS NULL THEN
12492         result.fail_part := 'no_item';
12493         result.success := FALSE;
12494         done := TRUE;
12495         RETURN NEXT result;
12496         RETURN;
12497     END IF;
12498
12499     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);
12500
12501     circ_matchpoint             := circ_test.matchpoint;
12502     result.matchpoint           := circ_matchpoint.id;
12503     result.circulate            := circ_matchpoint.circulate;
12504     result.duration_rule        := circ_matchpoint.duration_rule;
12505     result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
12506     result.max_fine_rule        := circ_matchpoint.max_fine_rule;
12507     result.hard_due_date        := circ_matchpoint.hard_due_date;
12508     result.renewals             := circ_matchpoint.renewals;
12509     result.grace_period         := circ_matchpoint.grace_period;
12510     result.buildrows            := circ_test.buildrows;
12511
12512     -- (Insta)Fail if we couldn't find a matchpoint
12513     IF circ_test.success = false THEN
12514         result.fail_part := 'no_matchpoint';
12515         result.success := FALSE;
12516         done := TRUE;
12517         RETURN NEXT result;
12518         RETURN;
12519     END IF;
12520
12521     -- All failures before this point are non-recoverable
12522     -- Below this point are possibly overridable failures
12523
12524     -- Fail if the user is barred
12525     IF user_object.barred IS TRUE THEN
12526         result.fail_part := 'actor.usr.barred';
12527         result.success := FALSE;
12528         done := TRUE;
12529         RETURN NEXT result;
12530     END IF;
12531
12532     -- Fail if the item can't circulate
12533     IF item_object.circulate IS FALSE THEN
12534         result.fail_part := 'asset.copy.circulate';
12535         result.success := FALSE;
12536         done := TRUE;
12537         RETURN NEXT result;
12538     END IF;
12539
12540     -- Fail if the item isn't in a circulateable status on a non-renewal
12541     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
12542         result.fail_part := 'asset.copy.status';
12543         result.success := FALSE;
12544         done := TRUE;
12545         RETURN NEXT result;
12546     -- Alternately, fail if the item isn't checked out on a renewal
12547     ELSIF renewal AND item_object.status <> 1 THEN
12548         result.fail_part := 'asset.copy.status';
12549         result.success := FALSE;
12550         done := TRUE;
12551         RETURN NEXT result;
12552     END IF;
12553
12554     -- Fail if the item can't circulate because of the shelving location
12555     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
12556     IF item_location_object.circulate IS FALSE THEN
12557         result.fail_part := 'asset.copy_location.circulate';
12558         result.success := FALSE;
12559         done := TRUE;
12560         RETURN NEXT result;
12561     END IF;
12562
12563     -- Use Circ OU for penalties and such
12564     SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );
12565
12566     IF renewal THEN
12567         penalty_type = '%RENEW%';
12568     ELSE
12569         penalty_type = '%CIRC%';
12570     END IF;
12571
12572     FOR standing_penalty IN
12573         SELECT  DISTINCT csp.*
12574           FROM  actor.usr_standing_penalty usp
12575                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
12576           WHERE usr = match_user
12577                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
12578                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
12579                 AND csp.block_list LIKE penalty_type LOOP
12580
12581         result.fail_part := standing_penalty.name;
12582         result.success := FALSE;
12583         done := TRUE;
12584         RETURN NEXT result;
12585     END LOOP;
12586
12587     -- Fail if the test is set to hard non-circulating
12588     IF circ_matchpoint.circulate IS FALSE THEN
12589         result.fail_part := 'config.circ_matrix_test.circulate';
12590         result.success := FALSE;
12591         done := TRUE;
12592         RETURN NEXT result;
12593     END IF;
12594
12595     -- Fail if the total copy-hold ratio is too low
12596     IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
12597         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
12598         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
12599             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
12600             result.success := FALSE;
12601             done := TRUE;
12602             RETURN NEXT result;
12603         END IF;
12604     END IF;
12605
12606     -- Fail if the available copy-hold ratio is too low
12607     IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
12608         IF hold_ratio.hold_count IS NULL THEN
12609             SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
12610         END IF;
12611         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
12612             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
12613             result.success := FALSE;
12614             done := TRUE;
12615             RETURN NEXT result;
12616         END IF;
12617     END IF;
12618
12619     -- Fail if the user has too many items out by defined limit sets
12620     FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
12621       JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
12622       WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
12623         ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
12624         ) LOOP
12625             IF circ_limit_set.items_out > 0 AND NOT renewal THEN
12626                 SELECT INTO context_org_list ARRAY_AGG(aou.id)
12627                   FROM actor.org_unit_full_path( circ_ou ) aou
12628                     JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
12629                   WHERE aout.depth >= circ_limit_set.depth;
12630                 IF circ_limit_set.global THEN
12631                     WITH RECURSIVE descendant_depth AS (
12632                         SELECT  ou.id,
12633                             ou.parent_ou
12634                         FROM  actor.org_unit ou
12635                         WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
12636                             UNION
12637                         SELECT  ou.id,
12638                             ou.parent_ou
12639                         FROM  actor.org_unit ou
12640                             JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
12641                     ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
12642                 END IF;
12643                 SELECT INTO items_out COUNT(DISTINCT circ.id)
12644                   FROM action.circulation circ
12645                     JOIN asset.copy copy ON (copy.id = circ.target_copy)
12646                     LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
12647                   WHERE circ.usr = match_user
12648                     AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
12649                     AND circ.checkin_time IS NULL
12650                     AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
12651                     AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
12652                         OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
12653                     );
12654                 IF items_out >= circ_limit_set.items_out THEN
12655                     result.fail_part := 'config.circ_matrix_circ_mod_test';
12656                     result.success := FALSE;
12657                     done := TRUE;
12658                     RETURN NEXT result;
12659                 END IF;
12660             END IF;
12661             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;
12662     END LOOP;
12663
12664     -- If we passed everything, return the successful matchpoint
12665     IF NOT done THEN
12666         RETURN NEXT result;
12667     END IF;
12668
12669     RETURN;
12670 END;
12671 $func$ LANGUAGE plpgsql;
12672
12673 -- We need to re-create these, as they got dropped with the type above.
12674 CREATE OR REPLACE FUNCTION action.item_user_circ_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
12675     SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );
12676 $func$ LANGUAGE SQL;
12677
12678 CREATE OR REPLACE FUNCTION action.item_user_renew_test( INT, BIGINT, INT ) RETURNS SETOF action.circ_matrix_test_result AS $func$
12679     SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );
12680 $func$ LANGUAGE SQL;
12681
12682 -- Temp function for migrating circ mod limits.
12683 CREATE OR REPLACE FUNCTION evergreen.temp_migrate_circ_mod_limits() RETURNS VOID AS $func$
12684 DECLARE
12685     circ_mod_group config.circ_matrix_circ_mod_test%ROWTYPE;
12686     current_set INT;
12687     circ_mod_count INT;
12688 BEGIN
12689     FOR circ_mod_group IN SELECT * FROM config.circ_matrix_circ_mod_test LOOP
12690         INSERT INTO config.circ_limit_set(name, owning_lib, items_out, depth, global, description)
12691             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'
12692                 FROM config.circ_matrix_matchpoint WHERE id = circ_mod_group.matchpoint
12693             RETURNING id INTO current_set;
12694         INSERT INTO config.circ_matrix_limit_set_map(matchpoint, limit_set, fallthrough, active) VALUES (circ_mod_group.matchpoint, current_set, false, true);
12695         INSERT INTO config.circ_limit_set_circ_mod_map(limit_set, circ_mod)
12696             SELECT current_set, circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = circ_mod_group.id;
12697         SELECT INTO circ_mod_count count(id) FROM config.circ_limit_set_circ_mod_map WHERE limit_set = current_set;
12698         RAISE NOTICE 'Created limit set with id % and % circ modifiers attached to matchpoint %', current_set, circ_mod_count, circ_mod_group.matchpoint;
12699     END LOOP;
12700 END;
12701 $func$ LANGUAGE plpgsql;
12702
12703 -- Run the temp function
12704 SELECT * FROM evergreen.temp_migrate_circ_mod_limits();
12705
12706 -- Drop the temp function
12707 DROP FUNCTION evergreen.temp_migrate_circ_mod_limits();
12708
12709 --Drop the old tables
12710 --Not sure we want to do this. Keeping them may help "something went wrong" correction.
12711 --DROP TABLE IF EXISTS config.circ_matrix_circ_mod_test_map, config.circ_matrix_circ_mod_test;
12712
12713
12714 -- Evergreen DB patch 0678.data.vandelay-default-merge-profiles.sql
12715
12716 -- check whether patch can be applied
12717 SELECT evergreen.upgrade_deps_block_check('0678', :eg_version);
12718
12719 INSERT INTO vandelay.merge_profile (owner, name, replace_spec) 
12720     VALUES (1, 'Match-Only Merge', '901c');
12721
12722 INSERT INTO vandelay.merge_profile (owner, name, preserve_spec) 
12723     VALUES (1, 'Full Overlay', '901c');
12724
12725 SELECT evergreen.upgrade_deps_block_check('0679', :eg_version);
12726
12727 -- Address typo in column name
12728 ALTER TABLE config.metabib_class ADD COLUMN buoyant BOOL DEFAULT FALSE NOT NULL;
12729 UPDATE config.metabib_class SET buoyant = bouyant;
12730 ALTER TABLE config.metabib_class DROP COLUMN bouyant;
12731
12732 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
12733 DECLARE
12734     normalizer      RECORD;
12735     value           TEXT := '';
12736 BEGIN
12737
12738     value := NEW.value;
12739
12740     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12741         FOR normalizer IN
12742             SELECT  n.func AS func,
12743                     n.param_count AS param_count,
12744                     m.params AS params
12745               FROM  config.index_normalizer n
12746                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12747               WHERE field = NEW.field AND m.pos < 0
12748               ORDER BY m.pos LOOP
12749                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12750                     quote_literal( value ) ||
12751                     CASE
12752                         WHEN normalizer.param_count > 0
12753                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12754                             ELSE ''
12755                         END ||
12756                     ')' INTO value;
12757
12758         END LOOP;
12759
12760         NEW.value := value;
12761     END IF;
12762
12763     IF NEW.index_vector = ''::tsvector THEN
12764         RETURN NEW;
12765     END IF;
12766
12767     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
12768         FOR normalizer IN
12769             SELECT  n.func AS func,
12770                     n.param_count AS param_count,
12771                     m.params AS params
12772               FROM  config.index_normalizer n
12773                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
12774               WHERE field = NEW.field AND m.pos >= 0
12775               ORDER BY m.pos LOOP
12776                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
12777                     quote_literal( value ) ||
12778                     CASE
12779                         WHEN normalizer.param_count > 0
12780                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
12781                             ELSE ''
12782                         END ||
12783                     ')' INTO value;
12784
12785         END LOOP;
12786     END IF;
12787
12788     IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN
12789         value :=  ARRAY_TO_STRING(
12790             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12791         );
12792         value := public.search_normalize(value);
12793     END IF;
12794
12795     NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
12796
12797     RETURN NEW;
12798 END;
12799 $$ LANGUAGE PLPGSQL;
12800
12801 -- Given a string such as a user might type into a search box, prepare
12802 -- two changed variants for TO_TSQUERY(). See
12803 -- http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
12804 -- The first variant is normalized to match indexed documents regardless
12805 -- of diacritics.  The second variant keeps its diacritics for proper
12806 -- highlighting via TS_HEADLINE().
12807 CREATE OR REPLACE
12808     FUNCTION metabib.autosuggest_prepare_tsquery(orig TEXT) RETURNS TEXT[] AS
12809 $$
12810 DECLARE
12811     orig_ended_in_space     BOOLEAN;
12812     result                  RECORD;
12813     plain                   TEXT;
12814     normalized              TEXT;
12815 BEGIN
12816     orig_ended_in_space := orig ~ E'\\s$';
12817
12818     orig := ARRAY_TO_STRING(
12819         evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
12820     );
12821
12822     normalized := public.search_normalize(orig); -- also trim()s
12823     plain := trim(orig);
12824
12825     IF NOT orig_ended_in_space THEN
12826         plain := plain || ':*';
12827         normalized := normalized || ':*';
12828     END IF;
12829
12830     plain := ARRAY_TO_STRING(
12831         evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
12832     );
12833     normalized := ARRAY_TO_STRING(
12834         evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
12835     );
12836
12837     RETURN ARRAY[normalized, plain];
12838 END;
12839 $$ LANGUAGE PLPGSQL;
12840
12841
12842 -- Definition of OUT parameters changes, so must drop first
12843 DROP FUNCTION IF EXISTS metabib.suggest_browse_entries (TEXT, TEXT, TEXT, INTEGER, INTEGER, INTEGER);
12844
12845 CREATE OR REPLACE
12846     FUNCTION metabib.suggest_browse_entries(
12847         raw_query_text  TEXT,   -- actually typed by humans at the UI level
12848         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
12849         headline_opts   TEXT,   -- markup options for ts_headline()
12850         visibility_org  INTEGER,-- null if you don't want opac visibility test
12851         query_limit     INTEGER,-- use in LIMIT clause of interal query
12852         normalization   INTEGER -- argument to TS_RANK_CD()
12853     ) RETURNS TABLE (
12854         value                   TEXT,   -- plain
12855         field                   INTEGER,
12856         buoyant_and_class_match BOOL,
12857         field_match             BOOL,
12858         field_weight            INTEGER,
12859         rank                    REAL,
12860         buoyant                 BOOL,
12861         match                   TEXT    -- marked up
12862     ) AS $func$
12863 DECLARE
12864     prepared_query_texts    TEXT[];
12865     query                   TSQUERY;
12866     plain_query             TSQUERY;
12867     opac_visibility_join    TEXT;
12868     search_class_join       TEXT;
12869     r_fields                RECORD;
12870 BEGIN
12871     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
12872
12873     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
12874     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
12875
12876     IF visibility_org IS NOT NULL THEN
12877         opac_visibility_join := '
12878     JOIN asset.opac_visible_copies aovc ON (
12879         aovc.record = mbedm.source AND
12880         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
12881     )';
12882     ELSE
12883         opac_visibility_join := '';
12884     END IF;
12885
12886     -- The following determines whether we only provide suggestsons matching
12887     -- the user's selected search_class, or whether we show other suggestions
12888     -- too. The reason for MIN() is that for search_classes like
12889     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
12890     -- implication is that if title as a class doesn't have restrict,
12891     -- nor does the proper field, but the uniform field does, you're going
12892     -- to get 'false' for your overall evaluation of 'should we restrict?'
12893     -- To invert that, change from MIN() to MAX().
12894
12895     SELECT
12896         INTO r_fields
12897             MIN(cmc.restrict::INT) AS restrict_class,
12898             MIN(cmf.restrict::INT) AS restrict_field
12899         FROM metabib.search_class_to_registered_components(search_class)
12900             AS _registered (field_class TEXT, field INT)
12901         JOIN
12902             config.metabib_class cmc ON (cmc.name = _registered.field_class)
12903         LEFT JOIN
12904             config.metabib_field cmf ON (cmf.id = _registered.field);
12905
12906     -- evaluate 'should we restrict?'
12907     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
12908         search_class_join := '
12909     JOIN
12910         metabib.search_class_to_registered_components($2)
12911         AS _registered (field_class TEXT, field INT) ON (
12912             (_registered.field IS NULL AND
12913                 _registered.field_class = cmf.field_class) OR
12914             (_registered.field = cmf.id)
12915         )
12916     ';
12917     ELSE
12918         search_class_join := '
12919     LEFT JOIN
12920         metabib.search_class_to_registered_components($2)
12921         AS _registered (field_class TEXT, field INT) ON (
12922             _registered.field_class = cmc.name
12923         )
12924     ';
12925     END IF;
12926
12927     RETURN QUERY EXECUTE 'SELECT *, TS_HEADLINE(value, $7, $3) FROM (SELECT DISTINCT
12928         mbe.value,
12929         cmf.id,
12930         cmc.buoyant AND _registered.field_class IS NOT NULL,
12931         _registered.field = cmf.id,
12932         cmf.weight,
12933         TS_RANK_CD(mbe.index_vector, $1, $6),
12934         cmc.buoyant
12935     FROM metabib.browse_entry_def_map mbedm
12936     JOIN metabib.browse_entry mbe ON (mbe.id = mbedm.entry)
12937     JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
12938     JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
12939     '  || search_class_join || opac_visibility_join ||
12940     ' WHERE $1 @@ mbe.index_vector
12941     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12942     LIMIT $5) x
12943     ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
12944     '   -- sic, repeat the order by clause in the outer select too
12945     USING
12946         query, search_class, headline_opts,
12947         visibility_org, query_limit, normalization, plain_query
12948         ;
12949
12950     -- sort order:
12951     --  buoyant AND chosen class = match class
12952     --  chosen field = match field
12953     --  field weight
12954     --  rank
12955     --  buoyancy
12956     --  value itself
12957
12958 END;
12959 $func$ LANGUAGE PLPGSQL;
12960
12961
12962 \qecho 
12963 \qecho The following takes about a minute per 100,000 rows in
12964 \qecho metabib.browse_entry on my development system, which is only a VM with
12965 \qecho 4 GB of memory and 2 cores.
12966 \qecho 
12967 \qecho The following is a very loose estimate of how long the next UPDATE
12968 \qecho statement would take to finish on MY machine, based on YOUR number
12969 \qecho of rows in metabib.browse_entry:
12970 \qecho 
12971
12972 SELECT (COUNT(id) / 100000.0) * INTERVAL '1 minute'
12973     AS "approximate duration of following UPDATE statement"
12974     FROM metabib.browse_entry;
12975
12976 UPDATE metabib.browse_entry SET index_vector = TO_TSVECTOR(
12977     'keyword',
12978     public.search_normalize(
12979         ARRAY_TO_STRING(
12980             evergreen.regexp_split_to_array(value, E'\\W+'), ' '
12981         )
12982     )
12983 );
12984
12985
12986 SELECT evergreen.upgrade_deps_block_check('0680', :eg_version);
12987
12988 -- Not much use in having identifier-class fields be suggestions. Credit for the idea goes to Ben Shum.
12989 UPDATE config.metabib_field SET browse_field = FALSE WHERE id < 100 AND field_class = 'identifier';
12990
12991
12992 ---------------------------------------------------------------------------
12993 -- The rest of this was tested on Evergreen Indiana's dev server, which has
12994 -- a large data set  of 2.6M bibs, and was instrumental in sussing out the
12995 -- needed adjustments.  Thanks, EG-IN!
12996 ---------------------------------------------------------------------------
12997
12998 -- GIN indexes are /much/ better for prefix matching, which is important for browse and autosuggest
12999 --Commented out the creation earlier, so we don't need to drop it here.
13000 --DROP INDEX metabib.metabib_browse_entry_index_vector_idx;
13001 CREATE INDEX metabib_browse_entry_index_vector_idx ON metabib.browse_entry USING GIN (index_vector);
13002
13003
13004 -- We need thes to make the autosuggest limiting joins fast
13005 CREATE INDEX browse_entry_def_map_def_idx ON metabib.browse_entry_def_map (def);
13006 CREATE INDEX browse_entry_def_map_entry_idx ON metabib.browse_entry_def_map (entry);
13007 CREATE INDEX browse_entry_def_map_source_idx ON metabib.browse_entry_def_map (source);
13008
13009 -- In practice this will always be ~1 row, and the default of 1000 causes terrible plans
13010 ALTER FUNCTION metabib.search_class_to_registered_components(text) ROWS 1;
13011
13012 -- Reworking of the generated query to act in a sane manner in the face of large datasets
13013 CREATE OR REPLACE
13014     FUNCTION metabib.suggest_browse_entries(
13015         raw_query_text  TEXT,   -- actually typed by humans at the UI level
13016         search_class    TEXT,   -- 'alias' or 'class' or 'class|field..', etc
13017         headline_opts   TEXT,   -- markup options for ts_headline()
13018         visibility_org  INTEGER,-- null if you don't want opac visibility test
13019         query_limit     INTEGER,-- use in LIMIT clause of interal query
13020         normalization   INTEGER -- argument to TS_RANK_CD()
13021     ) RETURNS TABLE (
13022         value                   TEXT,   -- plain
13023         field                   INTEGER,
13024         buoyant_and_class_match BOOL,
13025         field_match             BOOL,
13026         field_weight            INTEGER,
13027         rank                    REAL,
13028         buoyant                 BOOL,
13029         match                   TEXT    -- marked up
13030     ) AS $func$
13031 DECLARE
13032     prepared_query_texts    TEXT[];
13033     query                   TSQUERY;
13034     plain_query             TSQUERY;
13035     opac_visibility_join    TEXT;
13036     search_class_join       TEXT;
13037     r_fields                RECORD;
13038 BEGIN
13039     prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);
13040
13041     query := TO_TSQUERY('keyword', prepared_query_texts[1]);
13042     plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);
13043
13044     IF visibility_org IS NOT NULL THEN
13045         opac_visibility_join := '
13046     JOIN asset.opac_visible_copies aovc ON (
13047         aovc.record = x.source AND
13048         aovc.circ_lib IN (SELECT id FROM actor.org_unit_descendants($4))
13049     )';
13050     ELSE
13051         opac_visibility_join := '';
13052     END IF;
13053
13054     -- The following determines whether we only provide suggestsons matching
13055     -- the user's selected search_class, or whether we show other suggestions
13056     -- too. The reason for MIN() is that for search_classes like
13057     -- 'title|proper|uniform' you would otherwise get multiple rows.  The
13058     -- implication is that if title as a class doesn't have restrict,
13059     -- nor does the proper field, but the uniform field does, you're going
13060     -- to get 'false' for your overall evaluation of 'should we restrict?'
13061     -- To invert that, change from MIN() to MAX().
13062
13063     SELECT
13064         INTO r_fields
13065             MIN(cmc.restrict::INT) AS restrict_class,
13066             MIN(cmf.restrict::INT) AS restrict_field
13067         FROM metabib.search_class_to_registered_components(search_class)
13068             AS _registered (field_class TEXT, field INT)
13069         JOIN
13070             config.metabib_class cmc ON (cmc.name = _registered.field_class)
13071         LEFT JOIN
13072             config.metabib_field cmf ON (cmf.id = _registered.field);
13073
13074     -- evaluate 'should we restrict?'
13075     IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
13076         search_class_join := '
13077     JOIN
13078         metabib.search_class_to_registered_components($2)
13079         AS _registered (field_class TEXT, field INT) ON (
13080             (_registered.field IS NULL AND
13081                 _registered.field_class = cmf.field_class) OR
13082             (_registered.field = cmf.id)
13083         )
13084     ';
13085     ELSE
13086         search_class_join := '
13087     LEFT JOIN
13088         metabib.search_class_to_registered_components($2)
13089         AS _registered (field_class TEXT, field INT) ON (
13090             _registered.field_class = cmc.name
13091         )
13092     ';
13093     END IF;
13094
13095     RETURN QUERY EXECUTE '
13096 SELECT  DISTINCT
13097         x.value,
13098         x.id,
13099         x.push,
13100         x.restrict,
13101         x.weight,
13102         x.ts_rank_cd,
13103         x.buoyant,
13104         TS_HEADLINE(value, $7, $3)
13105   FROM  (SELECT DISTINCT
13106                 mbe.value,
13107                 cmf.id,
13108                 cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
13109                 _registered.field = cmf.id AS restrict,
13110                 cmf.weight,
13111                 TS_RANK_CD(mbe.index_vector, $1, $6),
13112                 cmc.buoyant,
13113                 mbedm.source
13114           FROM  metabib.browse_entry_def_map mbedm
13115
13116                 -- Start with a pre-limited set of 10k possible suggestions. More than that is not going to be useful anyway
13117                 JOIN (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000) mbe ON (mbe.id = mbedm.entry)
13118
13119                 JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
13120                 JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
13121                 '  || search_class_join || '
13122           ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
13123           LIMIT 1000) AS x -- This outer limit makes testing for opac visibility usably fast
13124         ' || opac_visibility_join || '
13125   ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
13126   LIMIT $5
13127 '   -- sic, repeat the order by clause in the outer select too
13128     USING
13129         query, search_class, headline_opts,
13130         visibility_org, query_limit, normalization, plain_query
13131         ;
13132
13133     -- sort order:
13134     --  buoyant AND chosen class = match class
13135     --  chosen field = match field
13136     --  field weight
13137     --  rank
13138     --  buoyancy
13139     --  value itself
13140
13141 END;
13142 $func$ LANGUAGE PLPGSQL;
13143
13144
13145 -- Evergreen DB patch 0681.schema.user-activity.sql
13146 --
13147
13148 -- check whether patch can be applied
13149 SELECT evergreen.upgrade_deps_block_check('0681', :eg_version);
13150
13151 -- SCHEMA --
13152
13153 CREATE TYPE config.usr_activity_group AS ENUM ('authen','authz','circ','hold','search');
13154
13155 CREATE TABLE config.usr_activity_type (
13156     id          SERIAL                      PRIMARY KEY, 
13157     ewho        TEXT,
13158     ewhat       TEXT,
13159     ehow        TEXT,
13160     label       TEXT                        NOT NULL, -- i18n
13161     egroup      config.usr_activity_group   NOT NULL,
13162     enabled     BOOL                        NOT NULL DEFAULT TRUE,
13163     transient   BOOL                        NOT NULL DEFAULT FALSE,
13164     CONSTRAINT  one_of_wwh CHECK (COALESCE(ewho,ewhat,ehow) IS NOT NULL)
13165 );
13166
13167 CREATE UNIQUE INDEX unique_wwh ON config.usr_activity_type 
13168     (COALESCE(ewho,''), COALESCE (ewhat,''), COALESCE(ehow,''));
13169
13170 CREATE TABLE actor.usr_activity (
13171     id          BIGSERIAL   PRIMARY KEY,
13172     usr         INT         REFERENCES actor.usr (id) ON DELETE SET NULL,
13173     etype       INT         NOT NULL REFERENCES config.usr_activity_type (id),
13174     event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
13175 );
13176
13177 -- remove transient activity entries on insert of new entries
13178 CREATE OR REPLACE FUNCTION actor.usr_activity_transient_trg () RETURNS TRIGGER AS $$
13179 BEGIN
13180     DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
13181         WHERE atype.transient AND 
13182             NEW.etype = atype.id AND
13183             act.etype = atype.id AND
13184             act.usr = NEW.usr;
13185     RETURN NEW;
13186 END;
13187 $$ LANGUAGE PLPGSQL;
13188
13189 CREATE TRIGGER remove_transient_usr_activity
13190     BEFORE INSERT ON actor.usr_activity
13191     FOR EACH ROW EXECUTE PROCEDURE actor.usr_activity_transient_trg();
13192
13193 -- given a set of activity criteria, find the most approprate activity type
13194 CREATE OR REPLACE FUNCTION actor.usr_activity_get_type (
13195         ewho TEXT, 
13196         ewhat TEXT, 
13197         ehow TEXT
13198     ) RETURNS SETOF config.usr_activity_type AS $$
13199 SELECT * FROM config.usr_activity_type 
13200     WHERE 
13201         enabled AND 
13202         (ewho  IS NULL OR ewho  = $1) AND
13203         (ewhat IS NULL OR ewhat = $2) AND
13204         (ehow  IS NULL OR ehow  = $3) 
13205     ORDER BY 
13206         -- BOOL comparisons sort false to true
13207         COALESCE(ewho, '')  != COALESCE($1, ''),
13208         COALESCE(ewhat,'')  != COALESCE($2, ''),
13209         COALESCE(ehow, '')  != COALESCE($3, '') 
13210     LIMIT 1;
13211 $$ LANGUAGE SQL;
13212
13213 -- given a set of activity criteria, finds the best
13214 -- activity type and inserts the activity entry
13215 CREATE OR REPLACE FUNCTION actor.insert_usr_activity (
13216         usr INT,
13217         ewho TEXT, 
13218         ewhat TEXT, 
13219         ehow TEXT
13220     ) RETURNS SETOF actor.usr_activity AS $$
13221 DECLARE
13222     new_row actor.usr_activity%ROWTYPE;
13223 BEGIN
13224     SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
13225     IF FOUND THEN
13226         new_row.usr := usr;
13227         INSERT INTO actor.usr_activity (usr, etype) 
13228             VALUES (usr, new_row.etype)
13229             RETURNING * INTO new_row;
13230         RETURN NEXT new_row;
13231     END IF;
13232 END;
13233 $$ LANGUAGE plpgsql;
13234
13235 -- SEED DATA --
13236
13237 INSERT INTO config.usr_activity_type (id, ewho, ewhat, ehow, egroup, label) VALUES
13238
13239      -- authen/authz actions
13240      -- note: "opensrf" is the default ingress/ehow
13241      (1,  NULL, 'login',  'opensrf',      'authen', oils_i18n_gettext(1 , 'Login via opensrf', 'cuat', 'label'))
13242     ,(2,  NULL, 'login',  'srfsh',        'authen', oils_i18n_gettext(2 , 'Login via srfsh', 'cuat', 'label'))
13243     ,(3,  NULL, 'login',  'gateway-v1',   'authen', oils_i18n_gettext(3 , 'Login via gateway-v1', 'cuat', 'label'))
13244     ,(4,  NULL, 'login',  'translator-v1','authen', oils_i18n_gettext(4 , 'Login via translator-v1', 'cuat', 'label'))
13245     ,(5,  NULL, 'login',  'xmlrpc',       'authen', oils_i18n_gettext(5 , 'Login via xmlrpc', 'cuat', 'label'))
13246     ,(6,  NULL, 'login',  'remoteauth',   'authen', oils_i18n_gettext(6 , 'Login via remoteauth', 'cuat', 'label'))
13247     ,(7,  NULL, 'login',  'sip2',         'authen', oils_i18n_gettext(7 , 'SIP2 Proxy Login', 'cuat', 'label'))
13248     ,(8,  NULL, 'login',  'apache',       'authen', oils_i18n_gettext(8 , 'Login via Apache module', 'cuat', 'label'))
13249
13250     ,(9,  NULL, 'verify', 'opensrf',      'authz',  oils_i18n_gettext(9 , 'Verification via opensrf', 'cuat', 'label'))
13251     ,(10, NULL, 'verify', 'srfsh',        'authz',  oils_i18n_gettext(10, 'Verification via srfsh', 'cuat', 'label'))
13252     ,(11, NULL, 'verify', 'gateway-v1',   'authz',  oils_i18n_gettext(11, 'Verification via gateway-v1', 'cuat', 'label'))
13253     ,(12, NULL, 'verify', 'translator-v1','authz',  oils_i18n_gettext(12, 'Verification via translator-v1', 'cuat', 'label'))
13254     ,(13, NULL, 'verify', 'xmlrpc',       'authz',  oils_i18n_gettext(13, 'Verification via xmlrpc', 'cuat', 'label'))
13255     ,(14, NULL, 'verify', 'remoteauth',   'authz',  oils_i18n_gettext(14, 'Verification via remoteauth', 'cuat', 'label'))
13256     ,(15, NULL, 'verify', 'sip2',         'authz',  oils_i18n_gettext(15, 'SIP2 User Verification', 'cuat', 'label'))
13257
13258      -- authen/authz actions w/ known uses of "who"
13259     ,(16, 'opac',        'login',  'gateway-v1',   'authen', oils_i18n_gettext(16, 'OPAC Login (jspac)', 'cuat', 'label'))
13260     ,(17, 'opac',        'login',  'apache',       'authen', oils_i18n_gettext(17, 'OPAC Login (tpac)', 'cuat', 'label'))
13261     ,(18, 'staffclient', 'login',  'gateway-v1',   'authen', oils_i18n_gettext(18, 'Staff Client Login', 'cuat', 'label'))
13262     ,(19, 'selfcheck',   'login',  'translator-v1','authen', oils_i18n_gettext(19, 'Self-Check Proxy Login', 'cuat', 'label'))
13263     ,(20, 'ums',         'login',  'xmlrpc',       'authen', oils_i18n_gettext(20, 'Unique Mgt Login', 'cuat', 'label'))
13264     ,(21, 'authproxy',   'login',  'apache',       'authen', oils_i18n_gettext(21, 'Apache Auth Proxy Login', 'cuat', 'label'))
13265     ,(22, 'libraryelf',  'login',  'xmlrpc',       'authz',  oils_i18n_gettext(22, 'LibraryElf Login', 'cuat', 'label'))
13266
13267     ,(23, 'selfcheck',   'verify', 'translator-v1','authz',  oils_i18n_gettext(23, 'Self-Check User Verification', 'cuat', 'label'))
13268     ,(24, 'ezproxy',     'verify', 'remoteauth',   'authz',  oils_i18n_gettext(24, 'EZProxy Verification', 'cuat', 'label'))
13269     -- ...
13270     ;
13271
13272 -- reserve the first 1000 slots
13273 SELECT SETVAL('config.usr_activity_type_id_seq'::TEXT, 1000);
13274
13275 INSERT INTO config.org_unit_setting_type 
13276     (name, label, description, grp, datatype) 
13277     VALUES (
13278         'circ.patron.usr_activity_retrieve.max',
13279          oils_i18n_gettext(
13280             'circ.patron.usr_activity_retrieve.max',
13281             'Max user activity entries to retrieve (staff client)',
13282             'coust', 
13283             'label'
13284         ),
13285         oils_i18n_gettext(
13286             'circ.patron.usr_activity_retrieve.max',
13287             '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.',
13288             'coust', 
13289             'description'
13290         ),
13291         'gui',
13292         'integer'
13293     );
13294
13295
13296 SELECT evergreen.upgrade_deps_block_check('0682', :eg_version);
13297
13298 CREATE TABLE asset.copy_location_group (
13299     id              SERIAL  PRIMARY KEY,
13300     name            TEXT    NOT NULL, -- i18n
13301     owner           INT     NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13302     pos             INT     NOT NULL DEFAULT 0,
13303     top             BOOL    NOT NULL DEFAULT FALSE,
13304     opac_visible    BOOL    NOT NULL DEFAULT TRUE,
13305     CONSTRAINT lgroup_once_per_owner UNIQUE (owner,name)
13306 );
13307
13308 CREATE TABLE asset.copy_location_group_map (
13309     id       SERIAL PRIMARY KEY,
13310     location    INT     NOT NULL REFERENCES asset.copy_location (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13311     lgroup      INT     NOT NULL REFERENCES asset.copy_location_group (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
13312     CONSTRAINT  lgroup_once_per_group UNIQUE (lgroup,location)
13313 );
13314
13315 -- check whether patch can be applied
13316 SELECT evergreen.upgrade_deps_block_check('0683', :eg_version);
13317
13318 INSERT INTO action_trigger.event_params (event_def, param, value)
13319     VALUES (5, 'check_email_notify', 1);
13320 INSERT INTO action_trigger.event_params (event_def, param, value)
13321     VALUES (7, 'check_email_notify', 1);
13322 INSERT INTO action_trigger.event_params (event_def, param, value)
13323     VALUES (9, 'check_email_notify', 1);
13324 INSERT INTO action_trigger.validator (module,description) VALUES
13325     ('HoldNotifyCheck',
13326     oils_i18n_gettext(
13327         'HoldNotifyCheck',
13328         'Check Hold notification flag(s)',
13329         'atval',
13330         'description'
13331     ));
13332 UPDATE action_trigger.event_definition SET validator = 'HoldNotifyCheck' WHERE id = 9;
13333
13334 -- NOT COVERED: Adding check_sms_notify to the proper trigger. It doesn't have a static id.
13335
13336 -- check whether patch can be applied
13337 SELECT evergreen.upgrade_deps_block_check('0684', :eg_version);
13338
13339 -- schema --
13340
13341 -- Replace the constraints with more flexible ENUM's
13342 ALTER TABLE vandelay.queue DROP CONSTRAINT queue_queue_type_check;
13343 ALTER TABLE vandelay.bib_queue DROP CONSTRAINT bib_queue_queue_type_check;
13344 ALTER TABLE vandelay.authority_queue DROP CONSTRAINT authority_queue_queue_type_check;
13345
13346 CREATE TYPE vandelay.bib_queue_queue_type AS ENUM ('bib', 'acq');
13347 CREATE TYPE vandelay.authority_queue_queue_type AS ENUM ('authority');
13348
13349 -- dropped column is also implemented by the child tables
13350 ALTER TABLE vandelay.queue DROP COLUMN queue_type; 
13351
13352 -- to recover after using the undo sql from below
13353 -- alter table vandelay.bib_queue  add column queue_type text default 'bib' not null;
13354 -- alter table vandelay.authority_queue  add column queue_type text default 'authority' not null;
13355
13356 -- modify the child tables to use the ENUMs
13357 ALTER TABLE vandelay.bib_queue 
13358     ALTER COLUMN queue_type DROP DEFAULT,
13359     ALTER COLUMN queue_type TYPE vandelay.bib_queue_queue_type 
13360         USING (queue_type::vandelay.bib_queue_queue_type),
13361     ALTER COLUMN queue_type SET DEFAULT 'bib';
13362
13363 ALTER TABLE vandelay.authority_queue 
13364     ALTER COLUMN queue_type DROP DEFAULT,
13365     ALTER COLUMN queue_type TYPE vandelay.authority_queue_queue_type 
13366         USING (queue_type::vandelay.authority_queue_queue_type),
13367     ALTER COLUMN queue_type SET DEFAULT 'authority';
13368
13369 -- give lineitems a pointer to their vandelay queued_record
13370
13371 ALTER TABLE acq.lineitem ADD COLUMN queued_record BIGINT
13372     REFERENCES vandelay.queued_bib_record (id) 
13373     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
13374
13375 ALTER TABLE acq.acq_lineitem_history ADD COLUMN queued_record BIGINT
13376     REFERENCES vandelay.queued_bib_record (id) 
13377     ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
13378
13379 -- seed data --
13380
13381 INSERT INTO permission.perm_list ( id, code, description ) 
13382     VALUES ( 
13383         521, 
13384         'IMPORT_ACQ_LINEITEM_BIB_RECORD_UPLOAD', 
13385         oils_i18n_gettext( 
13386             521,
13387             'Allows a user to create new bibs directly from an ACQ MARC file upload', 
13388             'ppl', 
13389             'description' 
13390         )
13391     );
13392
13393
13394 INSERT INTO vandelay.import_error ( code, description ) 
13395     VALUES ( 
13396         'import.record.perm_failure', 
13397         oils_i18n_gettext(
13398             'import.record.perm_failure', 
13399             'Perm failure creating a record', 'vie', 'description') 
13400     );
13401
13402
13403
13404
13405 -- Evergreen DB patch 0685.data.bluray_vr_format.sql
13406 --
13407 -- FIXME: insert description of change, if needed
13408 --
13409
13410
13411 -- check whether patch can be applied
13412 SELECT evergreen.upgrade_deps_block_check('0685', :eg_version);
13413
13414 -- FIXME: add/check SQL statements to perform the upgrade
13415 DO $FUNC$
13416 DECLARE
13417     same_marc BOOL;
13418 BEGIN
13419     -- Check if it is already there
13420     PERFORM * FROM config.marc21_physical_characteristic_value_map v
13421         JOIN config.marc21_physical_characteristic_subfield_map s ON v.ptype_subfield = s.id
13422         WHERE s.ptype_key = 'v' AND s.subfield = 'e' AND s.start_pos = '4' AND s.length = '1'
13423             AND v.value = 's';
13424
13425     -- If it is, bail.
13426     IF FOUND THEN
13427         RETURN;
13428     END IF;
13429
13430     -- Otherwise, insert it
13431     INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label)
13432     SELECT 's',id,'Blu-ray'
13433         FROM config.marc21_physical_characteristic_subfield_map
13434         WHERE ptype_key = 'v' AND subfield = 'e' AND start_pos = '4' AND length = '1';
13435
13436     -- And reingest the blue-ray items so that things see the new value
13437     SELECT INTO same_marc enabled FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc';
13438     UPDATE config.internal_flag SET enabled = true WHERE name = 'ingest.reingest.force_on_same_marc';
13439     UPDATE biblio.record_entry SET marc=marc WHERE id IN (SELECT record
13440         FROM
13441             metabib.full_rec a JOIN metabib.full_rec b USING (record)
13442         WHERE
13443             a.tag = 'LDR' AND a.value LIKE '______g%'
13444         AND b.tag = '007' AND b.value LIKE 'v___s%');
13445     UPDATE config.internal_flag SET enabled = same_marc WHERE name = 'ingest.reingest.force_on_same_marc';
13446 END;
13447 $FUNC$;
13448
13449
13450 -- Evergreen DB patch 0686.schema.auditor_boost.sql
13451 --
13452 -- FIXME: insert description of change, if needed
13453 --
13454 -- check whether patch can be applied
13455 SELECT evergreen.upgrade_deps_block_check('0686', :eg_version);
13456
13457 -- FIXME: add/check SQL statements to perform the upgrade
13458 -- These three functions are for capturing, getting, and clearing user and workstation information
13459
13460 -- Set the User AND workstation in one call. Tis faster. And less calls.
13461 -- First argument is user, second is workstation
13462 CREATE OR REPLACE FUNCTION auditor.set_audit_info(INT, INT) RETURNS VOID AS $$
13463     $_SHARED{"eg_audit_user"} = $_[0];
13464     $_SHARED{"eg_audit_ws"} = $_[1];
13465 $$ LANGUAGE plperl;
13466
13467 -- Get the User AND workstation in one call. Less calls, useful for joins ;)
13468 CREATE OR REPLACE FUNCTION auditor.get_audit_info() RETURNS TABLE (eg_user INT, eg_ws INT) AS $$
13469     return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];
13470 $$ LANGUAGE plperl;
13471
13472 -- Clear the audit info, for whatever reason
13473 CREATE OR REPLACE FUNCTION auditor.clear_audit_info() RETURNS VOID AS $$
13474     delete($_SHARED{"eg_audit_user"});
13475     delete($_SHARED{"eg_audit_ws"});
13476 $$ LANGUAGE plperl;
13477
13478 CREATE OR REPLACE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13479 BEGIN
13480     EXECUTE $$
13481         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
13482             audit_id    BIGINT                          PRIMARY KEY,
13483             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
13484             audit_action        TEXT                            NOT NULL,
13485             audit_user  INT,
13486             audit_ws    INT,
13487             LIKE $$ || sch || $$.$$ || tbl || $$
13488         );
13489     $$;
13490         RETURN TRUE;
13491 END;
13492 $creator$ LANGUAGE 'plpgsql';
13493
13494 CREATE OR REPLACE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13495 DECLARE
13496     column_list TEXT[];
13497 BEGIN
13498     SELECT INTO column_list array_agg(a.attname)
13499         FROM pg_catalog.pg_attribute a
13500             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
13501             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13502         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
13503
13504     EXECUTE $$
13505         CREATE OR REPLACE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
13506         RETURNS TRIGGER AS $func$
13507         BEGIN
13508             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, $$
13509             || array_to_string(column_list, ', ') || $$ )
13510                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
13511                     now(),
13512                     SUBSTR(TG_OP,1,1),
13513                     eg_user,
13514                     eg_ws,
13515                     OLD.$$ || array_to_string(column_list, ', OLD.') || $$
13516                 FROM auditor.get_audit_info();
13517             RETURN NULL;
13518         END;
13519         $func$ LANGUAGE 'plpgsql';
13520     $$;
13521     RETURN TRUE;
13522 END;
13523 $creator$ LANGUAGE 'plpgsql';
13524
13525 CREATE OR REPLACE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
13526 DECLARE
13527     column_list TEXT[];
13528 BEGIN
13529     SELECT INTO column_list array_agg(a.attname)
13530         FROM pg_catalog.pg_attribute a
13531             JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
13532             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13533         WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;
13534
13535     EXECUTE $$
13536         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
13537             SELECT -1 AS audit_id,
13538                    now() AS audit_time,
13539                    '-' AS audit_action,
13540                    -1 AS audit_user,
13541                    -1 AS audit_ws,
13542                    $$ || array_to_string(column_list, ', ') || $$
13543               FROM $$ || sch || $$.$$ || tbl || $$
13544                 UNION ALL
13545             SELECT audit_id, audit_time, audit_action, audit_user, audit_ws,
13546             $$ || array_to_string(column_list, ', ') || $$
13547               FROM auditor.$$ || sch || $$_$$ || tbl || $$_history;
13548     $$;
13549     RETURN TRUE;
13550 END;
13551 $creator$ LANGUAGE 'plpgsql';
13552
13553 -- Corrects all column discrepencies between audit table and core table:
13554 -- Adds missing columns
13555 -- Removes leftover columns
13556 -- Updates types
13557 -- Also, ensures all core auditor columns exist.
13558 CREATE OR REPLACE FUNCTION auditor.fix_columns() RETURNS VOID AS $BODY$
13559 DECLARE
13560     current_table TEXT = ''; -- Storage for post-loop main table name
13561     current_audit_table TEXT = ''; -- Storage for post-loop audit table name
13562     query TEXT = ''; -- Storage for built query
13563     cr RECORD; -- column record object
13564     alter_t BOOL = false; -- Has the alter table command been appended yet
13565     auditor_cores TEXT[] = ARRAY[]::TEXT[]; -- Core auditor function list (filled inside of loop)
13566     core_column TEXT; -- The current core column we are adding
13567 BEGIN
13568     FOR cr IN
13569         WITH audit_tables AS ( -- Basic grab of auditor tables. Anything in the auditor namespace, basically. With oids.
13570             SELECT c.oid AS audit_oid, c.relname AS audit_table
13571             FROM pg_catalog.pg_class c
13572             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13573             WHERE relkind='r' AND nspname = 'auditor'
13574         ),
13575         table_set AS ( -- Union of auditor tables with their "main" tables. With oids.
13576             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
13577             FROM pg_catalog.pg_class c
13578             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13579             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
13580             WHERE relkind = 'r'
13581         ),
13582         column_lists AS ( -- All columns associated with the auditor or main table, grouped by the main table's oid.
13583             SELECT DISTINCT ON (main_oid, attname) t.main_oid, a.attname
13584             FROM table_set t
13585             JOIN pg_catalog.pg_attribute a ON a.attrelid IN (t.main_oid, t.audit_oid)
13586             WHERE attnum > 0 AND NOT attisdropped
13587         ),
13588         column_defs AS ( -- The motherload, every audit table and main table plus column names and defs.
13589             SELECT audit_table,
13590                    main_namespace,
13591                    main_table,
13592                    a.attname AS main_column, -- These two will be null for columns that have since been deleted, or for auditor core columns
13593                    pg_catalog.format_type(a.atttypid, a.atttypmod) AS main_column_def,
13594                    b.attname AS audit_column, -- These two will be null for columns that have since been added
13595                    pg_catalog.format_type(b.atttypid, b.atttypmod) AS audit_column_def
13596             FROM table_set t
13597             JOIN column_lists c USING (main_oid)
13598             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
13599             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
13600         )
13601         -- Nice sorted output from the above
13602         SELECT * FROM column_defs WHERE main_column_def IS DISTINCT FROM audit_column_def ORDER BY main_namespace, main_table, main_column, audit_column
13603     LOOP
13604         IF current_table <> (cr.main_namespace || '.' || cr.main_table) THEN -- New table?
13605             FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Update missing core auditor columns
13606                 IF NOT alter_t THEN -- Add ALTER TABLE if we haven't already
13607                     query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13608                     alter_t:=TRUE;
13609                 ELSE
13610                     query:=query || $$,$$;
13611                 END IF;
13612                 -- 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.
13613                 query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
13614             END LOOP;
13615             IF alter_t THEN -- Open alter table = needs a semicolon
13616                 query:=query || $$; $$;
13617                 alter_t:=FALSE;
13618                 IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
13619                     -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
13620                     -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
13621                     EXECUTE query;
13622                     EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
13623                         $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
13624                     EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
13625                         $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
13626                     query:='';
13627                 END IF;
13628             END IF;
13629             -- New table means we reset the list of needed auditor core columns
13630             auditor_cores = ARRAY['audit_id bigint', 'audit_time timestamp with time zone', 'audit_action text', 'audit_user integer', 'audit_ws integer'];
13631             -- And store some values for use later, because we can't rely on cr in all places.
13632             current_table:=cr.main_namespace || '.' || cr.main_table;
13633             current_audit_table:=cr.audit_table;
13634         END IF;
13635         IF cr.main_column IS NULL AND cr.audit_column LIKE 'audit_%' THEN -- Core auditor column?
13636             -- Remove core from list of cores
13637             SELECT INTO auditor_cores array_agg(core) FROM unnest(auditor_cores) AS core WHERE core != (cr.audit_column || ' ' || cr.audit_column_def);
13638         ELSIF cr.main_column IS NULL THEN -- Main column doesn't exist, and it isn't an auditor column. Needs dropping from the auditor.
13639             IF NOT alter_t THEN
13640                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13641                 alter_t:=TRUE;
13642             ELSE
13643                 query:=query || $$,$$;
13644             END IF;
13645             query:=query || $$ DROP COLUMN $$ || cr.audit_column;
13646         ELSIF cr.audit_column IS NULL AND cr.main_column IS NOT NULL THEN -- New column auditor doesn't have. Add it.
13647             IF NOT alter_t THEN
13648                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13649                 alter_t:=TRUE;
13650             ELSE
13651                 query:=query || $$,$$;
13652             END IF;
13653             query:=query || $$ ADD COLUMN $$ || cr.main_column || $$ $$ || cr.main_column_def;
13654         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.
13655             IF NOT alter_t THEN
13656                 query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13657                 alter_t:=TRUE;
13658             ELSE
13659                 query:=query || $$,$$;
13660             END IF;
13661             query:=query || $$ ALTER COLUMN $$ || cr.audit_column || $$ TYPE $$ || cr.main_column_def;
13662         END IF;
13663     END LOOP;
13664     FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Repeat this outside of the loop to catch the last table
13665         IF NOT alter_t THEN
13666             query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
13667             alter_t:=TRUE;
13668         ELSE
13669             query:=query || $$,$$;
13670         END IF;
13671         -- 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.
13672         query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
13673     END LOOP;
13674     IF alter_t THEN -- Open alter table = needs a semicolon
13675         query:=query || $$;$$;
13676         IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
13677             -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
13678             -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
13679             EXECUTE query;
13680             EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
13681                 $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
13682             EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
13683                 $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
13684             query:='';
13685         END IF;
13686     END IF;
13687     EXECUTE query;
13688 END;
13689 $BODY$ LANGUAGE plpgsql;
13690
13691 -- Update it all routine
13692 CREATE OR REPLACE FUNCTION auditor.update_auditors() RETURNS boolean AS $BODY$
13693 DECLARE
13694     auditor_name TEXT;
13695     table_schema TEXT;
13696     table_name TEXT;
13697 BEGIN
13698     -- Drop Lifecycle view(s) before potential column changes
13699     FOR auditor_name IN
13700         SELECT c.relname
13701             FROM pg_catalog.pg_class c
13702                 JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13703             WHERE relkind = 'v' AND n.nspname = 'auditor' LOOP
13704         EXECUTE $$ DROP VIEW auditor.$$ || auditor_name || $$;$$;
13705     END LOOP;
13706     -- Fix all column discrepencies
13707     PERFORM auditor.fix_columns();
13708     -- Re-create trigger functions and lifecycle views
13709     FOR table_schema, table_name IN
13710         WITH audit_tables AS (
13711             SELECT c.oid AS audit_oid, c.relname AS audit_table
13712             FROM pg_catalog.pg_class c
13713             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13714             WHERE relkind='r' AND nspname = 'auditor'
13715         ),
13716         table_set AS (
13717             SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
13718             FROM pg_catalog.pg_class c
13719             JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
13720             JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
13721             WHERE relkind = 'r'
13722         )
13723         SELECT main_namespace, main_table FROM table_set LOOP
13724         
13725         PERFORM auditor.create_auditor_func(table_schema, table_name);
13726         PERFORM auditor.create_auditor_lifecycle(table_schema, table_name);
13727     END LOOP;
13728     RETURN TRUE;
13729 END;
13730 $BODY$ LANGUAGE plpgsql;
13731
13732 -- Go ahead and update them all now
13733 SELECT auditor.update_auditors();
13734
13735
13736 -- Evergreen DB patch 0687.schema.enhance_reingest.sql
13737 --
13738 -- FIXME: insert description of change, if needed
13739 --
13740
13741
13742 -- check whether patch can be applied
13743 SELECT evergreen.upgrade_deps_block_check('0687', :eg_version);
13744
13745 -- FIXME: add/check SQL statements to perform the upgrade
13746 -- New function def
13747 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$
13748 DECLARE
13749     fclass          RECORD;
13750     ind_data        metabib.field_entry_template%ROWTYPE;
13751     mbe_row         metabib.browse_entry%ROWTYPE;
13752     mbe_id          BIGINT;
13753 BEGIN
13754     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
13755     IF NOT FOUND THEN
13756         IF NOT skip_search THEN
13757             FOR fclass IN SELECT * FROM config.metabib_class LOOP
13758                 -- RAISE NOTICE 'Emptying out %', fclass.name;
13759                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
13760             END LOOP;
13761         END IF;
13762         IF NOT skip_facet THEN
13763             DELETE FROM metabib.facet_entry WHERE source = bib_id;
13764         END IF;
13765         IF NOT skip_browse THEN
13766             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
13767         END IF;
13768     END IF;
13769
13770     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
13771         IF ind_data.field < 0 THEN
13772             ind_data.field = -1 * ind_data.field;
13773         END IF;
13774
13775         IF ind_data.facet_field AND NOT skip_facet THEN
13776             INSERT INTO metabib.facet_entry (field, source, value)
13777                 VALUES (ind_data.field, ind_data.source, ind_data.value);
13778         END IF;
13779
13780         IF ind_data.browse_field AND NOT skip_browse THEN
13781             -- A caveat about this SELECT: this should take care of replacing
13782             -- old mbe rows when data changes, but not if normalization (by
13783             -- which I mean specifically the output of
13784             -- evergreen.oils_tsearch2()) changes.  It may or may not be
13785             -- expensive to add a comparison of index_vector to index_vector
13786             -- to the WHERE clause below.
13787             SELECT INTO mbe_row * FROM metabib.browse_entry WHERE value = ind_data.value;
13788             IF FOUND THEN
13789                 mbe_id := mbe_row.id;
13790             ELSE
13791                 INSERT INTO metabib.browse_entry (value) VALUES
13792                     (metabib.browse_normalize(ind_data.value, ind_data.field));
13793                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
13794             END IF;
13795
13796             INSERT INTO metabib.browse_entry_def_map (entry, def, source)
13797                 VALUES (mbe_id, ind_data.field, ind_data.source);
13798         END IF;
13799
13800         IF ind_data.search_field AND NOT skip_search THEN
13801             EXECUTE $$
13802                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
13803                     VALUES ($$ ||
13804                         quote_literal(ind_data.field) || $$, $$ ||
13805                         quote_literal(ind_data.source) || $$, $$ ||
13806                         quote_literal(ind_data.value) ||
13807                     $$);$$;
13808         END IF;
13809
13810     END LOOP;
13811
13812     RETURN;
13813 END;
13814 $func$ LANGUAGE PLPGSQL;
13815
13816 -- Delete old one
13817 DROP FUNCTION IF EXISTS metabib.reingest_metabib_field_entries(BIGINT);
13818
13819
13820 -- Evergreen DB patch 0690.schema.unapi_limit_rank.sql
13821 --
13822 -- Rewrite the in-database unapi functions to include per-object limits and
13823 -- offsets, such as a maximum number of copies and call numbers for given
13824 -- bib record via the HSTORE syntax (for example, 'acn => 5, acp => 10' would
13825 -- limit to a maximum of 5 call numbers for the bib, with up to 10 copies per
13826 -- call number).
13827 --
13828 -- Add some notion of "preferred library" that will provide copy counts
13829 -- and optionally affect the sorting of returned copies.
13830 --
13831 -- Sort copies by availability, preferring the most available copies.
13832 --
13833 -- Return located URIs.
13834 --
13835 --
13836
13837 -- check whether patch can be applied
13838 SELECT evergreen.upgrade_deps_block_check('0690', :eg_version);
13839
13840 -- The simplest way to apply all of these changes is just to replace the unapi
13841 -- schema entirely -- the following is a copy of 990.schema.unapi.sql with
13842 -- the initial COMMIT in place in case the upgrade_deps_block_check fails;
13843 -- if it does, then the attempt to create the unapi schema in the following
13844 -- transaction will also fail. Not graceful, but safe!
13845 DROP SCHEMA IF EXISTS unapi CASCADE;
13846
13847 CREATE SCHEMA unapi;
13848
13849 CREATE OR REPLACE FUNCTION evergreen.org_top()
13850 RETURNS SETOF actor.org_unit AS $$
13851     SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;
13852 $$ LANGUAGE SQL STABLE
13853 ROWS 1;
13854
13855 CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT)
13856 RETURNS anyarray AS $$
13857     SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2;
13858 $$ LANGUAGE SQL STABLE;
13859
13860 CREATE OR REPLACE FUNCTION evergreen.rank_ou(lib INT, search_lib INT, pref_lib INT DEFAULT NULL)
13861 RETURNS INTEGER AS $$
13862     WITH search_libs AS (
13863         SELECT id, distance FROM actor.org_unit_descendants_distance($2)
13864     )
13865     SELECT COALESCE(
13866         (SELECT -10000 FROM actor.org_unit
13867          WHERE $1 = $3 AND id = $3 AND $2 IN (
13868                 SELECT id FROM actor.org_unit WHERE parent_ou IS NULL
13869              )
13870         ),
13871         (SELECT distance FROM search_libs WHERE id = $1),
13872         10000
13873     );
13874 $$ LANGUAGE SQL STABLE;
13875
13876 CREATE OR REPLACE FUNCTION evergreen.rank_cp_status(status INT)
13877 RETURNS INTEGER AS $$
13878     WITH totally_available AS (
13879         SELECT id, 0 AS avail_rank
13880         FROM config.copy_status
13881         WHERE opac_visible IS TRUE
13882             AND copy_active IS TRUE
13883             AND id != 1 -- "Checked out"
13884     ), almost_available AS (
13885         SELECT id, 10 AS avail_rank
13886         FROM config.copy_status
13887         WHERE holdable IS TRUE
13888             AND opac_visible IS TRUE
13889             AND copy_active IS FALSE
13890             OR id = 1 -- "Checked out"
13891     )
13892     SELECT COALESCE(
13893         (SELECT avail_rank FROM totally_available WHERE $1 IN (id)),
13894         (SELECT avail_rank FROM almost_available WHERE $1 IN (id)),
13895         100
13896     );
13897 $$ LANGUAGE SQL STABLE;
13898
13899 CREATE OR REPLACE FUNCTION evergreen.ranked_volumes(
13900     bibid BIGINT, 
13901     ouid INT,
13902     depth INT DEFAULT NULL,
13903     slimit HSTORE DEFAULT NULL,
13904     soffset HSTORE DEFAULT NULL,
13905     pref_lib INT DEFAULT NULL
13906 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank BIGINT) AS $$
13907     SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
13908         SELECT acn.id, aou.name, acn.label_sortkey,
13909             evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status),
13910             RANK() OVER w
13911         FROM asset.call_number acn
13912             JOIN asset.copy acp ON (acn.id = acp.call_number)
13913             JOIN actor.org_unit_descendants( $2, COALESCE(
13914                 $3, (
13915                     SELECT depth
13916                     FROM actor.org_unit_type aout
13917                         INNER JOIN actor.org_unit ou ON ou_type = aout.id
13918                     WHERE ou.id = $2
13919                 ), $6)
13920             ) AS aou ON (acp.circ_lib = aou.id)
13921         WHERE acn.record = $1
13922             AND acn.deleted IS FALSE
13923             AND acp.deleted IS FALSE
13924         GROUP BY acn.id, acp.status, aou.name, acn.label_sortkey, aou.id
13925         WINDOW w AS (
13926             ORDER BY evergreen.rank_ou(aou.id, $2, $6), evergreen.rank_cp_status(acp.status)
13927         )
13928     ) AS ua
13929     GROUP BY ua.id, ua.name, ua.label_sortkey
13930     ORDER BY rank, ua.name, ua.label_sortkey
13931     LIMIT ($4 -> 'acn')::INT
13932     OFFSET ($5 -> 'acn')::INT;
13933 $$
13934 LANGUAGE SQL STABLE;
13935
13936 CREATE OR REPLACE FUNCTION evergreen.located_uris (
13937     bibid BIGINT, 
13938     ouid INT,
13939     pref_lib INT DEFAULT NULL
13940 ) RETURNS TABLE (id BIGINT, name TEXT, label_sortkey TEXT, rank INT) AS $$
13941     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
13942       FROM asset.call_number acn
13943            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
13944            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
13945            INNER JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
13946       WHERE acn.record = $1
13947           AND acn.deleted IS FALSE
13948           AND auri.active IS TRUE
13949     UNION
13950     SELECT acn.id, aou.name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
13951       FROM asset.call_number acn
13952            INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number 
13953            INNER JOIN asset.uri auri ON auri.id = auricnm.uri
13954            INNER JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
13955       WHERE acn.record = $1
13956           AND acn.deleted IS FALSE
13957           AND auri.active IS TRUE;
13958 $$
13959 LANGUAGE SQL STABLE;
13960
13961 CREATE TABLE unapi.bre_output_layout (
13962     name                TEXT    PRIMARY KEY,
13963     transform           TEXT    REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
13964     mime_type           TEXT    NOT NULL,
13965     feed_top            TEXT    NOT NULL,
13966     holdings_element    TEXT,
13967     title_element       TEXT,
13968     description_element TEXT,
13969     creator_element     TEXT,
13970     update_ts_element   TEXT
13971 );
13972
13973 INSERT INTO unapi.bre_output_layout
13974     (name,           transform, mime_type,              holdings_element, feed_top,         title_element, description_element, creator_element, update_ts_element)
13975         VALUES
13976     ('holdings_xml', NULL,      'application/xml',      NULL,             'hxml',           NULL,          NULL,                NULL,            NULL),
13977     ('marcxml',      'marcxml', 'application/marc+xml', 'record',         'collection',     NULL,          NULL,                NULL,            NULL),
13978     ('mods32',       'mods32',  'application/mods+xml', 'mods',           'modsCollection', NULL,          NULL,                NULL,            NULL)
13979 ;
13980
13981 -- Dummy functions, so we can create the real ones out of order
13982 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;
13983 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;
13984 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;
13985 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;
13986 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;
13987 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;
13988 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;
13989 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;
13990 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;
13991 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;
13992 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;
13993 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;
13994 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;
13995 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;
13996 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;
13997 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;
13998 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;
13999 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;
14000 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;
14001 CREATE OR REPLACE FUNCTION unapi.bre (
14002     obj_id BIGINT,
14003     format TEXT,
14004     ename TEXT,
14005     includes TEXT[],
14006     org TEXT,
14007     depth INT DEFAULT NULL,
14008     slimit HSTORE DEFAULT NULL,
14009     soffset HSTORE DEFAULT NULL,
14010     include_xmlns BOOL DEFAULT TRUE,
14011     pref_lib INT DEFAULT NULL
14012 )
14013 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
14014 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;
14015 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;
14016 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;
14017
14018 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
14019     bid BIGINT,
14020     ouid INT,
14021     org TEXT,
14022     depth INT DEFAULT NULL,
14023     includes TEXT[] DEFAULT NULL::TEXT[],
14024     slimit HSTORE DEFAULT NULL,
14025     soffset HSTORE DEFAULT NULL,
14026     include_xmlns BOOL DEFAULT TRUE,
14027     pref_lib INT DEFAULT NULL
14028 )
14029 RETURNS XML AS $F$ SELECT NULL::XML $F$ LANGUAGE SQL STABLE;
14030
14031 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;
14032
14033 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$
14034 DECLARE
14035     key     TEXT;
14036     output  XML;
14037 BEGIN
14038     key :=
14039         'id'        || COALESCE(obj_id::TEXT,'') ||
14040         'format'    || COALESCE(format::TEXT,'') ||
14041         'ename'     || COALESCE(ename::TEXT,'') ||
14042         'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
14043         'org'       || COALESCE(org::TEXT,'') ||
14044         'depth'     || COALESCE(depth::TEXT,'') ||
14045         'slimit'    || COALESCE(slimit::TEXT,'') ||
14046         'soffset'   || COALESCE(soffset::TEXT,'') ||
14047         'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
14048     -- RAISE NOTICE 'memoize key: %', key;
14049
14050     key := MD5(key);
14051     -- RAISE NOTICE 'memoize hash: %', key;
14052
14053     -- XXX cache logic ... memcached? table?
14054
14055     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;
14056     RETURN output;
14057 END;
14058 $F$ LANGUAGE PLPGSQL STABLE;
14059
14060 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$
14061 DECLARE
14062     layout          unapi.bre_output_layout%ROWTYPE;
14063     transform       config.xml_transform%ROWTYPE;
14064     item_format     TEXT;
14065     tmp_xml         TEXT;
14066     xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
14067     ouid            INT;
14068     element_list    TEXT[];
14069 BEGIN
14070
14071     IF org = '-' OR org IS NULL THEN
14072         SELECT shortname INTO org FROM evergreen.org_top();
14073     END IF;
14074
14075     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
14076     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
14077
14078     IF layout.name IS NULL THEN
14079         RETURN NULL::XML;
14080     END IF;
14081
14082     SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
14083     xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);
14084
14085     -- Gather the bib xml
14086     SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns)) INTO tmp_xml FROM UNNEST( id_list ) i;
14087
14088     IF layout.title_element IS NOT NULL THEN
14089         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
14090     END IF;
14091
14092     IF layout.description_element IS NOT NULL THEN
14093         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
14094     END IF;
14095
14096     IF layout.creator_element IS NOT NULL THEN
14097         EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
14098     END IF;
14099
14100     IF layout.update_ts_element IS NOT NULL THEN
14101         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;
14102     END IF;
14103
14104     IF unapi_url IS NOT NULL THEN
14105         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;
14106     END IF;
14107
14108     IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;
14109
14110     element_list := regexp_split_to_array(layout.feed_top,E'\\.');
14111     FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
14112         EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
14113     END LOOP;
14114
14115     RETURN tmp_xml::XML;
14116 END;
14117 $F$ LANGUAGE PLPGSQL STABLE;
14118
14119 CREATE OR REPLACE FUNCTION unapi.bre (
14120     obj_id BIGINT,
14121     format TEXT,
14122     ename TEXT,
14123     includes TEXT[],
14124     org TEXT,
14125     depth INT DEFAULT NULL,
14126     slimit HSTORE DEFAULT NULL,
14127     soffset HSTORE DEFAULT NULL,
14128     include_xmlns BOOL DEFAULT TRUE,
14129     pref_lib INT DEFAULT NULL
14130 )
14131 RETURNS XML AS $F$
14132 DECLARE
14133     me      biblio.record_entry%ROWTYPE;
14134     layout  unapi.bre_output_layout%ROWTYPE;
14135     xfrm    config.xml_transform%ROWTYPE;
14136     ouid    INT;
14137     tmp_xml TEXT;
14138     top_el  TEXT;
14139     output  XML;
14140     hxml    XML;
14141     axml    XML;
14142 BEGIN
14143
14144     IF org = '-' OR org IS NULL THEN
14145         SELECT shortname INTO org FROM evergreen.org_top();
14146     END IF;
14147
14148     SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
14149
14150     IF ouid IS NULL THEN
14151         RETURN NULL::XML;
14152     END IF;
14153
14154     IF format = 'holdings_xml' THEN -- the special case
14155         output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
14156         RETURN output;
14157     END IF;
14158
14159     SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;
14160
14161     IF layout.name IS NULL THEN
14162         RETURN NULL::XML;
14163     END IF;
14164
14165     SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;
14166
14167     SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;
14168
14169     -- grab SVF if we need them
14170     IF ('mra' = ANY (includes)) THEN 
14171         axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
14172     ELSE
14173         axml := NULL::XML;
14174     END IF;
14175
14176     -- grab holdings if we need them
14177     IF ('holdings_xml' = ANY (includes)) THEN 
14178         hxml := unapi.holdings_xml(obj_id, ouid, org, depth, evergreen.array_remove_item_by_value(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
14179     ELSE
14180         hxml := NULL::XML;
14181     END IF;
14182
14183
14184     -- generate our item node
14185
14186
14187     IF format = 'marcxml' THEN
14188         tmp_xml := me.marc;
14189         IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
14190            tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
14191         END IF; 
14192     ELSE
14193         tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
14194     END IF;
14195
14196     top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');
14197
14198     IF axml IS NOT NULL THEN 
14199         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
14200     END IF;
14201
14202     IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
14203         tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
14204     END IF;
14205
14206     IF ('bre.unapi' = ANY (includes)) THEN 
14207         output := REGEXP_REPLACE(
14208             tmp_xml,
14209             '</' || top_el || '>(.*?)',
14210             XMLELEMENT(
14211                 name abbr,
14212                 XMLATTRIBUTES(
14213                     'http://www.w3.org/1999/xhtml' AS xmlns,
14214                     'unapi-id' AS class,
14215                     'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
14216                 )
14217             )::TEXT || '</' || top_el || E'>\\1'
14218         );
14219     ELSE
14220         output := tmp_xml;
14221     END IF;
14222
14223     output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
14224     RETURN output;
14225 END;
14226 $F$ LANGUAGE PLPGSQL STABLE;
14227
14228 CREATE OR REPLACE FUNCTION unapi.holdings_xml (
14229     bid BIGINT,
14230     ouid INT,
14231     org TEXT,
14232     depth INT DEFAULT NULL,
14233     includes TEXT[] DEFAULT NULL::TEXT[],
14234     slimit HSTORE DEFAULT NULL,
14235     soffset HSTORE DEFAULT NULL,
14236     include_xmlns BOOL DEFAULT TRUE,
14237     pref_lib INT DEFAULT NULL
14238 )
14239 RETURNS XML AS $F$
14240      SELECT  XMLELEMENT(
14241                  name holdings,
14242                  XMLATTRIBUTES(
14243                     CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14244                     CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id
14245                  ),
14246                  XMLELEMENT(
14247                      name counts,
14248                      (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
14249                          SELECT  XMLELEMENT(
14250                                      name count,
14251                                      XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14252                                  )::text
14253                            FROM  asset.opac_ou_record_copy_count($2,  $1)
14254                                      UNION
14255                          SELECT  XMLELEMENT(
14256                                      name count,
14257                                      XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14258                                  )::text
14259                            FROM  asset.staff_ou_record_copy_count($2, $1)
14260                                      UNION
14261                          SELECT  XMLELEMENT(
14262                                      name count,
14263                                      XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
14264                                  )::text
14265                            FROM  asset.opac_ou_record_copy_count($9,  $1)
14266                                      ORDER BY 1
14267                      )x)
14268                  ),
14269                  CASE 
14270                      WHEN ('bmp' = ANY ($5)) THEN
14271                         XMLELEMENT(
14272                             name monograph_parts,
14273                             (SELECT XMLAGG(bmp) FROM (
14274                                 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)
14275                                   FROM  biblio.monograph_part
14276                                   WHERE record = $1
14277                             )x)
14278                         )
14279                      ELSE NULL
14280                  END,
14281                  XMLELEMENT(
14282                      name volumes,
14283                      (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
14284                         -- Physical copies
14285                         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
14286                         FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9) AS y
14287                         UNION ALL
14288                         -- Located URIs
14289                         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
14290                         FROM evergreen.located_uris($1, $2, $9) AS uris
14291                      )x)
14292                  ),
14293                  CASE WHEN ('ssub' = ANY ($5)) THEN 
14294                      XMLELEMENT(
14295                          name subscriptions,
14296                          (SELECT XMLAGG(ssub) FROM (
14297                             SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
14298                               FROM  serial.subscription
14299                               WHERE record_entry = $1
14300                         )x)
14301                      )
14302                  ELSE NULL END,
14303                  CASE WHEN ('acp' = ANY ($5)) THEN 
14304                      XMLELEMENT(
14305                          name foreign_copies,
14306                          (SELECT XMLAGG(acp) FROM (
14307                             SELECT  unapi.acp(p.target_copy,'xml','copy',evergreen.array_remove_item_by_value($5,'acp'), $3, $4, $6, $7, FALSE)
14308                               FROM  biblio.peer_bib_copy_map p
14309                                     JOIN asset.copy c ON (p.target_copy = c.id)
14310                               WHERE NOT c.deleted AND p.peer_record = $1
14311                             LIMIT ($6 -> 'acp')::INT
14312                             OFFSET ($7 -> 'acp')::INT
14313                         )x)
14314                      )
14315                  ELSE NULL END
14316              );
14317 $F$ LANGUAGE SQL STABLE;
14318
14319 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$
14320         SELECT  XMLELEMENT(
14321                     name subscription,
14322                     XMLATTRIBUTES(
14323                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14324                         'tag:open-ils.org:U2@ssub/' || id AS id,
14325                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
14326                         start_date AS start, end_date AS end, expected_date_offset
14327                     ),
14328                     CASE 
14329                         WHEN ('sdist' = ANY ($4)) THEN
14330                             XMLELEMENT( name distributions,
14331                                 (SELECT XMLAGG(sdist) FROM (
14332                                     SELECT  unapi.sdist( id, 'xml', 'distribution', evergreen.array_remove_item_by_value($4,'ssub'), $5, $6, $7, $8, FALSE)
14333                                       FROM  serial.distribution
14334                                       WHERE subscription = ssub.id
14335                                 )x)
14336                             )
14337                         ELSE NULL
14338                     END
14339                 )
14340           FROM  serial.subscription ssub
14341           WHERE id = $1
14342           GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;
14343 $F$ LANGUAGE SQL STABLE;
14344
14345 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$
14346         SELECT  XMLELEMENT(
14347                     name distribution,
14348                     XMLATTRIBUTES(
14349                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14350                         'tag:open-ils.org:U2@sdist/' || id AS id,
14351                                 'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
14352                                     'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
14353                         unit_label_prefix, label, unit_label_suffix, summary_method
14354                     ),
14355                     unapi.aou( holding_lib, $2, 'holding_lib', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8),
14356                     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,
14357                     CASE 
14358                         WHEN ('sstr' = ANY ($4)) THEN
14359                             XMLELEMENT( name streams,
14360                                 (SELECT XMLAGG(sstr) FROM (
14361                                     SELECT  unapi.sstr( id, 'xml', 'stream', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14362                                       FROM  serial.stream
14363                                       WHERE distribution = sdist.id
14364                                 )x)
14365                             )
14366                         ELSE NULL
14367                     END,
14368                     XMLELEMENT( name summaries,
14369                         CASE 
14370                             WHEN ('sbsum' = ANY ($4)) THEN
14371                                 (SELECT XMLAGG(sbsum) FROM (
14372                                     SELECT  unapi.sbsum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14373                                       FROM  serial.basic_summary
14374                                       WHERE distribution = sdist.id
14375                                 )x)
14376                             ELSE NULL
14377                         END,
14378                         CASE 
14379                             WHEN ('sisum' = ANY ($4)) THEN
14380                                 (SELECT XMLAGG(sisum) FROM (
14381                                     SELECT  unapi.sisum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14382                                       FROM  serial.index_summary
14383                                       WHERE distribution = sdist.id
14384                                 )x)
14385                             ELSE NULL
14386                         END,
14387                         CASE 
14388                             WHEN ('sssum' = ANY ($4)) THEN
14389                                 (SELECT XMLAGG(sssum) FROM (
14390                                     SELECT  unapi.sssum( id, 'xml', 'serial_summary', evergreen.array_remove_item_by_value($4,'sdist'), $5, $6, $7, $8, FALSE)
14391                                       FROM  serial.supplement_summary
14392                                       WHERE distribution = sdist.id
14393                                 )x)
14394                             ELSE NULL
14395                         END
14396                     )
14397                 )
14398           FROM  serial.distribution sdist
14399           WHERE id = $1
14400           GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;
14401 $F$ LANGUAGE SQL STABLE;
14402
14403 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$
14404     SELECT  XMLELEMENT(
14405                 name stream,
14406                 XMLATTRIBUTES(
14407                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14408                     'tag:open-ils.org:U2@sstr/' || id AS id,
14409                     routing_label
14410                 ),
14411                 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,
14412                 CASE 
14413                     WHEN ('sitem' = ANY ($4)) THEN
14414                         XMLELEMENT( name items,
14415                             (SELECT XMLAGG(sitem) FROM (
14416                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'sstr'), $5, $6, $7, $8, FALSE)
14417                                   FROM  serial.item
14418                                   WHERE stream = sstr.id
14419                             )x)
14420                         )
14421                     ELSE NULL
14422                 END
14423             )
14424       FROM  serial.stream sstr
14425       WHERE id = $1
14426       GROUP BY id, routing_label, distribution;
14427 $F$ LANGUAGE SQL STABLE;
14428
14429 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$
14430     SELECT  XMLELEMENT(
14431                 name issuance,
14432                 XMLATTRIBUTES(
14433                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14434                     'tag:open-ils.org:U2@siss/' || id AS id,
14435                     create_date, edit_date, label, date_published,
14436                     holding_code, holding_type, holding_link_id
14437                 ),
14438                 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,
14439                 CASE 
14440                     WHEN ('sitem' = ANY ($4)) THEN
14441                         XMLELEMENT( name items,
14442                             (SELECT XMLAGG(sitem) FROM (
14443                                 SELECT  unapi.sitem( id, 'xml', 'serial_item', evergreen.array_remove_item_by_value($4,'siss'), $5, $6, $7, $8, FALSE)
14444                                   FROM  serial.item
14445                                   WHERE issuance = sstr.id
14446                             )x)
14447                         )
14448                     ELSE NULL
14449                 END
14450             )
14451       FROM  serial.issuance sstr
14452       WHERE id = $1
14453       GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;
14454 $F$ LANGUAGE SQL STABLE;
14455
14456 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$
14457         SELECT  XMLELEMENT(
14458                     name serial_item,
14459                     XMLATTRIBUTES(
14460                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14461                         'tag:open-ils.org:U2@sitem/' || id AS id,
14462                         'tag:open-ils.org:U2@siss/' || issuance AS issuance,
14463                         date_expected, date_received
14464                     ),
14465                     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,
14466                     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,
14467                     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,
14468                     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
14469 --                    XMLELEMENT( name notes,
14470 --                        CASE 
14471 --                            WHEN ('acpn' = ANY ($4)) THEN
14472 --                                (SELECT XMLAGG(acpn) FROM (
14473 --                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8)
14474 --                                      FROM  asset.copy_note
14475 --                                      WHERE owning_copy = cp.id AND pub
14476 --                                )x)
14477 --                            ELSE NULL
14478 --                        END
14479 --                    )
14480                 )
14481           FROM  serial.item sitem
14482           WHERE id = $1;
14483 $F$ LANGUAGE SQL STABLE;
14484
14485
14486 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$
14487     SELECT  XMLELEMENT(
14488                 name serial_summary,
14489                 XMLATTRIBUTES(
14490                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14491                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14492                     'sssum' AS type, generated_coverage, textual_holdings, show_generated
14493                 ),
14494                 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
14495             )
14496       FROM  serial.supplement_summary ssum
14497       WHERE id = $1
14498       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14499 $F$ LANGUAGE SQL STABLE;
14500
14501 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$
14502     SELECT  XMLELEMENT(
14503                 name serial_summary,
14504                 XMLATTRIBUTES(
14505                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14506                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14507                     'sbsum' AS type, generated_coverage, textual_holdings, show_generated
14508                 ),
14509                 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
14510             )
14511       FROM  serial.basic_summary ssum
14512       WHERE id = $1
14513       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14514 $F$ LANGUAGE SQL STABLE;
14515
14516 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$
14517     SELECT  XMLELEMENT(
14518                 name serial_summary,
14519                 XMLATTRIBUTES(
14520                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14521                     'tag:open-ils.org:U2@sbsum/' || id AS id,
14522                     'sisum' AS type, generated_coverage, textual_holdings, show_generated
14523                 ),
14524                 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
14525             )
14526       FROM  serial.index_summary ssum
14527       WHERE id = $1
14528       GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;
14529 $F$ LANGUAGE SQL STABLE;
14530
14531
14532 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$
14533 DECLARE
14534     output XML;
14535 BEGIN
14536     IF ename = 'circlib' THEN
14537         SELECT  XMLELEMENT(
14538                     name circlib,
14539                     XMLATTRIBUTES(
14540                         'http://open-ils.org/spec/actors/v1' AS xmlns,
14541                         id AS ident
14542                     ),
14543                     name
14544                 ) INTO output
14545           FROM  actor.org_unit aou
14546           WHERE id = obj_id;
14547     ELSE
14548         EXECUTE $$SELECT  XMLELEMENT(
14549                     name $$ || ename || $$,
14550                     XMLATTRIBUTES(
14551                         'http://open-ils.org/spec/actors/v1' AS xmlns,
14552                         'tag:open-ils.org:U2@aou/' || id AS id,
14553                         shortname, name, opac_visible
14554                     )
14555                 )
14556           FROM  actor.org_unit aou
14557          WHERE id = $1 $$ INTO output USING obj_id;
14558     END IF;
14559
14560     RETURN output;
14561
14562 END;
14563 $F$ LANGUAGE PLPGSQL STABLE;
14564
14565 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$
14566     SELECT  XMLELEMENT(
14567                 name location,
14568                 XMLATTRIBUTES(
14569                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14570                     id AS ident,
14571                     holdable,
14572                     opac_visible,
14573                     label_prefix AS prefix,
14574                     label_suffix AS suffix
14575                 ),
14576                 name
14577             )
14578       FROM  asset.copy_location
14579       WHERE id = $1;
14580 $F$ LANGUAGE SQL STABLE;
14581
14582 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$
14583     SELECT  XMLELEMENT(
14584                 name status,
14585                 XMLATTRIBUTES(
14586                     CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14587                     id AS ident,
14588                     holdable,
14589                     opac_visible
14590                 ),
14591                 name
14592             )
14593       FROM  config.copy_status
14594       WHERE id = $1;
14595 $F$ LANGUAGE SQL STABLE;
14596
14597 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$
14598         SELECT  XMLELEMENT(
14599                     name copy_note,
14600                     XMLATTRIBUTES(
14601                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14602                         create_date AS date,
14603                         title
14604                     ),
14605                     value
14606                 )
14607           FROM  asset.copy_note
14608           WHERE id = $1;
14609 $F$ LANGUAGE SQL STABLE;
14610
14611 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$
14612         SELECT  XMLELEMENT(
14613                     name statcat,
14614                     XMLATTRIBUTES(
14615                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14616                         sc.name,
14617                         sc.opac_visible
14618                     ),
14619                     asce.value
14620                 )
14621           FROM  asset.stat_cat_entry asce
14622                 JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
14623           WHERE asce.id = $1;
14624 $F$ LANGUAGE SQL STABLE;
14625
14626 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$
14627         SELECT  XMLELEMENT(
14628                     name monograph_part,
14629                     XMLATTRIBUTES(
14630                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14631                         'tag:open-ils.org:U2@bmp/' || id AS id,
14632                         id AS ident,
14633                         label,
14634                         label_sortkey,
14635                         'tag:open-ils.org:U2@bre/' || record AS record
14636                     ),
14637                     CASE 
14638                         WHEN ('acp' = ANY ($4)) THEN
14639                             XMLELEMENT( name copies,
14640                                 (SELECT XMLAGG(acp) FROM (
14641                                     SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'bmp'), $5, $6, $7, $8, FALSE)
14642                                       FROM  asset.copy cp
14643                                             JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
14644                                       WHERE cpm.part = $1
14645                                           AND cp.deleted IS FALSE
14646                                       ORDER BY COALESCE(cp.copy_number,0), cp.barcode
14647                                       LIMIT ($7 -> 'acp')::INT
14648                                       OFFSET ($8 -> 'acp')::INT
14649
14650                                 )x)
14651                             )
14652                         ELSE NULL
14653                     END,
14654                     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
14655                 )
14656           FROM  biblio.monograph_part
14657           WHERE id = $1
14658           GROUP BY id, label, label_sortkey, record;
14659 $F$ LANGUAGE SQL STABLE;
14660
14661 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$
14662         SELECT  XMLELEMENT(
14663                     name copy,
14664                     XMLATTRIBUTES(
14665                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14666                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
14667                         create_date, edit_date, copy_number, circulate, deposit,
14668                         ref, holdable, deleted, deposit_amount, price, barcode,
14669                         circ_modifier, circ_as_type, opac_visible, age_protect
14670                     ),
14671                     unapi.ccs( status, $2, 'status', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
14672                     unapi.acl( location, $2, 'location', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE),
14673                     unapi.aou( circ_lib, $2, 'circ_lib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
14674                     unapi.aou( circ_lib, $2, 'circlib', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8),
14675                     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,
14676                     CASE 
14677                         WHEN ('acpn' = ANY ($4)) THEN
14678                             XMLELEMENT( name copy_notes,
14679                                 (SELECT XMLAGG(acpn) FROM (
14680                                     SELECT  unapi.acpn( id, 'xml', 'copy_note', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14681                                       FROM  asset.copy_note
14682                                       WHERE owning_copy = cp.id AND pub
14683                                 )x)
14684                             )
14685                         ELSE NULL
14686                     END,
14687                     CASE 
14688                         WHEN ('ascecm' = ANY ($4)) THEN
14689                             XMLELEMENT( name statcats,
14690                                 (SELECT XMLAGG(ascecm) FROM (
14691                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14692                                       FROM  asset.stat_cat_entry_copy_map
14693                                       WHERE owning_copy = cp.id
14694                                 )x)
14695                             )
14696                         ELSE NULL
14697                     END,
14698                     CASE
14699                         WHEN ('bre' = ANY ($4)) THEN
14700                             XMLELEMENT( name foreign_records,
14701                                 (SELECT XMLAGG(bre) FROM (
14702                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
14703                                       FROM  biblio.peer_bib_copy_map
14704                                       WHERE target_copy = cp.id
14705                                 )x)
14706
14707                             )
14708                         ELSE NULL
14709                     END,
14710                     CASE 
14711                         WHEN ('bmp' = ANY ($4)) THEN
14712                             XMLELEMENT( name monograph_parts,
14713                                 (SELECT XMLAGG(bmp) FROM (
14714                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14715                                       FROM  asset.copy_part_map
14716                                       WHERE target_copy = cp.id
14717                                 )x)
14718                             )
14719                         ELSE NULL
14720                     END,
14721                     CASE 
14722                         WHEN ('circ' = ANY ($4)) THEN
14723                             XMLELEMENT( name current_circulation,
14724                                 (SELECT XMLAGG(circ) FROM (
14725                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
14726                                       FROM  action.circulation
14727                                       WHERE target_copy = cp.id
14728                                             AND checkin_time IS NULL
14729                                 )x)
14730                             )
14731                         ELSE NULL
14732                     END
14733                 )
14734           FROM  asset.copy cp
14735           WHERE id = $1
14736               AND cp.deleted IS FALSE
14737           GROUP BY id, status, location, circ_lib, call_number, create_date,
14738               edit_date, copy_number, circulate, deposit, ref, holdable,
14739               deleted, deposit_amount, price, barcode, circ_modifier,
14740               circ_as_type, opac_visible, age_protect;
14741 $F$ LANGUAGE SQL STABLE;
14742
14743 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$
14744         SELECT  XMLELEMENT(
14745                     name serial_unit,
14746                     XMLATTRIBUTES(
14747                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14748                         'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
14749                         create_date, edit_date, copy_number, circulate, deposit,
14750                         ref, holdable, deleted, deposit_amount, price, barcode,
14751                         circ_modifier, circ_as_type, opac_visible, age_protect,
14752                         status_changed_time, floating, mint_condition,
14753                         detailed_contents, sort_key, summary_contents, cost 
14754                     ),
14755                     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),
14756                     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),
14757                     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),
14758                     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),
14759                     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,
14760                     XMLELEMENT( name copy_notes,
14761                         CASE 
14762                             WHEN ('acpn' = ANY ($4)) THEN
14763                                 (SELECT XMLAGG(acpn) FROM (
14764                                     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)
14765                                       FROM  asset.copy_note
14766                                       WHERE owning_copy = cp.id AND pub
14767                                 )x)
14768                             ELSE NULL
14769                         END
14770                     ),
14771                     XMLELEMENT( name statcats,
14772                         CASE 
14773                             WHEN ('ascecm' = ANY ($4)) THEN
14774                                 (SELECT XMLAGG(ascecm) FROM (
14775                                     SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14776                                       FROM  asset.stat_cat_entry_copy_map
14777                                       WHERE owning_copy = cp.id
14778                                 )x)
14779                             ELSE NULL
14780                         END
14781                     ),
14782                     XMLELEMENT( name foreign_records,
14783                         CASE
14784                             WHEN ('bre' = ANY ($4)) THEN
14785                                 (SELECT XMLAGG(bre) FROM (
14786                                     SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
14787                                       FROM  biblio.peer_bib_copy_map
14788                                       WHERE target_copy = cp.id
14789                                 )x)
14790                             ELSE NULL
14791                         END
14792                     ),
14793                     CASE 
14794                         WHEN ('bmp' = ANY ($4)) THEN
14795                             XMLELEMENT( name monograph_parts,
14796                                 (SELECT XMLAGG(bmp) FROM (
14797                                     SELECT  unapi.bmp( part, 'xml', 'monograph_part', evergreen.array_remove_item_by_value($4,'acp'), $5, $6, $7, $8, FALSE)
14798                                       FROM  asset.copy_part_map
14799                                       WHERE target_copy = cp.id
14800                                 )x)
14801                             )
14802                         ELSE NULL
14803                     END,
14804                     CASE 
14805                         WHEN ('circ' = ANY ($4)) THEN
14806                             XMLELEMENT( name current_circulation,
14807                                 (SELECT XMLAGG(circ) FROM (
14808                                     SELECT  unapi.circ( id, 'xml', 'circ', evergreen.array_remove_item_by_value($4,'circ'), $5, $6, $7, $8, FALSE)
14809                                       FROM  action.circulation
14810                                       WHERE target_copy = cp.id
14811                                             AND checkin_time IS NULL
14812                                 )x)
14813                             )
14814                         ELSE NULL
14815                     END
14816                 )
14817           FROM  serial.unit cp
14818           WHERE id = $1
14819               AND cp.deleted IS FALSE
14820           GROUP BY id, status, location, circ_lib, call_number, create_date,
14821               edit_date, copy_number, circulate, floating, mint_condition,
14822               deposit, ref, holdable, deleted, deposit_amount, price,
14823               barcode, circ_modifier, circ_as_type, opac_visible,
14824               status_changed_time, detailed_contents, sort_key,
14825               summary_contents, cost, age_protect;
14826 $F$ LANGUAGE SQL STABLE;
14827
14828 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$
14829         SELECT  XMLELEMENT(
14830                     name volume,
14831                     XMLATTRIBUTES(
14832                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14833                         'tag:open-ils.org:U2@acn/' || acn.id AS id,
14834                         acn.id AS vol_id, o.shortname AS lib,
14835                         o.opac_visible AS opac_visible,
14836                         deleted, label, label_sortkey, label_class, record
14837                     ),
14838                     unapi.aou( owning_lib, $2, 'owning_lib', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8),
14839                     CASE 
14840                         WHEN ('acp' = ANY ($4)) THEN
14841                             CASE WHEN $6 IS NOT NULL THEN
14842                                 XMLELEMENT( name copies,
14843                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
14844                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
14845                                             evergreen.rank_cp_status(cp.status) AS rank_avail
14846                                           FROM  asset.copy cp
14847                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
14848                                           WHERE cp.call_number = acn.id
14849                                               AND cp.deleted IS FALSE
14850                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
14851                                           LIMIT ($7 -> 'acp')::INT
14852                                           OFFSET ($8 -> 'acp')::INT
14853                                     )x)
14854                                 )
14855                             ELSE
14856                                 XMLELEMENT( name copies,
14857                                     (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
14858                                         SELECT  unapi.acp( cp.id, 'xml', 'copy', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
14859                                             evergreen.rank_cp_status(cp.status) AS rank_avail
14860                                           FROM  asset.copy cp
14861                                                 JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
14862                                           WHERE cp.call_number = acn.id
14863                                               AND cp.deleted IS FALSE
14864                                           ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
14865                                           LIMIT ($7 -> 'acp')::INT
14866                                           OFFSET ($8 -> 'acp')::INT
14867                                     )x)
14868                                 )
14869                             END
14870                         ELSE NULL
14871                     END,
14872                     XMLELEMENT(
14873                         name uris,
14874                         (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)
14875                     ),
14876                     unapi.acnp( acn.prefix, 'marcxml', 'prefix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
14877                     unapi.acns( acn.suffix, 'marcxml', 'suffix', evergreen.array_remove_item_by_value($4,'acn'), $5, $6, $7, $8, FALSE),
14878                     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
14879                 ) AS x
14880           FROM  asset.call_number acn
14881                 JOIN actor.org_unit o ON (o.id = acn.owning_lib)
14882           WHERE acn.id = $1
14883               AND acn.deleted IS FALSE
14884           GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;
14885 $F$ LANGUAGE SQL STABLE;
14886
14887 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$
14888         SELECT  XMLELEMENT(
14889                     name call_number_prefix,
14890                     XMLATTRIBUTES(
14891                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14892                         id AS ident,
14893                         label,
14894                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
14895                         label_sortkey
14896                     )
14897                 )
14898           FROM  asset.call_number_prefix
14899           WHERE id = $1;
14900 $F$ LANGUAGE SQL STABLE;
14901
14902 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$
14903         SELECT  XMLELEMENT(
14904                     name call_number_suffix,
14905                     XMLATTRIBUTES(
14906                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14907                         id AS ident,
14908                         label,
14909                         'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
14910                         label_sortkey
14911                     )
14912                 )
14913           FROM  asset.call_number_suffix
14914           WHERE id = $1;
14915 $F$ LANGUAGE SQL STABLE;
14916
14917 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$
14918         SELECT  XMLELEMENT(
14919                     name uri,
14920                     XMLATTRIBUTES(
14921                         CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14922                         'tag:open-ils.org:U2@auri/' || uri.id AS id,
14923                         use_restriction,
14924                         href,
14925                         label
14926                     ),
14927                     CASE 
14928                         WHEN ('acn' = ANY ($4)) THEN
14929                             XMLELEMENT( name copies,
14930                                 (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)
14931                             )
14932                         ELSE NULL
14933                     END
14934                 ) AS x
14935           FROM  asset.uri uri
14936           WHERE uri.id = $1
14937           GROUP BY uri.id, use_restriction, href, label;
14938 $F$ LANGUAGE SQL STABLE;
14939
14940 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$
14941         SELECT  XMLELEMENT(
14942                     name attributes,
14943                     XMLATTRIBUTES(
14944                         CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
14945                         'tag:open-ils.org:U2@mra/' || mra.id AS id,
14946                         'tag:open-ils.org:U2@bre/' || mra.id AS record
14947                     ),
14948                     (SELECT XMLAGG(foo.y)
14949                       FROM (SELECT XMLELEMENT(
14950                                 name field,
14951                                 XMLATTRIBUTES(
14952                                     key AS name,
14953                                     cvm.value AS "coded-value",
14954                                     rad.filter,
14955                                     rad.sorter
14956                                 ),
14957                                 x.value
14958                             )
14959                            FROM EACH(mra.attrs) AS x
14960                                 JOIN config.record_attr_definition rad ON (x.key = rad.name)
14961                                 LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = x.key AND code = x.value)
14962                         )foo(y)
14963                     )
14964                 )
14965           FROM  metabib.record_attr mra
14966           WHERE mra.id = $1;
14967 $F$ LANGUAGE SQL STABLE;
14968
14969 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$
14970     SELECT XMLELEMENT(
14971         name circ,
14972         XMLATTRIBUTES(
14973             CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
14974             'tag:open-ils.org:U2@circ/' || id AS id,
14975             xact_start,
14976             due_date
14977         ),
14978         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,
14979         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
14980     )
14981     FROM action.circulation
14982     WHERE id = $1;
14983 $F$ LANGUAGE SQL STABLE;
14984
14985 /*
14986
14987  -- Some test queries
14988
14989 SELECT unapi.memoize( 'bre', 1,'mods32','','{holdings_xml,acp}'::TEXT[], 'SYS1');
14990 SELECT unapi.memoize( 'bre', 1,'marcxml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
14991 SELECT unapi.memoize( 'bre', 1,'holdings_xml','','{holdings_xml,acp}'::TEXT[], 'SYS1');
14992
14993 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>');
14994
14995 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>');
14996 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>');
14997 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>');
14998 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>');
14999
15000 SELECT unapi.biblio_record_entry_feed('{216}'::BIGINT[],'marcxml','{}'::TEXT[], 'BR1');
15001 EXPLAIN ANALYZE SELECT unapi.bre(216,'marcxml','record','{holdings_xml,bre.unapi}'::TEXT[], 'BR1');
15002 EXPLAIN ANALYZE SELECT unapi.bre(216,'holdings_xml','record','{}'::TEXT[], 'BR1');
15003 EXPLAIN ANALYZE SELECT unapi.holdings_xml(216,4,'BR1',2,'{bre}'::TEXT[]);
15004 EXPLAIN ANALYZE SELECT unapi.bre(216,'mods32','record','{}'::TEXT[], 'BR1');
15005
15006 -- Limit to 5 call numbers, 5 copies, with a preferred library of 4 (BR1), in SYS2 at a depth of 0
15007 EXPLAIN ANALYZE SELECT unapi.bre(36,'marcxml','record','{holdings_xml,mra,acp,acnp,acns,bmp}','SYS2',0,'acn=>5,acp=>5',NULL,TRUE,4);
15008
15009 */
15010
15011 -- Evergreen DB patch 0693.schema.do_not_despace_issns.sql
15012 --
15013 -- FIXME: insert description of change, if needed
15014 --
15015
15016
15017 -- check whether patch can be applied
15018 SELECT evergreen.upgrade_deps_block_check('0693', :eg_version);
15019
15020 -- FIXME: add/check SQL statements to perform the upgrade
15021 -- Delete the index normalizer that was meant to remove spaces from ISSNs
15022 -- but ended up breaking records with multiple ISSNs
15023 DELETE FROM config.metabib_field_index_norm_map WHERE id IN (
15024     SELECT map.id FROM config.metabib_field_index_norm_map map
15025         INNER JOIN config.metabib_field cmf ON cmf.id = map.field
15026         INNER JOIN config.index_normalizer cin ON cin.id = map.norm
15027     WHERE cin.func = 'replace'
15028         AND cmf.field_class = 'identifier'
15029         AND cmf.name = 'issn'
15030         AND map.params = $$[" ",""]$$
15031 );
15032
15033 -- Reindex records that have more than just a single ISSN
15034 -- to ensure that spaces are maintained
15035 SELECT metabib.reingest_metabib_field_entries(source)
15036   FROM metabib.identifier_field_entry mife
15037     INNER JOIN config.metabib_field cmf ON cmf.id = mife.field
15038   WHERE cmf.field_class = 'identifier'
15039     AND cmf.name = 'issn'
15040     AND char_length(value) > 9
15041 ;
15042
15043
15044 COMMIT;