]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/version-upgrade/1.6.1-2.0-upgrade-db.sql
LP#820006: Action trigger notices fixes
[working/Evergreen.git] / Open-ILS / src / sql / Pg / version-upgrade / 1.6.1-2.0-upgrade-db.sql
1 -- Before starting the transaction: drop some constraints that
2 -- may or may not exist.
3
4 CREATE OR REPLACE FUNCTION oils_text_as_bytea (TEXT) RETURNS BYTEA AS $_$
5     SELECT CAST(REGEXP_REPLACE(UPPER($1), $$\\$$, $$\\\\$$, 'g') AS BYTEA);
6 $_$ LANGUAGE SQL IMMUTABLE;
7
8 DROP INDEX asset.asset_call_number_upper_label_id_owning_lib_idx;
9 CREATE INDEX asset_call_number_upper_label_id_owning_lib_idx ON asset.call_number (oils_text_as_bytea(label),id,owning_lib);
10
11 \qecho Before starting the transaction: drop some constraints.
12 \qecho If a DROP fails because the constraint doesn't exist, ignore the failure.
13
14 -- ARG! VIM! '
15
16 ALTER TABLE permission.grp_perm_map        DROP CONSTRAINT grp_perm_map_perm_fkey;
17 ALTER TABLE permission.usr_perm_map        DROP CONSTRAINT usr_perm_map_perm_fkey;
18 ALTER TABLE permission.usr_object_perm_map DROP CONSTRAINT usr_object_perm_map_perm_fkey;
19 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_or_record_once_per_owner;
20 ALTER TABLE booking.resource_type          DROP CONSTRAINT brt_name_once_per_owner;
21
22 \qecho Before starting the transaction: create seed data for the asset.uri table
23 \qecho If the INSERT fails because the -1 value already exists, ignore the failure.
24 INSERT INTO asset.uri (id, href, active) VALUES (-1, 'http://example.com/fake', FALSE);
25
26 \qecho Beginning the transaction now
27
28 BEGIN;
29
30 UPDATE biblio.record_entry SET marc = '<record xmlns="http://www.loc.gov/MARC21/slim"/>' WHERE id = -1;
31
32 -- Highest-numbered individual upgrade script incorporated herein:
33
34 INSERT INTO config.upgrade_log (version) VALUES ('0475');
35
36 -- Push the auri sequence in case it's out of date
37 -- Add 2 as the sequence value must be 1 or higher, and seed is -1
38 SELECT SETVAL('asset.uri_id_seq'::TEXT, (SELECT MAX(id) + 2 FROM asset.uri));
39
40 -- Remove some uses of the connectby() function from the tablefunc contrib module
41 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT, INT ) RETURNS SETOF actor.org_unit AS $$
42     WITH RECURSIVE descendant_depth AS (
43         SELECT  ou.id,
44                 ou.parent_ou,
45                 out.depth
46           FROM  actor.org_unit ou
47                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
48                 JOIN anscestor_depth ad ON (ad.id = ou.id)
49           WHERE ad.depth = $2
50             UNION ALL
51         SELECT  ou.id,
52                 ou.parent_ou,
53                 out.depth
54           FROM  actor.org_unit ou
55                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
56                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
57     ), anscestor_depth AS (
58         SELECT  ou.id,
59                 ou.parent_ou,
60                 out.depth
61           FROM  actor.org_unit ou
62                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
63           WHERE ou.id = $1
64             UNION ALL
65         SELECT  ou.id,
66                 ou.parent_ou,
67                 out.depth
68           FROM  actor.org_unit ou
69                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
70                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
71     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
72 $$ LANGUAGE SQL;
73
74 CREATE OR REPLACE FUNCTION actor.org_unit_descendants( INT ) RETURNS SETOF actor.org_unit AS $$
75     WITH RECURSIVE descendant_depth AS (
76         SELECT  ou.id,
77                 ou.parent_ou,
78                 out.depth
79           FROM  actor.org_unit ou
80                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
81           WHERE ou.id = $1
82             UNION ALL
83         SELECT  ou.id,
84                 ou.parent_ou,
85                 out.depth
86           FROM  actor.org_unit ou
87                 JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
88                 JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
89     ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);
90 $$ LANGUAGE SQL;
91
92 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
93     WITH RECURSIVE anscestor_depth AS (
94         SELECT  ou.id,
95                 ou.parent_ou
96           FROM  actor.org_unit ou
97           WHERE ou.id = $1
98             UNION ALL
99         SELECT  ou.id,
100                 ou.parent_ou
101           FROM  actor.org_unit ou
102                 JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
103     ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
104 $$ LANGUAGE SQL;
105
106 -- Support merge template buckets
107 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
108
109 -- Recreate one of the constraints that we just dropped,
110 -- under a different name:
111
112 ALTER TABLE booking.resource_type
113         ALTER COLUMN record TYPE BIGINT;
114
115 ALTER TABLE booking.resource_type
116         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
117
118 -- Now upgrade permission.perm_list.  This is fairly complicated.
119
120 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
121 -- permissions, the dependents will follow and stay in sync:
122
123 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
124     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
125
126 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_perm_map_perm_fkey FOREIGN KEY (perm)
127     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
128
129 ALTER TABLE permission.usr_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
130     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
131
132 UPDATE permission.perm_list
133     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
134     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
135
136 -- The following UPDATES were originally in an individual upgrade script, but should
137 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
138 -- We retain the UPDATES here, commented out, as historical relics.
139
140 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
141 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
142
143 -- Spelling correction
144 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
145
146 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
147 -- in order to clean up accumulated cruft.
148
149 -- The first step is to establish some triggers so that, when we change the id of a permission,
150 -- the associated translations are updated accordingly.
151
152 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
153 BEGIN
154
155     EXECUTE $$
156         UPDATE  config.i18n_core
157           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
158           WHERE fq_field LIKE '$$ || hint || $$.%' 
159                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
160
161     RETURN;
162
163 END;
164 $_$ LANGUAGE PLPGSQL;
165
166 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
167 BEGIN
168     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
169     RETURN NEW;
170 END;
171 $_$ LANGUAGE PLPGSQL;
172
173 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
174 BEGIN
175     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
176     RETURN NEW;
177 END;
178 $_$ LANGUAGE PLPGSQL;
179
180
181 CREATE TRIGGER maintain_perm_i18n_tgr
182     AFTER UPDATE ON permission.perm_list
183     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
184
185 -- Next, create a new table as a convenience for sloshing data back and forth,
186 -- and for recording which permission went where.  It looks just like
187 -- permission.perm_list, but with two extra columns: one for the old id, and one to
188 -- distinguish between predefined permissions and non-predefined permissions.
189
190 -- This table is, in effect, a temporary table, because we can drop it once the
191 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
192 -- concerned, because we don't want it to disappear at the end of the session.
193 -- We keep it around so that we have a map showing the old id and the new id for
194 -- each permission.  However there is no IDL entry for it, nor is it defined
195 -- in the base sql files.
196
197 CREATE TABLE permission.temp_perm (
198         id          INT        PRIMARY KEY,
199         code        TEXT       UNIQUE,
200         description TEXT,
201         old_id      INT,
202         predefined  BOOL       NOT NULL DEFAULT TRUE
203 );
204
205 -- Populate the temp table with a definitive set of predefined permissions,
206 -- hard-coding the ids.
207
208 -- The first set of permissions is derived from the database, as loaded in a
209 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
210 -- script.  The second set is derived from the IDL -- permissions that are referenced
211 -- in <permacrud> elements but not defined in the database.
212
213 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
214      '' );
215 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
216      'Allow a user to log in to the OPAC' );
217 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
218      'Allow a user to log in to the staff client' );
219 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
220      'Allow a user to create a metarecord holds' );
221 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
222      'Allow a user to place a hold at the title level' );
223 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
224      'Allow a user to place a volume level hold' );
225 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
226      'Allow a user to place a hold on a specific copy' );
227 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
228      'Allow a user to create holds for another user (if true, we still check to make sure they have permission to make the type of hold they are requesting, for example, COPY_HOLDS)' );
229 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
230      '* no longer applicable' );
231 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
232      'Allow a user to view another user''s holds' );
233 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
234      '* no longer applicable' );
235 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
236      'Allow a user to update another user''s hold' );
237 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
238      'Allow a user to renew items' );
239 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
240      'Allow a user to view bill details' );
241 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
242      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
243 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
244      'Allow a user to edit a MARC record' );
245 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
246      'Allow a user to create new MARC records' );
247 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
248      'Allow a user to import a MARC record via the Z39.50 interface' );
249 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
250      'Allow a user to create a volume' );
251 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
252      'Allow a user to edit volumes - needed for merging records. This is a duplicate of VOLUME_UPDATE; user must have both permissions at appropriate level to merge records.' );
253 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
254      'Allow a user to delete a volume' );
255 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
256      'Allow a user to create a new copy object' );
257 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
258      'Allow a user to edit a copy' );
259 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
260      'Allow a user to delete a copy' );
261 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
262      'Allow a user to continue to renew an item even if it is required for a hold' );
263 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
264      'Allow a user to create another user' );
265 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
266      'Allow a user to edit a user''s record' );
267 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
268      'Allow a user to mark a user as deleted' );
269 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
270      'Allow a user to view another user''s Patron Record' );
271 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
272      'Allow a user to check in a copy' );
273 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
274      'Allow a user to place an item in transit' );
275 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
276      'Allow a user to view user permissions within the user permissions editor' );
277 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
278      '* no longer applicable' );
279 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
280      'Allow a user to record payments in the Billing Interface' );
281 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
282      'Allow a user to mark an item as ''lost''' );
283 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
284      'Allow a user to mark an item as ''missing''' );
285 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
286      'Allow a user to mark an item as ''claims returned''' );
287 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
288      'Allow a user to create a new billable transaction' );
289 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
290      'Allow a user may view another user''s transactions' );
291 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
292      'Allow a user to create a new bill on a transaction' );
293 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
294      'Allow a user to view another user''s containers (buckets)' );
295 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
296      'Allow a user to create a new container for another user' );
297 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
298      'Allow a user to change the settings for an organization unit' );
299 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
300      'Allow a user to see what another user has checked out' );
301 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
302      'Allow a user to delete another user''s container' );
303 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
304      'Allow a user to create a container item for another user' );
305 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
306      'Allow a user to add other users to permission groups' );
307 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
308      'Allow a user to remove other users from permission groups' );
309 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
310      'Allow a user to view other users'' permission groups' );
311 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
312      'Allow a user to determine whether another user can check out an item' );
313 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
314      'Allow a user to edit copies in batch' );
315 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
316      'User may create a new patron statistical category' );
317 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
318      'User may create a copy statistical category' );
319 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
320      'User may create an entry in a patron statistical category' );
321 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
322      'User may create an entry in a copy statistical category' );
323 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
324      'User may update a patron statistical category' );
325 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
326      'User may update a copy statistical category' );
327 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
328      'User may update an entry in a patron statistical category' );
329 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
330      'User may update an entry in a copy statistical category' );
331 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
332      'User may link another user to an entry in a statistical category' );
333 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
334      'User may link a copy to an entry in a statistical category' );
335 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
336      'User may delete a patron statistical category' );
337 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
338      'User may delete a copy statistical category' );
339 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
340      'User may delete an entry from a patron statistical category' );
341 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
342      'User may delete an entry from a copy statistical category' );
343 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
344      'User may delete a patron statistical category entry map' );
345 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
346      'User may delete a copy statistical category entry map' );
347 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
348      'Allow a user to create a new non-cataloged item type' );
349 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
350      'Allow a user to update a non-cataloged item type' );
351 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
352      'Allow a user to create a new in-house-use ' );
353 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
354      'Allow a user to check out a copy' );
355 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
356      'Allow a user to create a new copy location' );
357 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
358      'Allow a user to update a copy location' );
359 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
360      'Allow a user to delete a copy location' );
361 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
362      'Allow a user to create a transit_copy object for transiting a copy' );
363 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
364      'Allow a user to close out a transit on a copy' );
365 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
366      'Allow a user to see if another user has permission to place a hold on a given copy' );
367 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
368      'Allow a user to view which users have checked out a given copy' );
369 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
370      'Allow a user to perform Z39.50 queries against remote servers' );
371 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
372      'Allow a user to register a new workstation' );
373 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
374      'Allow a user to view all notes attached to a copy' );
375 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
376      'Allow a user to view all notes attached to a volume' );
377 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
378      'Allow a user to view all notes attached to a title' );
379 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
380      'Allow a user to create a new copy note' );
381 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
382      'Allow a user to create a new volume note' );
383 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
384      'Allow a user to create a new title note' );
385 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
386      'Allow a user to delete another user''s copy notes' );
387 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
388      'Allow a user to delete another user''s volume note' );
389 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
390      'Allow a user to delete another user''s title note' );
391 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
392      'Allow a user to update another user''s container' );
393 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
394      'Allow a user to create a container for themselves' );
395 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
396      'Allow a user to view notifications attached to a hold' );
397 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
398      'Allow a user to create new hold notifications' );
399 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
400      'Allow a user to update an organization unit setting' );
401 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
402      'Allow a user to upload an offline script' );
403 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
404      'Allow a user to view uploaded offline script information' );
405 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
406      'Allow a user to execute an offline script batch' );
407 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
408      'Allow a user to change the due date on an item to any date' );
409 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
410      'Allow a user to bypass the circulation permit call for check out' );
411 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
412      'Allow a user to override the copy_is_reference event' );
413 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
414      'Allow a user to void a bill' );
415 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
416      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
417 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
418      'Allow a user to check out an item in a non-circulatable status' );
419 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
420      'Allow a user to check in/out an item that has an alert message' );
421 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
422      'Allow a user to remove the lost status from a copy' );
423 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
424      'Allow a user to change the missing status on a copy' );
425 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
426      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
427 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
428      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
429 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
430      'Allow a user to query the ZIP code data method' );
431 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
432      'Allow a user to cancel holds' );
433 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
434      'Allow a user to create duplicate holds (two or more holds on the same title)' );
435 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
436      'Allow a user to remove a closed date interval for a given location' );
437 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
438      'Allow a user to update a closed date interval for a given location' );
439 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
440      'Allow a user to create a new closed date for a location' );
441 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
442      'Allow a user to delete a non cataloged type' );
443 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
444      'Allow a user to put someone into collections' );
445 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
446      'Allow a user to remove someone from collections' );
447 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
448      'Allow a user to bar a patron' );
449 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
450      'Allow a user to un-bar a patron' );
451 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
452      'Allow a user to remove an existing workstation so a new one can replace it' );
453 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
454      'Allow a user to add/remove users to/from the "User" group' );
455 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
456      'Allow a user to add/remove users to/from the "Patron" group' );
457 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
458      'Allow a user to add/remove users to/from the "Staff" group' );
459 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
460      'Allow a user to add/remove users to/from the "Circulator" group' );
461 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
462      'Allow a user to add/remove users to/from the "Cataloger" group' );
463 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
464      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
465 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
466      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
467 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
468      'Allow a user to add/remove users to/from the "LibraryManager" group' );
469 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
470      'Allow a user to add/remove users to/from the "Cat1" group' );
471 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
472      'Allow a user to add/remove users to/from the "Supercat" group' );
473 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
474      'Allow a user to add/remove users to/from the "SIP-Client" group' );
475 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
476      'Allow a user to add/remove users to/from the "Vendor" group' );
477 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
478      'Allow a user to place a hold on an age-protected item' );
479 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
480      'Allow a user to renew an item past the maximum renewal count' );
481 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
482      'Allow staff to override checkout count failure' );
483 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
484      'Allow staff to override overdue count failure' );
485 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
486      'Allow staff to override fine amount checkout failure' );
487 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
488      'Allow staff to override circulation copy range failure' );
489 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
490      'Allow staff to override item on holds shelf failure' );
491 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
492      'Allow staff to force checkout of Missing/Lost type items' );
493 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
494      'Allow a user to place multiple holds on a single title' );
495 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
496      'Allow a user to run reports' );
497 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
498      'Allow a user to share report his own folders' );
499 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
500      'Allow a user to view report output' );
501 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
502      'Allow a user to checkout an item that is marked as non-circ' );
503 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
504      'Allow a user to delete an item out of another user''s container' );
505 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
506      'Allow a staff member to define where another staff member has their permissions' );
507 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
508      'Allow a user to create a new funding source' );
509 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
510      'Allow a user to delete a funding source' );
511 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
512      'Allow a user to view a funding source' );
513 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
514      'Allow a user to update a funding source' );
515 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
516      'Allow a user to create a new fund' );
517 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
518      'Allow a user to delete a fund' );
519 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
520      'Allow a user to view a fund' );
521 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
522      'Allow a user to update a fund' );
523 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
524      'Allow a user to create a new fund allocation' );
525 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
526      'Allow a user to delete a fund allocation' );
527 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
528      'Allow a user to view a fund allocation' );
529 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
530      'Allow a user to update a fund allocation' );
531 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
532      'Lowest level permission required to access the ACQ interface' );
533 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
534      'Allow a user to create a new provider' );
535 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
536      'Allow a user to delate a provider' );
537 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
538      'Allow a user to view a provider' );
539 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
540      'Allow a user to update a provider' );
541 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
542      'Allow a user to create/view/update/delete a funding source' );
543 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
544      '(Deprecated) Allow a user to create/view/update/delete a fund' );
545 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
546      'Allow a user to view/credit/debit a funding source' );
547 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
548      'Allow a user to view/credit/debit a fund' );
549 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
550      'Allows a user to create a picklist' );
551 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
552      'Allow a user to create/view/update/delete a provider' );
553 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
554      'Allow a user to view and purchase from a provider' );
555 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
556      'Allow a user to view another users picklist' );
557 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
558      'Allow a staff member to directly remove a bibliographic record' );
559 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
560      'Allow a user to create/view/update/delete a currency_type' );
561 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
562      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
563 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
564      'Allow a user to view billing types' );
565 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
566      'Allow a user to mark an item status as ''available''' );
567 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
568      'Allow a user to mark an item status as ''checked out''' );
569 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
570      'Allow a user to mark an item status as ''bindery''' );
571 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
572      'Allow a user to mark an item status as ''lost''' );
573 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
574      'Allow a user to mark an item status as ''missing''' );
575 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
576      'Allow a user to mark an item status as ''in process''' );
577 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
578      'Allow a user to mark an item status as ''in transit''' );
579 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
580      'Allow a user to mark an item status as ''reshelving''' );
581 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
582      'Allow a user to mark an item status as ''on holds shelf''' );
583 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
584      'Allow a user to mark an item status as ''on order''' );
585 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
586      'Allow a user to mark an item status as ''inter-library loan''' );
587 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
588      'Allows a user to add/remove/edit users in the "ACQ" group' );
589 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
590      'Allows a user to create a purchase order' );
591 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
592      'Allows a user to view a purchase order' );
593 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
594      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
595 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
596      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
597 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
598      'Allows a user to view all org settings at the specified level' );
599 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
600      'Allows a user to create a new MFHD record' );
601 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
602      'Allows a user to update an MFHD record' );
603 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
604      'Allows a user to delete an MFHD record' );
605 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
606      'Allow a user to create/view/update/delete a fund' );
607 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
608      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
609 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
610      'Allows staff to override the max claims returned value for a patron' );
611 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
612      'Allows staff to manually change a patron''s claims returned count' );
613 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
614      'Allows staff to edit the note for a bill on a transaction' );
615 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
616      'Allows staff to edit the note for a payment on a transaction' );
617 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
618      'Allows staff to manually change a patron''s claims never checkout out count' );
619 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
620      'Allow a user to create/view/update/delete a copy location order' );
621 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
622      '' );
623 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
624      '' );
625 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
626      '' );
627 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
628      '' );
629 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
630      '' );
631 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
632      '' );
633 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
634      '' );
635 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
636      '' );
637 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
638      '' );
639 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
640      '' );
641 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
642      '' );
643 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
644      '' );
645 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
646      '' );
647 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
648      '' );
649 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
650      '' );
651 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
652      '' );
653 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
654      '' );
655 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
656      '' );
657 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
658      '' );
659 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
660      '' );
661 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
662      '' );
663 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
664      '' );
665 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
666      '' );
667 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
668      '' );
669 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
670      '' );
671 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
672      '' );
673 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
674      '' );
675 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
676      '' );
677 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
678      '' );
679 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
680      '' );
681 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
682      '' );
683 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
684      '' );
685 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
686      '' );
687 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
688      '' );
689 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
690      '' );
691 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
692      '' );
693 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
694      '' );
695 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
696      '' );
697 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
698      '' );
699 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
700      '' );
701 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
702      '' );
703 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
704      '' );
705 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
706      '' );
707 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
708      '' );
709 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
710      '' );
711 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
712      '' );
713 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
714      '' );
715 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
716      '' );
717 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
718      '' );
719 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
720      '' );
721 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
722      '' );
723 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
724      '' );
725 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
726      '' );
727 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
728      '' );
729 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
730      '' );
731 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
732      '' );
733 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
734      '' );
735 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
736      '' );
737 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
738      '' );
739 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
740      '' );
741 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
742      '' );
743 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
744      '' );
745 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
746      '' );
747 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
748      '' );
749 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
750      '' );
751 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
752      '' );
753 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
754      '' );
755 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
756      '' );
757 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
758      '' );
759 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
760      '' );
761 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
762      '' );
763 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
764      '' );
765 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
766      '' );
767 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
768      '' );
769 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
770      '' );
771 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
772      '' );
773 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
774      '' );
775 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
776      '' );
777 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
778      '' );
779 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
780      '' );
781 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
782      '' );
783 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
784      '' );
785 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
786      '' );
787 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
788      '' );
789 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
790      '' );
791 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
792      '' );
793 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
794      '' );
795 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
796      '' );
797 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
798      '' );
799 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
800      '' );
801 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
802      '' );
803 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
804      '' );
805 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
806      '' );
807 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
808      '' );
809 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
810      '' );
811 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
812      '' );
813 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
814      '' );
815 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
816      '' );
817 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
818      '' );
819 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
820      '' );
821 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
822      '' );
823 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
824      '' );
825 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
826      '' );
827 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
828      '' );
829 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
830      '' );
831 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
832      '' );
833 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
834      '' );
835 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
836      '' );
837 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
838      '' );
839 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
840      '' );
841 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
842      '' );
843 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
844      '' );
845 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
846      '' );
847 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
848      '' );
849 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
850      '' );
851 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
852      '' );
853 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
854      '' );
855 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
856      '' );
857 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
858      '' );
859 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
860      '' );
861 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
862      '' );
863 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
864      '' );
865 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
866      '' );
867 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
868      '' );
869 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
870      '' );
871 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
872      '' );
873 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
874      '' );
875 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
876      '' );
877 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
878      '' );
879 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
880      '' );
881 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
882      '' );
883 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
884      '' );
885 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
886      '' );
887 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
888      '' );
889 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
890      '' );
891 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
892      '' );
893 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
894      '' );
895 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
896      '' );
897 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
898      '' );
899 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
900      '' );
901 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
902      'Allows a user to place a hold on an item that they already have checked out' );
903 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
904      'Allow a user to create/update/delete reasons for order cancellations' );
905 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
906      'Allow a user to transfer different amounts of money out of one fund and into another' );
907 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
908      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
909 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
910      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
911 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
912      'Allow a user to force renewal of an item that could fulfill a hold request' );
913 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
914      'Allow a user to merge authority records together' );
915 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
916      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
917 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
918      'Allow a user to administer trigger event definitions' );
919 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
920      'Allow a user to create, delete, and update trigger cleanup entries' );
921 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
922      'Allow a user to create trigger cleanup entries' );
923 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
924      'Allow a user to delete trigger cleanup entries' );
925 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
926      'Allow a user to update trigger cleanup entries' );
927 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
928      'Allow a user to create trigger event definitions' );
929 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
930      'Allow a user to delete trigger event definitions' );
931 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
932      'Allow a user to update trigger event definitions' );
933 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
934      'Allow a user to view trigger event definitions' );
935 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
936      'Allow a user to create, update, and delete trigger hooks' );
937 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
938      'Allow a user to create trigger hooks' );
939 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
940      'Allow a user to delete trigger hooks' );
941 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
942      'Allow a user to update trigger hooks' );
943 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
944      'Allow a user to create, update, and delete trigger reactors' );
945 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
946      'Allow a user to create trigger reactors' );
947 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
948      'Allow a user to delete trigger reactors' );
949 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
950      'Allow a user to update trigger reactors' );
951 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
952      'Allow a user to delete trigger template output' );
953 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
954      'Allow a user to delete trigger template output' );
955 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
956      'Allow a user to create, update, and delete trigger validators' );
957 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
958      'Allow a user to create trigger validators' );
959 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
960      'Allow a user to delete trigger validators' );
961 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
962      'Allow a user to update trigger validators' );
963 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
964      'Enables the user to create/update/delete booking resources' );
965 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
966      'Enables the user to create/update/delete booking resource types' );
967 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
968      'Enables the user to create/update/delete booking resource attributes' );
969 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
970      'Enables the user to create/update/delete booking resource attribute maps' );
971 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
972      'Enables the user to create/update/delete booking resource attribute values' );
973 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
974      'Enables the user to create/update/delete booking reservations' );
975 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
976      'Enables the user to create/update/delete booking reservation attribute value maps' );
977 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
978      'Allows a user to retrieve a booking reservation pull list' );
979 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
980      'Allows a user to capture booking reservations' );
981 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
982      '' );
983 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
984      '' );
985 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
986      'Allows user records to be merged' );
987 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
988      'Allow a user to place holds on serials issuances' );
989 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
990      'View org unit settings related to credit card processing' );
991 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
992      'Update org unit settings related to credit card processing' );
993 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
994         'Create/update/delete serial caption and pattern objects' );
995 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
996         'Create/update/delete serial subscription objects' );
997 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
998         'Create/update/delete serial distribution objects' );
999 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
1000         'Create/update/delete serial stream objects' );
1001 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
1002         'Receive serial items' );
1003
1004 -- Now for the permissions from the IDL.  We don't have descriptions for them.
1005
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1080 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1081 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1082 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1083 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1084 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1085 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1086 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1087 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1088 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1089 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1090 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1091 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1092 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1093 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1094 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1095
1096 -- For every permission in the temp_perm table that has a matching
1097 -- permission in the real table: record the original id.
1098
1099 UPDATE permission.temp_perm AS tp
1100 SET old_id =
1101         (
1102                 SELECT id
1103                 FROM permission.perm_list AS ppl
1104                 WHERE ppl.code = tp.code
1105         )
1106 WHERE code IN ( SELECT code FROM permission.perm_list );
1107
1108 -- Start juggling ids.
1109
1110 -- If any permissions have negative ids (with the special exception of -1),
1111 -- we need to move them into the positive range in order to avoid duplicate
1112 -- key problems (since we are going to use the negative range as a temporary
1113 -- staging area).
1114
1115 -- First, move any predefined permissions that have negative ids (again with
1116 -- the special exception of -1).  Temporarily give them positive ids based on
1117 -- the sequence.
1118
1119 UPDATE permission.perm_list
1120 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1121 WHERE id < -1
1122   AND code IN (SELECT code FROM permission.temp_perm);
1123
1124 -- Identify any non-predefined permissions whose ids are either negative
1125 -- or within the range (0-1000) reserved for predefined permissions.
1126 -- Assign them ids above 1000, based on the sequence.  Record the new
1127 -- ids in the temp_perm table.
1128
1129 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1130 (
1131         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1132                 code, description, id, false
1133         FROM permission.perm_list
1134         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1135         AND code NOT IN (SELECT code FROM permission.temp_perm)
1136 );
1137
1138 -- Now update the ids of those non-predefined permissions, using the
1139 -- values assigned in the previous step.
1140
1141 UPDATE permission.perm_list AS ppl
1142 SET id = (
1143                 SELECT id
1144                 FROM permission.temp_perm AS tp
1145                 WHERE tp.code = ppl.code
1146         )
1147 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1148
1149 -- Now the negative ids have been eliminated, except for -1.  Move all the
1150 -- predefined permissions temporarily into the negative range.
1151
1152 UPDATE permission.perm_list
1153 SET id = -1 - id
1154 WHERE id <> -1
1155 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1156
1157 -- Apply the final ids to the existing predefined permissions.
1158
1159 UPDATE permission.perm_list AS ppl
1160 SET id =
1161         (
1162                 SELECT id
1163                 FROM permission.temp_perm AS tp
1164                 WHERE tp.code = ppl.code
1165         )
1166 WHERE
1167         id <> -1
1168         AND ppl.code IN
1169         (
1170                 SELECT code from permission.temp_perm
1171                 WHERE predefined
1172                 AND old_id IS NOT NULL
1173         );
1174
1175 -- If there are any predefined permissions that don't exist yet in
1176 -- permission.perm_list, insert them now.
1177
1178 INSERT INTO permission.perm_list ( id, code, description )
1179 (
1180         SELECT id, code, description
1181         FROM permission.temp_perm
1182         WHERE old_id IS NULL
1183 );
1184
1185 -- Reset the sequence to the lowest feasible value.  This may or may not
1186 -- accomplish anything, but it will do no harm.
1187
1188 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1189         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1190
1191 -- If any permission lacks a description, use the code as a description.
1192 -- It's better than nothing.
1193
1194 UPDATE permission.perm_list
1195 SET description = code
1196 WHERE description IS NULL
1197    OR description = '';
1198
1199 -- Thus endeth the Great Renumbering.
1200
1201 -- Having massaged the permissions, massage the way they are assigned, by inserting
1202 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1203 -- been assigned, so we insert the rows only if they aren't already there.
1204
1205 -- for backwards compat, give everyone the permission
1206 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1207     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1208         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1209                 AND NOT EXISTS (
1210                         SELECT 1
1211                         FROM permission.grp_perm_map AS map
1212                         WHERE
1213                                 grp = 1
1214                                 AND map.perm = perm.id
1215                 );
1216
1217 -- Add trigger administration permissions to the Local System Administrator group.
1218 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1219     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1220     WHERE (
1221                 perm.code LIKE 'ADMIN_TRIGGER%'
1222         OR perm.code LIKE 'CREATE_TRIGGER%'
1223         OR perm.code LIKE 'DELETE_TRIGGER%'
1224         OR perm.code LIKE 'UPDATE_TRIGGER%'
1225         ) AND NOT EXISTS (
1226                 SELECT 1
1227                 FROM permission.grp_perm_map AS map
1228                 WHERE
1229                         grp = 10
1230                         AND map.perm = perm.id
1231         );
1232
1233 -- View trigger permissions are required at a consortial level for initial setup
1234 -- (as before, only if the row doesn't already exist)
1235 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1236     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1237         WHERE code LIKE 'VIEW_TRIGGER%'
1238                 AND NOT EXISTS (
1239                         SELECT 1
1240                         FROM permission.grp_perm_map AS map
1241                         WHERE
1242                                 grp = 10
1243                                 AND map.perm = perm.id
1244                 );
1245
1246 -- Permission for merging auth records may already be defined,
1247 -- so add it only if it isn't there.
1248 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1249     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1250         WHERE code = 'MERGE_AUTH_RECORDS'
1251                 AND NOT EXISTS (
1252                         SELECT 1
1253                         FROM permission.grp_perm_map AS map
1254                         WHERE
1255                                 grp = 4
1256                                 AND map.perm = perm.id
1257                 );
1258
1259 -- Create a reference table as parent to both
1260 -- config.org_unit_setting_type and config_usr_setting_type
1261
1262 CREATE TABLE config.settings_group (
1263     name    TEXT PRIMARY KEY,
1264     label   TEXT UNIQUE NOT NULL -- I18N
1265 );
1266
1267 -- org_unit setting types
1268 CREATE TABLE config.org_unit_setting_type (
1269     name            TEXT    PRIMARY KEY,
1270     label           TEXT    UNIQUE NOT NULL,
1271     grp             TEXT    REFERENCES config.settings_group (name),
1272     description     TEXT,
1273     datatype        TEXT    NOT NULL DEFAULT 'string',
1274     fm_class        TEXT,
1275     view_perm       INT,
1276     update_perm     INT,
1277     --
1278     -- define valid datatypes
1279     --
1280     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1281     ( 'bool', 'integer', 'float', 'currency', 'interval',
1282       'date', 'string', 'object', 'array', 'link' ) ),
1283     --
1284     -- fm_class is meaningful only for 'link' datatype
1285     --
1286     CONSTRAINT coust_no_empty_link CHECK
1287     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1288       ( datatype <> 'link' AND fm_class IS NULL ) ),
1289         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1290                 ON UPDATE CASCADE
1291                 ON DELETE RESTRICT
1292                 DEFERRABLE INITIALLY DEFERRED,
1293         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1294                 ON UPDATE CASCADE
1295                 DEFERRABLE INITIALLY DEFERRED
1296 );
1297
1298 CREATE TABLE config.usr_setting_type (
1299
1300     name TEXT PRIMARY KEY,
1301     opac_visible BOOL NOT NULL DEFAULT FALSE,
1302     label TEXT UNIQUE NOT NULL,
1303     description TEXT,
1304     grp             TEXT    REFERENCES config.settings_group (name),
1305     datatype TEXT NOT NULL DEFAULT 'string',
1306     fm_class TEXT,
1307
1308     --
1309     -- define valid datatypes
1310     --
1311     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1312     ( 'bool', 'integer', 'float', 'currency', 'interval',
1313         'date', 'string', 'object', 'array', 'link' ) ),
1314
1315     --
1316     -- fm_class is meaningful only for 'link' datatype
1317     --
1318     CONSTRAINT coust_no_empty_link CHECK
1319     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1320         ( datatype <> 'link' AND fm_class IS NULL ) )
1321
1322 );
1323
1324 --------------------------------------
1325 -- Seed data for org_unit_setting_type
1326 --------------------------------------
1327
1328 INSERT into config.org_unit_setting_type
1329 ( name, label, description, datatype ) VALUES
1330
1331 ( 'auth.opac_timeout',
1332   'OPAC Inactivity Timeout (in seconds)',
1333   null,
1334   'integer' ),
1335
1336 ( 'auth.staff_timeout',
1337   'Staff Login Inactivity Timeout (in seconds)',
1338   null,
1339   'integer' ),
1340
1341 ( 'circ.lost_materials_processing_fee',
1342   'Lost Materials Processing Fee',
1343   null,
1344   'currency' ),
1345
1346 ( 'cat.default_item_price',
1347   'Default Item Price',
1348   null,
1349   'currency' ),
1350
1351 ( 'org.bounced_emails',
1352   'Sending email address for patron notices',
1353   null,
1354   'string' ),
1355
1356 ( 'circ.hold_expire_alert_interval',
1357   'Holds: Expire Alert Interval',
1358   'Amount of time before a hold expires at which point the patron should be alerted',
1359   'interval' ),
1360
1361 ( 'circ.hold_expire_interval',
1362   'Holds: Expire Interval',
1363   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1364   'interval' ),
1365
1366 ( 'credit.payments.allow',
1367   'Allow Credit Card Payments',
1368   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1369   'bool' ),
1370
1371 ( 'global.default_locale',
1372   'Global Default Locale',
1373   null,
1374   'string' ),
1375
1376 ( 'circ.void_overdue_on_lost',
1377   'Void overdue fines when items are marked lost',
1378   null,
1379   'bool' ),
1380
1381 ( 'circ.hold_stalling.soft',
1382   'Holds: Soft stalling interval',
1383   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1384   'interval' ),
1385
1386 ( 'circ.hold_stalling_hard',
1387   'Holds: Hard stalling interval',
1388   '',
1389   'interval' ),
1390
1391 ( 'circ.hold_boundary.hard',
1392   'Holds: Hard boundary',
1393   null,
1394   'integer' ),
1395
1396 ( 'circ.hold_boundary.soft',
1397   'Holds: Soft boundary',
1398   null,
1399   'integer' ),
1400
1401 ( 'opac.barcode_regex',
1402   'Patron barcode format',
1403   'Regular expression defining the patron barcode format',
1404   'string' ),
1405
1406 ( 'global.password_regex',
1407   'Password format',
1408   'Regular expression defining the password format',
1409   'string' ),
1410
1411 ( 'circ.item_checkout_history.max',
1412   'Maximum previous checkouts displayed',
1413   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1414   'integer' ),
1415
1416 ( 'circ.reshelving_complete.interval',
1417   'Change reshelving status interval',
1418   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1419   'interval' ),
1420
1421 ( 'circ.holds.default_estimated_wait_interval',
1422   'Holds: Default Estimated Wait',
1423   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the default estimated length of time to assume an item will be checked out.',
1424   'interval' ),
1425
1426 ( 'circ.holds.min_estimated_wait_interval',
1427   'Holds: Minimum Estimated Wait',
1428   'When predicting the amount of time a patron will be waiting for a hold to be fulfilled, this is the minimum estimated length of time to assume an item will be checked out.',
1429   'interval' ),
1430
1431 ( 'circ.selfcheck.patron_login_timeout',
1432   'Selfcheck: Patron Login Timeout (in seconds)',
1433   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1434   'integer' ),
1435
1436 ( 'circ.selfcheck.alert.popup',
1437   'Selfcheck: Pop-up alert for errors',
1438   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1439   'bool' ),
1440
1441 ( 'circ.selfcheck.require_patron_password',
1442   'Selfcheck: Require patron password',
1443   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1444   'bool' ),
1445
1446 ( 'global.juvenile_age_threshold',
1447   'Juvenile Age Threshold',
1448   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1449   'interval' ),
1450
1451 ( 'cat.bib.keep_on_empty',
1452   'Retain empty bib records',
1453   'Retain a bib record even when all attached copies are deleted',
1454   'bool' ),
1455
1456 ( 'cat.bib.alert_on_empty',
1457   'Alert on empty bib records',
1458   'Alert staff when the last copy for a record is being deleted',
1459   'bool' ),
1460
1461 ( 'patron.password.use_phone',
1462   'Patron: password from phone #',
1463   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1464   'bool' ),
1465
1466 ( 'circ.charge_on_damaged',
1467   'Charge item price when marked damaged',
1468   'Charge item price when marked damaged',
1469   'bool' ),
1470
1471 ( 'circ.charge_lost_on_zero',
1472   'Charge lost on zero',
1473   '',
1474   'bool' ),
1475
1476 ( 'circ.damaged_item_processing_fee',
1477   'Charge processing fee for damaged items',
1478   'Charge processing fee for damaged items',
1479   'currency' ),
1480
1481 ( 'circ.void_lost_on_checkin',
1482   'Circ: Void lost item billing when returned',
1483   'Void lost item billing when returned',
1484   'bool' ),
1485
1486 ( 'circ.max_accept_return_of_lost',
1487   'Circ: Void lost max interval',
1488   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1489   'interval' ),
1490
1491 ( 'circ.void_lost_proc_fee_on_checkin',
1492   'Circ: Void processing fee on lost item return',
1493   'Void processing fee when lost item returned',
1494   'bool' ),
1495
1496 ( 'circ.restore_overdue_on_lost_return',
1497   'Circ: Restore overdues on lost item return',
1498   'Restore overdue fines on lost item return',
1499   'bool' ),
1500
1501 ( 'circ.lost_immediately_available',
1502   'Circ: Lost items usable on checkin',
1503   'Lost items are usable on checkin instead of going ''home'' first',
1504   'bool' ),
1505
1506 ( 'circ.holds_fifo',
1507   'Holds: FIFO',
1508   'Force holds to a more strict First-In, First-Out capture',
1509   'bool' ),
1510
1511 ( 'opac.allow_pending_address',
1512   'OPAC: Allow pending addresses',
1513   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1514   'bool' ),
1515
1516 ( 'ui.circ.show_billing_tab_on_bills',
1517   'Show billing tab first when bills are present',
1518   'If enabled and a patron has outstanding bills and the alert page is not required, show the billing tab by default, instead of the checkout tab, when a patron is loaded',
1519   'bool' ),
1520
1521 ( 'ui.general.idle_timeout',
1522     'GUI: Idle timeout',
1523     'If you want staff client windows to be minimized after a certain amount of system idle time, set this to the number of seconds of idle time that you want to allow before minimizing (requires staff client restart).',
1524     'integer' ),
1525
1526 ( 'ui.circ.in_house_use.entry_cap',
1527   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1528   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1529   'integer' ),
1530
1531 ( 'ui.circ.in_house_use.entry_warn',
1532   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1533   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1534   'integer' ),
1535
1536 ( 'acq.default_circ_modifier',
1537   'Default circulation modifier',
1538   null,
1539   'string' ),
1540
1541 ( 'acq.tmp_barcode_prefix',
1542   'Temporary barcode prefix',
1543   null,
1544   'string' ),
1545
1546 ( 'acq.tmp_callnumber_prefix',
1547   'Temporary call number prefix',
1548   null,
1549   'string' ),
1550
1551 ( 'ui.circ.patron_summary.horizontal',
1552   'Patron circulation summary is horizontal',
1553   null,
1554   'bool' ),
1555
1556 ( 'ui.staff.require_initials',
1557   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1558   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1559   'bool' ),
1560
1561 ( 'ui.general.button_bar',
1562   'Button bar',
1563   null,
1564   'bool' ),
1565
1566 ( 'circ.hold_shelf_status_delay',
1567   'Hold Shelf Status Delay',
1568   'The purpose is to provide an interval of time after an item goes into the on-holds-shelf status before it appears to patrons that it is actually on the holds shelf.  This gives staff time to process the item before it shows as ready-for-pickup.',
1569   'interval' ),
1570
1571 ( 'circ.patron_invalid_address_apply_penalty',
1572   'Invalid patron address penalty',
1573   'When set, if a patron address is set to invalid, a penalty is applied.',
1574   'bool' ),
1575
1576 ( 'circ.checkout_fills_related_hold',
1577   'Checkout Fills Related Hold',
1578   'When a patron checks out an item and they have no holds that directly target the item, the system will attempt to find a hold for the patron that could be fulfilled by the checked out item and fulfills it',
1579   'bool'),
1580
1581 ( 'circ.selfcheck.auto_override_checkout_events',
1582   'Selfcheck override events list',
1583   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1584   'array' ),
1585
1586 ( 'circ.staff_client.do_not_auto_attempt_print',
1587   'Disable Automatic Print Attempt Type List',
1588   'Disable automatic print attempts from staff client interfaces for the receipt types in this list.  Possible values: "Checkout", "Bill Pay", "Hold Slip", "Transit Slip", and "Hold/Transit Slip".  This is different from the Auto-Print checkbox in the pertinent interfaces in that it disables automatic print attempts altogether, rather than encouraging silent printing by suppressing the print dialog.  The Auto-Print checkbox in these interfaces have no effect on the behavior for this setting.  In the case of the Hold, Transit, and Hold/Transit slips, this also suppresses the alert dialogs that precede the print dialog (the ones that offer Print and Do Not Print as options).',
1589   'array' ),
1590
1591 ( 'ui.patron.default_inet_access_level',
1592   'Default level of patrons'' internet access',
1593   null,
1594   'integer' ),
1595
1596 ( 'circ.max_patron_claim_return_count',
1597     'Max Patron Claims Returned Count',
1598     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1599     'integer' ),
1600
1601 ( 'circ.obscure_dob',
1602     'Obscure the Date of Birth field',
1603     'When true, the Date of Birth column in patron lists will default to Not Visible, and in the Patron Summary sidebar the value will display as <Hidden> unless the field label is clicked.',
1604     'bool' ),
1605
1606 ( 'circ.auto_hide_patron_summary',
1607     'GUI: Toggle off the patron summary sidebar after first view.',
1608     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1609     'bool' ),
1610
1611 ( 'credit.processor.default',
1612     'Credit card processing: Name default credit processor',
1613     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1614     'string' ),
1615
1616 ( 'credit.processor.authorizenet.enabled',
1617     'Credit card processing: AuthorizeNet enabled',
1618     '',
1619     'bool' ),
1620
1621 ( 'credit.processor.authorizenet.login',
1622     'Credit card processing: AuthorizeNet login',
1623     '',
1624     'string' ),
1625
1626 ( 'credit.processor.authorizenet.password',
1627     'Credit card processing: AuthorizeNet password',
1628     '',
1629     'string' ),
1630
1631 ( 'credit.processor.authorizenet.server',
1632     'Credit card processing: AuthorizeNet server',
1633     'Required if using a developer/test account with AuthorizeNet',
1634     'string' ),
1635
1636 ( 'credit.processor.authorizenet.testmode',
1637     'Credit card processing: AuthorizeNet test mode',
1638     '',
1639     'bool' ),
1640
1641 ( 'credit.processor.paypal.enabled',
1642     'Credit card processing: PayPal enabled',
1643     '',
1644     'bool' ),
1645 ( 'credit.processor.paypal.login',
1646     'Credit card processing: PayPal login',
1647     '',
1648     'string' ),
1649 ( 'credit.processor.paypal.password',
1650     'Credit card processing: PayPal password',
1651     '',
1652     'string' ),
1653 ( 'credit.processor.paypal.signature',
1654     'Credit card processing: PayPal signature',
1655     '',
1656     'string' ),
1657 ( 'credit.processor.paypal.testmode',
1658     'Credit card processing: PayPal test mode',
1659     '',
1660     'bool' ),
1661
1662 ( 'ui.admin.work_log.max_entries',
1663     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1664     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1665   'interval' ),
1666
1667 ( 'ui.admin.patron_log.max_entries',
1668     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1669     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1670   'interval' ),
1671
1672 ( 'lib.courier_code',
1673     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1674     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1675     'string'),
1676
1677 ( 'circ.block_renews_for_holds',
1678     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1679     oils_i18n_gettext('circ.block_renews_for_holds', 'When an item could fulfill a hold, do not allow the current patron to renew', 'coust', 'description'),
1680     'bool' ),
1681
1682 ( 'circ.password_reset_request_per_user_limit',
1683     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1684     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.', 'coust', 'description'),
1685     'string'),
1686
1687 ( 'circ.password_reset_request_time_to_live',
1688     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1689     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Length of time (in seconds) a self-serve password reset request should remain active.', 'coust', 'description'),
1690     'string'),
1691
1692 ( 'circ.password_reset_request_throttle',
1693     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1694     oils_i18n_gettext('circ.password_reset_request_throttle', 'Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.', 'coust', 'description'),
1695     'string')
1696 ;
1697
1698 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1699         'ui.circ.suppress_checkin_popups',
1700         oils_i18n_gettext(
1701             'ui.circ.suppress_checkin_popups', 
1702             'Circ: Suppress popup-dialogs during check-in.', 
1703             'coust', 
1704             'label'),
1705         oils_i18n_gettext(
1706             'ui.circ.suppress_checkin_popups', 
1707             'Circ: Suppress popup-dialogs during check-in.', 
1708             'coust', 
1709             'description'),
1710         'bool'
1711 );
1712
1713 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1714         'format.date',
1715         oils_i18n_gettext(
1716             'format.date',
1717             'GUI: Format Dates with this pattern.', 
1718             'coust', 
1719             'label'),
1720         oils_i18n_gettext(
1721             'format.date',
1722             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1723             'coust', 
1724             'description'),
1725         'string'
1726 ), (
1727         'format.time',
1728         oils_i18n_gettext(
1729             'format.time',
1730             'GUI: Format Times with this pattern.', 
1731             'coust', 
1732             'label'),
1733         oils_i18n_gettext(
1734             'format.time',
1735             'GUI: Format Times with this pattern (examples: "h:m:s.SSS a z" for "2:07:20.666 PM Eastern Daylight Time", "HH:mm" for "14:07")', 
1736             'coust', 
1737             'description'),
1738         'string'
1739 );
1740
1741 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1742         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1743         oils_i18n_gettext(
1744             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1745             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1746             'coust', 
1747             'label'),
1748         oils_i18n_gettext(
1749             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1750             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1751             'coust', 
1752             'description'),
1753         'bool'
1754 );
1755
1756 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1757         'url.remote_column_settings',
1758         oils_i18n_gettext(
1759             'url.remote_column_settings',
1760             'GUI: URL for remote directory containing list column settings.', 
1761             'coust', 
1762             'label'),
1763         oils_i18n_gettext(
1764             'url.remote_column_settings',
1765             'GUI: URL for remote directory containing list column settings.  The format and naming convention for the files found in this directory match those in the local settings directory for a given workstation.  An administrator could create the desired settings locally and then copy all the tree_columns_for_* files to the remote directory.', 
1766             'coust', 
1767             'description'),
1768         'string'
1769 );
1770
1771 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1772         'gui.disable_local_save_columns',
1773         oils_i18n_gettext(
1774             'gui.disable_local_save_columns',
1775             'GUI: Disable the ability to save list column configurations locally.', 
1776             'coust', 
1777             'label'),
1778         oils_i18n_gettext(
1779             'gui.disable_local_save_columns',
1780             'GUI: Disable the ability to save list column configurations locally.  If set, columns may still be manipulated, however, the changes do not persist.  Also, existing local configurations are ignored if this setting is true.', 
1781             'coust', 
1782             'description'),
1783         'bool'
1784 );
1785
1786 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1787         'circ.password_reset_request_requires_matching_email',
1788         oils_i18n_gettext(
1789             'circ.password_reset_request_requires_matching_email',
1790             'Circulation: Require matching email address for password reset requests', 
1791             'coust', 
1792             'label'),
1793         oils_i18n_gettext(
1794             'circ.password_reset_request_requires_matching_email',
1795             'Circulation: Require matching email address for password reset requests', 
1796             'coust', 
1797             'description'),
1798         'bool'
1799 );
1800
1801 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1802         'circ.holds.expired_patron_block',
1803         oils_i18n_gettext(
1804             'circ.holds.expired_patron_block',
1805             'Circulation: Block hold request if hold recipient privileges have expired', 
1806             'coust', 
1807             'label'),
1808         oils_i18n_gettext(
1809             'circ.holds.expired_patron_block',
1810             'Circulation: Block hold request if hold recipient privileges have expired', 
1811             'coust', 
1812             'description'),
1813         'bool'
1814 );
1815
1816 INSERT INTO config.org_unit_setting_type
1817     (name, label, description, datatype) VALUES (
1818         'circ.booking_reservation.default_elbow_room',
1819         oils_i18n_gettext(
1820             'circ.booking_reservation.default_elbow_room',
1821             'Booking: Elbow room',
1822             'coust',
1823             'label'
1824         ),
1825         oils_i18n_gettext(
1826             'circ.booking_reservation.default_elbow_room',
1827             'Elbow room specifies how far in the future you must make a reservation on an item if that item will have to transit to reach its pickup location.  It secondarily defines how soon a reservation on a given item must start before the check-in process will opportunistically capture it for the reservation shelf.',
1828             'coust',
1829             'label'
1830         ),
1831         'interval'
1832     );
1833
1834 -- Org_unit_setting_type(s) that need an fm_class:
1835 INSERT into config.org_unit_setting_type
1836 ( name, label, description, datatype, fm_class ) VALUES
1837 ( 'acq.default_copy_location',
1838   'Default copy location',
1839   null,
1840   'link',
1841   'acpl' );
1842
1843 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1844     'circ.holds.org_unit_target_weight',
1845     'Holds: Org Unit Target Weight',
1846     'Org Units can be organized into hold target groups based on a weight.  Potential copies from org units with the same weight are chosen at random.',
1847     'integer'
1848 );
1849
1850 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1851     'circ.holds.target_holds_by_org_unit_weight',
1852     'Holds: Use weight-based hold targeting',
1853     'Use library weight based hold targeting',
1854     'bool'
1855 );
1856
1857 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1858     'circ.holds.max_org_unit_target_loops',
1859     'Holds: Maximum library target attempts',
1860     'When this value is set and greater than 0, the system will only attempt to find a copy at each possible branch the configured number of times',
1861     'integer'
1862 );
1863
1864
1865 -- Org setting for overriding the circ lib of a precat copy
1866 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1867     'circ.pre_cat_copy_circ_lib',
1868     'Pre-cat Item Circ Lib',
1869     'Override the default circ lib of "here" with a pre-configured circ lib for pre-cat items.  The value should be the "shortname" (aka policy name) of the org unit',
1870     'string'
1871 );
1872
1873 -- Circ auto-renew interval setting
1874 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1875     'circ.checkout_auto_renew_age',
1876     'Checkout auto renew age',
1877     'When an item has been checked out for at least this amount of time, an attempt to check out the item to the patron that it is already checked out to will simply renew the circulation',
1878     'interval'
1879 );
1880
1881 -- Setting for behind the desk hold pickups
1882 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1883     'circ.holds.behind_desk_pickup_supported',
1884     'Holds: Behind Desk Pickup Supported',
1885     'If a branch supports both a public holds shelf and behind-the-desk pickups, set this value to true.  This gives the patron the option to enable behind-the-desk pickups for their holds',
1886     'bool'
1887 );
1888
1889 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1890         'acq.holds.allow_holds_from_purchase_request',
1891         oils_i18n_gettext(
1892             'acq.holds.allow_holds_from_purchase_request', 
1893             'Allows patrons to create automatic holds from purchase requests.', 
1894             'coust', 
1895             'label'),
1896         oils_i18n_gettext(
1897             'acq.holds.allow_holds_from_purchase_request', 
1898             'Allows patrons to create automatic holds from purchase requests.', 
1899             'coust', 
1900             'description'),
1901         'bool'
1902 );
1903
1904 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1905     'circ.holds.target_skip_me',
1906     'Skip For Hold Targeting',
1907     'When true, don''t target any copies at this org unit for holds',
1908     'bool'
1909 );
1910
1911 -- claims returned mark item missing 
1912 INSERT INTO
1913     config.org_unit_setting_type ( name, label, description, datatype )
1914     VALUES (
1915         'circ.claim_return.mark_missing',
1916         'Claim Return: Mark copy as missing', 
1917         'When a circ is marked as claims-returned, also mark the copy as missing',
1918         'bool'
1919     );
1920
1921 -- claims never checked out mark item missing 
1922 INSERT INTO
1923     config.org_unit_setting_type ( name, label, description, datatype )
1924     VALUES (
1925         'circ.claim_never_checked_out.mark_missing',
1926         'Claim Never Checked Out: Mark copy as missing', 
1927         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1928         'bool'
1929     );
1930
1931 -- mark damaged void overdue setting
1932 INSERT INTO
1933     config.org_unit_setting_type ( name, label, description, datatype )
1934     VALUES (
1935         'circ.damaged.void_ovedue',
1936         'Mark item damaged voids overdues',
1937         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1938         'bool'
1939     );
1940
1941 -- hold cancel display limits
1942 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1943     VALUES (
1944         'circ.holds.canceled.display_count',
1945         'Holds: Canceled holds display count',
1946         'How many canceled holds to show in patron holds interfaces',
1947         'integer'
1948     );
1949
1950 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1951     VALUES (
1952         'circ.holds.canceled.display_age',
1953         'Holds: Canceled holds display age',
1954         'Show all canceled holds that were canceled within this amount of time',
1955         'interval'
1956     );
1957
1958 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1959     VALUES (
1960         'circ.holds.uncancel.reset_request_time',
1961         'Holds: Reset request time on un-cancel',
1962         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1963         'bool'
1964     );
1965
1966 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1967     VALUES (
1968         'circ.holds.default_shelf_expire_interval',
1969         'Default hold shelf expire interval',
1970         '',
1971         'interval'
1972 );
1973
1974 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1975     VALUES (
1976         'circ.claim_return.copy_status', 
1977         'Claim Return Copy Status', 
1978         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1979         'link', 
1980         'ccs' 
1981     );
1982
1983 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1984     VALUES ( 
1985         'circ.max_fine.cap_at_price',
1986         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1987         oils_i18n_gettext('circ.max_fine.cap_at_price', 'This prevents the system from charging more than the item price in overdue fines', 'coust', 'description'),
1988         'bool' 
1989     );
1990
1991 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1992     VALUES ( 
1993         'circ.holds.clear_shelf.copy_status',
1994         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1995         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Any copies that have not been put into reshelving, in-transit, or on-holds-shelf (for a new hold) during the clear shelf process will be put into this status.  This is basically a purgatory status for copies waiting to be pulled from the shelf and processed by hand', 'coust', 'description'),
1996         'link',
1997         'ccs'
1998     );
1999
2000 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2001     VALUES ( 
2002         'circ.selfcheck.workstation_required',
2003         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
2004         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
2005         'bool'
2006     ), (
2007         'circ.selfcheck.patron_password_required',
2008         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
2009         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
2010         'bool'
2011     );
2012
2013 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2014     VALUES ( 
2015         'circ.selfcheck.alert.sound',
2016         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2017         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2018         'bool'
2019     );
2020
2021 INSERT INTO
2022     config.org_unit_setting_type (name, label, description, datatype)
2023     VALUES (
2024         'notice.telephony.callfile_lines',
2025         'Telephony: Arbitrary line(s) to include in each notice callfile',
2026         $$
2027         This overrides lines from opensrf.xml.
2028         Line(s) must be valid for your target server and platform
2029         (e.g. Asterisk 1.4).
2030         $$,
2031         'string'
2032     );
2033
2034 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2035     VALUES ( 
2036         'circ.offline.username_allowed',
2037         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2038         oils_i18n_gettext('circ.offline.username_allowed', 'During offline circulations, allow patrons to identify themselves with usernames in addition to barcode.  For this setting to work, a barcode format must also be defined', 'coust', 'description'),
2039         'bool'
2040     );
2041
2042 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2043 VALUES (
2044     'acq.fund.balance_limit.warn',
2045     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2046     oils_i18n_gettext('acq.fund.balance_limit.warn', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will result in a warning to the staff.', 'coust', 'descripton'),
2047     'integer'
2048 );
2049
2050 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2051 VALUES (
2052     'acq.fund.balance_limit.block',
2053     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2054     oils_i18n_gettext('acq.fund.balance_limit.block', 'When the amount remaining in the fund, including spent money and encumbrances, goes below this percentage, attempts to spend from the fund will be blocked.', 'coust', 'description'),
2055     'integer'
2056 );
2057
2058 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2059     VALUES (
2060         'circ.holds.hold_has_copy_at.alert',
2061         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2062         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, alert the patron', 'coust', 'description'),
2063         'bool'
2064     ),(
2065         'circ.holds.hold_has_copy_at.block',
2066         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2067         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'If there is an available copy at the requesting library that could fulfill a hold during hold placement time, do not allow the hold to be placed', 'coust', 'description'),
2068         'bool'
2069     );
2070
2071 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2072 VALUES (
2073     'auth.persistent_login_interval',
2074     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2075     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2076     'interval'
2077 );
2078
2079 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2080         'cat.marc_control_number_identifier',
2081         oils_i18n_gettext(
2082             'cat.marc_control_number_identifier', 
2083             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2084             'coust', 
2085             'label'),
2086         oils_i18n_gettext(
2087             'cat.marc_control_number_identifier', 
2088             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2089             'coust', 
2090             'description'),
2091         'string'
2092 );
2093
2094 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2095     VALUES (
2096         'circ.selfcheck.block_checkout_on_copy_status',
2097         oils_i18n_gettext(
2098             'circ.selfcheck.block_checkout_on_copy_status',
2099             'Selfcheck: Block copy checkout status',
2100             'coust',
2101             'label'
2102         ),
2103         oils_i18n_gettext(
2104             'circ.selfcheck.block_checkout_on_copy_status',
2105             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2106             'coust',
2107             'description'
2108         ),
2109         'array'
2110     );
2111
2112 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2113 VALUES (
2114     'serial.prev_issuance_copy_location',
2115     oils_i18n_gettext(
2116         'serial.prev_issuance_copy_location',
2117         'Serials: Previous Issuance Copy Location',
2118         'coust',
2119         'label'
2120     ),
2121     oils_i18n_gettext(
2122         'serial.prev_issuance_copy_location',
2123         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2124         'coust',
2125         'descripton'
2126         ),
2127     'link',
2128     'acpl'
2129 );
2130
2131 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2132 VALUES (
2133     'cat.default_classification_scheme',
2134     oils_i18n_gettext(
2135         'cat.default_classification_scheme',
2136         'Cataloging: Default Classification Scheme',
2137         'coust',
2138         'label'
2139     ),
2140     oils_i18n_gettext(
2141         'cat.default_classification_scheme',
2142         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2143         'coust',
2144         'descripton'
2145         ),
2146     'link',
2147     'acnc'
2148 );
2149
2150 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2151         'opac.org_unit_hiding.depth',
2152         oils_i18n_gettext(
2153             'opac.org_unit_hiding.depth',
2154             'OPAC: Org Unit Hiding Depth', 
2155             'coust', 
2156             'label'),
2157         oils_i18n_gettext(
2158             'opac.org_unit_hiding.depth',
2159             'This will hide certain org units in the public OPAC if the Original Location (url param "ol") for the OPAC inherits this setting.  This setting specifies an org unit depth, that together with the OPAC Original Location determines which section of the Org Hierarchy should be visible in the OPAC.  For example, a stock Evergreen installation will have a 3-tier hierarchy (Consortium/System/Branch), where System has a depth of 1 and Branch has a depth of 2.  If this setting contains a depth of 1 in such an installation, then every library in the System in which the Original Location belongs will be visible, and everything else will be hidden.  A depth of 0 will effectively make every org visible.  The embedded OPAC in the staff client ignores this setting.', 
2160             'coust', 
2161             'description'),
2162         'integer'
2163 );
2164
2165 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2166     VALUES ( 
2167         'circ.holds.clear_shelf.no_capture_holds',
2168         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2169         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2170         'bool'
2171     );
2172
2173 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2174     'circ.booking_reservation.stop_circ',
2175     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2176     'When true, items on booking reserve during the proposed checkout period will not be allowed to circulate unless overridden with the COPY_RESERVED.override permission.',
2177     'bool'
2178 );
2179
2180 ---------------------------------
2181 -- Seed data for usr_setting_type
2182 ----------------------------------
2183
2184 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2185     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2186
2187 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2188     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2189
2190 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2191     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2192
2193 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2194     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2195
2196 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2197     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2198
2199 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2200     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2201
2202 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2203     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2204
2205 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2206     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2207
2208 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2209     VALUES (
2210         'history.circ.retention_age',
2211         TRUE,
2212         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2213         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2214         'interval'
2215     ),(
2216         'history.circ.retention_start',
2217         FALSE,
2218         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2219         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2220         'date'
2221     );
2222
2223 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2224     VALUES (
2225         'history.hold.retention_age',
2226         TRUE,
2227         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2228         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2229         'interval'
2230     ),(
2231         'history.hold.retention_start',
2232         TRUE,
2233         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2234         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2235         'interval'
2236     ),(
2237         'history.hold.retention_count',
2238         TRUE,
2239         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2240         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2241         'integer'
2242     );
2243
2244 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2245     VALUES (
2246         'opac.default_sort',
2247         TRUE,
2248         oils_i18n_gettext(
2249             'opac.default_sort',
2250             'OPAC Default Search Sort',
2251             'cust',
2252             'label'
2253         ),
2254         oils_i18n_gettext(
2255             'opac.default_sort',
2256             'OPAC Default Search Sort',
2257             'cust',
2258             'description'
2259         ),
2260         'string'
2261     );
2262
2263 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2264         'circ.missing_pieces.copy_status',
2265         oils_i18n_gettext(
2266             'circ.missing_pieces.copy_status',
2267             'Circulation: Item Status for Missing Pieces',
2268             'coust',
2269             'label'),
2270         oils_i18n_gettext(
2271             'circ.missing_pieces.copy_status',
2272             'This is the Item Status to use for items that have been marked or scanned as having Missing Pieces.  In the absence of this setting, the Damaged status is used.',
2273             'coust',
2274             'description'),
2275         'link',
2276         'ccs'
2277 );
2278
2279 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2280         'circ.do_not_tally_claims_returned',
2281         oils_i18n_gettext(
2282             'circ.do_not_tally_claims_returned',
2283             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2284             'coust',
2285             'label'),
2286         oils_i18n_gettext(
2287             'circ.do_not_tally_claims_returned',
2288             'In the Patron Display interface, the number of total active circulations for a given patron is presented in the Summary sidebar and underneath the Items Out navigation button.  This setting will prevent Claims Returned circulations from counting toward these tallies.',
2289             'coust',
2290             'description'),
2291         'bool'
2292 );
2293
2294 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2295     VALUES
2296         ('cat.label.font.size',
2297             oils_i18n_gettext('cat.label.font.size',
2298                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2299             oils_i18n_gettext('cat.label.font.size',
2300                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2301             'integer'
2302         )
2303         ,('cat.label.font.family',
2304             oils_i18n_gettext('cat.label.font.family',
2305                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2306             oils_i18n_gettext('cat.label.font.family',
2307                 'Set the preferred font family for spine and pocket labels. You can specify a list of fonts, separated by commas, in order of preference; the system will use the first font it finds with a matching name. For example, "Arial, Helvetica, serif".',
2308                 'coust', 'description'),
2309             'string'
2310         )
2311         ,('cat.spine.line.width',
2312             oils_i18n_gettext('cat.spine.line.width',
2313                 'Cataloging: Spine label line width', 'coust', 'label'),
2314             oils_i18n_gettext('cat.spine.line.width',
2315                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2316                 'coust', 'description'),
2317             'integer'
2318         )
2319         ,('cat.spine.line.height',
2320             oils_i18n_gettext('cat.spine.line.height',
2321                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2322             oils_i18n_gettext('cat.spine.line.height',
2323                 'Set the default maximum number of lines for spine labels.',
2324                 'coust', 'description'),
2325             'integer'
2326         )
2327         ,('cat.spine.line.margin',
2328             oils_i18n_gettext('cat.spine.line.margin',
2329                 'Cataloging: Spine label left margin', 'coust', 'label'),
2330             oils_i18n_gettext('cat.spine.line.margin',
2331                 'Set the left margin for spine labels in number of characters.',
2332                 'coust', 'description'),
2333             'integer'
2334         )
2335 ;
2336
2337 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2338     VALUES
2339         ('cat.label.font.weight',
2340             oils_i18n_gettext('cat.label.font.weight',
2341                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2342             oils_i18n_gettext('cat.label.font.weight',
2343                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2344                 'coust', 'description'),
2345             'string'
2346         )
2347 ;
2348
2349 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2350         'circ.patron_edit.clone.copy_address',
2351         oils_i18n_gettext(
2352             'circ.patron_edit.clone.copy_address',
2353             'Patron Registration: Cloned patrons get address copy',
2354             'coust',
2355             'label'
2356         ),
2357         oils_i18n_gettext(
2358             'circ.patron_edit.clone.copy_address',
2359             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2360             'coust',
2361             'description'
2362         ),
2363         'bool'
2364 );
2365
2366 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2367         'ui.patron.default_ident_type',
2368         oils_i18n_gettext(
2369             'ui.patron.default_ident_type',
2370             'GUI: Default Ident Type for Patron Registration',
2371             'coust',
2372             'label'),
2373         oils_i18n_gettext(
2374             'ui.patron.default_ident_type',
2375             'This is the default Ident Type for new users in the patron editor.',
2376             'coust',
2377             'description'),
2378         'link',
2379         'cit'
2380 );
2381
2382 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2383         'ui.patron.default_country',
2384         oils_i18n_gettext(
2385             'ui.patron.default_country',
2386             'GUI: Default Country for New Addresses in Patron Editor',
2387             'coust',
2388             'label'),
2389         oils_i18n_gettext(
2390             'ui.patron.default_country',
2391             'This is the default Country for new addresses in the patron editor.',
2392             'coust',
2393             'description'),
2394         'string'
2395 );
2396
2397 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2398         'ui.patron.registration.require_address',
2399         oils_i18n_gettext(
2400             'ui.patron.registration.require_address',
2401             'GUI: Require at least one address for Patron Registration',
2402             'coust',
2403             'label'),
2404         oils_i18n_gettext(
2405             'ui.patron.registration.require_address',
2406             'Enforces a requirement for having at least one address for a patron during registration.',
2407             'coust',
2408             'description'),
2409         'bool'
2410 );
2411
2412 INSERT INTO config.org_unit_setting_type (
2413     name, label, description, datatype
2414 ) VALUES
2415     ('credit.processor.payflowpro.enabled',
2416         'Credit card processing: Enable PayflowPro payments',
2417         'This is NOT the same thing as the settings labeled with just "PayPal."',
2418         'bool'
2419     ),
2420     ('credit.processor.payflowpro.login',
2421         'Credit card processing: PayflowPro login/merchant ID',
2422         'Often the same thing as the PayPal manager login',
2423         'string'
2424     ),
2425     ('credit.processor.payflowpro.password',
2426         'Credit card processing: PayflowPro password',
2427         'PayflowPro password',
2428         'string'
2429     ),
2430     ('credit.processor.payflowpro.testmode',
2431         'Credit card processing: PayflowPro test mode',
2432         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2433         'bool'
2434     ),
2435     ('credit.processor.payflowpro.vendor',
2436         'Credit card processing: PayflowPro vendor',
2437         'Often the same thing as the login',
2438         'string'
2439     ),
2440     ('credit.processor.payflowpro.partner',
2441         'Credit card processing: PayflowPro partner',
2442         'Often "PayPal" or "VeriSign", sometimes others',
2443         'string'
2444     );
2445
2446 -- Patch the name of an old selfcheck setting
2447 UPDATE actor.org_unit_setting
2448     SET name = 'circ.selfcheck.alert.popup'
2449     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2450
2451 -- Rename certain existing org_unit settings, if present,
2452 -- and make sure their values are JSON
2453 UPDATE actor.org_unit_setting SET
2454     name = 'circ.holds.default_estimated_wait_interval',
2455     --
2456     -- The value column should be JSON.  The old value should be a number,
2457     -- but it may or may not be quoted.  The following CASE behaves
2458     -- differently depending on whether value is quoted.  It is simplistic,
2459     -- and will be defeated by leading or trailing white space, or various
2460     -- malformations.
2461     --
2462     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2463                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2464                 ELSE '"' || value || ' days"'
2465             END
2466 WHERE name = 'circ.hold_estimate_wait_interval';
2467
2468 -- Create types for existing org unit settings
2469 -- not otherwise accounted for
2470
2471 INSERT INTO config.org_unit_setting_type(
2472  name,
2473  label,
2474  description
2475 )
2476 SELECT DISTINCT
2477         name,
2478         name,
2479         'FIXME'
2480 FROM
2481         actor.org_unit_setting
2482 WHERE
2483         name NOT IN (
2484                 SELECT name
2485                 FROM config.org_unit_setting_type
2486         );
2487
2488 -- Add foreign key to org_unit_setting
2489
2490 ALTER TABLE actor.org_unit_setting
2491         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2492                 DEFERRABLE INITIALLY DEFERRED;
2493
2494 -- Create types for existing user settings
2495 -- not otherwise accounted for
2496
2497 INSERT INTO config.usr_setting_type (
2498         name,
2499         label,
2500         description
2501 )
2502 SELECT DISTINCT
2503         name,
2504         name,
2505         'FIXME'
2506 FROM
2507         actor.usr_setting
2508 WHERE
2509         name NOT IN (
2510                 SELECT name
2511                 FROM config.usr_setting_type
2512         );
2513
2514 -- Add foreign key to user_setting_type
2515
2516 ALTER TABLE actor.usr_setting
2517         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2518                 ON DELETE CASCADE ON UPDATE CASCADE
2519                 DEFERRABLE INITIALLY DEFERRED;
2520
2521 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2522     (1, 'cat.spine.line.margin', 0)
2523     ,(1, 'cat.spine.line.height', 9)
2524     ,(1, 'cat.spine.line.width', 8)
2525     ,(1, 'cat.label.font.family', '"monospace"')
2526     ,(1, 'cat.label.font.size', 10)
2527     ,(1, 'cat.label.font.weight', '"normal"')
2528 ;
2529
2530 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2531 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2532 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2533 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2534
2535 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2536     use JSON::XS;
2537     my $json = shift();
2538     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2539     return $@ ? 0 : 1;
2540 $f$ LANGUAGE PLPERLU;
2541
2542 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2543
2544 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2545     'hold_request.cancel.expire_no_target',
2546     'ahr',
2547     'A hold is cancelled because no copies were found'
2548 );
2549
2550 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2551     'hold_request.cancel.expire_holds_shelf',
2552     'ahr',
2553     'A hold is cancelled because it was on the holds shelf too long'
2554 );
2555
2556 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2557     'hold_request.cancel.staff',
2558     'ahr',
2559     'A hold is cancelled because it was cancelled by staff'
2560 );
2561
2562 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2563     'hold_request.cancel.patron',
2564     'ahr',
2565     'A hold is cancelled by the patron'
2566 );
2567
2568 -- Fix typos in descriptions
2569 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2570 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2571
2572 -- Add a hook for renewals
2573 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2574
2575 INSERT INTO action_trigger.validator (module,description) VALUES ('MaxPassiveDelayAge','Check that the event is not too far past the delay_field time -- requires a max_delay_age interval parameter');
2576
2577 -- Sample Pre-due Notice --
2578
2579 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2580     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2581 $$
2582 [%- USE date -%]
2583 [%- user = target.0.usr -%]
2584 To: [%- params.recipient_email || user.email %]
2585 From: [%- params.sender_email || default_sender %]
2586 Subject: Courtesy Notice
2587
2588 Dear [% user.family_name %], [% user.first_given_name %]
2589 As a reminder, the following items are due in 3 days.
2590
2591 [% FOR circ IN target %]
2592     [%- copy_details = helpers.get_copy_bib_basics(circ.target_copy.id) -%]
2593     Title: [% copy_details.title %]
2594     Author: [% copy_details.author %]
2595     Call Number: [% circ.target_copy.call_number.label %]
2596     Barcode: [% circ.target_copy.barcode %] 
2597     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2598     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2599     Library: [% circ.circ_lib.name %]
2600     Library Phone: [% circ.circ_lib.phone %]
2601 [% END %]
2602
2603 $$);
2604
2605 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2606     (6, 'target_copy.call_number'),
2607     (6, 'target_copy.location'),
2608     (6, 'usr'),
2609     (6, 'circ_lib.billing_address');
2610
2611 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2612     (6, 'max_delay_age', '"1 day"');
2613
2614 -- also add the max delay age to the default overdue notice event def
2615 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2616     (1, 'max_delay_age', '"1 day"');
2617   
2618 INSERT INTO action_trigger.validator (module,description) VALUES ('MinPassiveTargetAge','Check that the target is old enough to be used by this event -- requires a min_target_age interval parameter, and accepts an optional target_age_field to specify what time to use for offsetting');
2619
2620 INSERT INTO action_trigger.reactor (module,description) VALUES ('ApplyPatronPenalty','Applies the configured penalty to a patron.  Required named environment variables are "user", which refers to the user object, and "context_org", which refers to the org_unit object that acts as the focus for the penalty.');
2621
2622 INSERT INTO action_trigger.hook (
2623         key,
2624         core_type,
2625         description,
2626         passive
2627     ) VALUES (
2628         'hold_request.shelf_expires_soon',
2629         'ahr',
2630         'A hold on the shelf will expire there soon.',
2631         TRUE
2632     );
2633
2634 INSERT INTO action_trigger.event_definition (
2635         id,
2636         active,
2637         owner,
2638         name,
2639         hook,
2640         validator,
2641         reactor,
2642         delay,
2643         delay_field,
2644         group_field,
2645         template
2646     ) VALUES (
2647         7,
2648         FALSE,
2649         1,
2650         'Hold Expires from Shelf Soon',
2651         'hold_request.shelf_expires_soon',
2652         'HoldIsAvailable',
2653         'SendEmail',
2654         '- 1 DAY',
2655         'shelf_expire_time',
2656         'usr',
2657 $$
2658 [%- USE date -%]
2659 [%- user = target.0.usr -%]
2660 To: [%- params.recipient_email || user.email %]
2661 From: [%- params.sender_email || default_sender %]
2662 Subject: Hold Available Notification
2663
2664 Dear [% user.family_name %], [% user.first_given_name %]
2665 You requested holds on the following item(s), which are available for
2666 pickup, but these holds will soon expire.
2667
2668 [% FOR hold IN target %]
2669     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2670     Title: [% data.title %]
2671     Author: [% data.author %]
2672     Library: [% hold.pickup_lib.name %]
2673 [% END %]
2674 $$
2675     );
2676
2677 INSERT INTO action_trigger.environment (
2678         event_def,
2679         path
2680     ) VALUES
2681     ( 7, 'current_copy'),
2682     ( 7, 'pickup_lib.billing_address'),
2683     ( 7, 'usr');
2684
2685 INSERT INTO action_trigger.hook (
2686         key,
2687         core_type,
2688         description,
2689         passive
2690     ) VALUES (
2691         'hold_request.long_wait',
2692         'ahr',
2693         'A patron has been waiting on a hold to be fulfilled for a long time.',
2694         TRUE
2695     );
2696
2697 INSERT INTO action_trigger.event_definition (
2698         id,
2699         active,
2700         owner,
2701         name,
2702         hook,
2703         validator,
2704         reactor,
2705         delay,
2706         delay_field,
2707         group_field,
2708         template
2709     ) VALUES (
2710         9,
2711         FALSE,
2712         1,
2713         'Hold waiting for pickup for long time',
2714         'hold_request.long_wait',
2715         'NOOP_True',
2716         'SendEmail',
2717         '6 MONTHS',
2718         'request_time',
2719         'usr',
2720 $$
2721 [%- USE date -%]
2722 [%- user = target.0.usr -%]
2723 To: [%- params.recipient_email || user.email %]
2724 From: [%- params.sender_email || default_sender %]
2725 Subject: Long Wait Hold Notification
2726
2727 Dear [% user.family_name %], [% user.first_given_name %]
2728
2729 You requested hold(s) on the following item(s), but unfortunately
2730 we have not been able to fulfill your request after a considerable
2731 length of time.  If you would still like to receive these items,
2732 no action is required.
2733
2734 [% FOR hold IN target %]
2735     [%- copy_details = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2736     Title: [% copy_details.title %]
2737     Author: [% copy_details.author %]
2738 [% END %]
2739 $$
2740 );
2741
2742 INSERT INTO action_trigger.environment (
2743         event_def,
2744         path
2745     ) VALUES
2746     (9, 'pickup_lib'),
2747     (9, 'usr'),
2748     (9, 'current_copy.call_number');
2749
2750 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2751     VALUES (
2752         'format.selfcheck.checkout',
2753         'circ',
2754         'Formats circ objects for self-checkout receipt',
2755         TRUE
2756     );
2757
2758 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2759     VALUES (
2760         10,
2761         TRUE,
2762         1,
2763         'Self-Checkout Receipt',
2764         'format.selfcheck.checkout',
2765         'NOOP_True',
2766         'ProcessTemplate',
2767         'usr',
2768         'print-on-demand',
2769 $$
2770 [%- USE date -%]
2771 [%- SET user = target.0.usr -%]
2772 [%- SET lib = target.0.circ_lib -%]
2773 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2774 [%- SET hours = lib.hours_of_operation -%]
2775 <div>
2776     <style> li { padding: 8px; margin 5px; }</style>
2777     <div>[% date.format %]</div>
2778     <div>[% lib.name %]</div>
2779     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2780     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2781     <div>[% lib.phone %]</div>
2782     <br/>
2783
2784     [% user.family_name %], [% user.first_given_name %]
2785     <ol>
2786     [% FOR circ IN target %]
2787         [%-
2788             SET idx = loop.count - 1;
2789             SET udata =  user_data.$idx
2790         -%]
2791         <li>
2792             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2793             <div>Barcode: [% circ.target_copy.barcode %]</div>
2794             [% IF udata.renewal_failure %]
2795                 <div style='color:red;'>Renewal Failed</div>
2796             [% ELSE %]
2797                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2798             [% END %]
2799         </li>
2800     [% END %]
2801     </ol>
2802     
2803     <div>
2804         Library Hours
2805         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2806         <div>
2807             Monday 
2808             [% PROCESS format_time time = hours.dow_0_open %] 
2809             [% PROCESS format_time time = hours.dow_0_close %] 
2810         </div>
2811         <div>
2812             Tuesday 
2813             [% PROCESS format_time time = hours.dow_1_open %] 
2814             [% PROCESS format_time time = hours.dow_1_close %] 
2815         </div>
2816         <div>
2817             Wednesday 
2818             [% PROCESS format_time time = hours.dow_2_open %] 
2819             [% PROCESS format_time time = hours.dow_2_close %] 
2820         </div>
2821         <div>
2822             Thursday
2823             [% PROCESS format_time time = hours.dow_3_open %] 
2824             [% PROCESS format_time time = hours.dow_3_close %] 
2825         </div>
2826         <div>
2827             Friday
2828             [% PROCESS format_time time = hours.dow_4_open %] 
2829             [% PROCESS format_time time = hours.dow_4_close %] 
2830         </div>
2831         <div>
2832             Saturday
2833             [% PROCESS format_time time = hours.dow_5_open %] 
2834             [% PROCESS format_time time = hours.dow_5_close %] 
2835         </div>
2836         <div>
2837             Sunday 
2838             [% PROCESS format_time time = hours.dow_6_open %] 
2839             [% PROCESS format_time time = hours.dow_6_close %] 
2840         </div>
2841     </div>
2842 </div>
2843 $$
2844 );
2845
2846 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2847     ( 10, 'target_copy'),
2848     ( 10, 'circ_lib.billing_address'),
2849     ( 10, 'circ_lib.hours_of_operation'),
2850     ( 10, 'usr');
2851
2852 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2853     VALUES (
2854         'format.selfcheck.items_out',
2855         'circ',
2856         'Formats items out for self-checkout receipt',
2857         TRUE
2858     );
2859
2860 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2861     VALUES (
2862         11,
2863         TRUE,
2864         1,
2865         'Self-Checkout Items Out Receipt',
2866         'format.selfcheck.items_out',
2867         'NOOP_True',
2868         'ProcessTemplate',
2869         'usr',
2870         'print-on-demand',
2871 $$
2872 [%- USE date -%]
2873 [%- SET user = target.0.usr -%]
2874 <div>
2875     <style> li { padding: 8px; margin 5px; }</style>
2876     <div>[% date.format %]</div>
2877     <br/>
2878
2879     [% user.family_name %], [% user.first_given_name %]
2880     <ol>
2881     [% FOR circ IN target %]
2882         <li>
2883             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2884             <div>Barcode: [% circ.target_copy.barcode %]</div>
2885             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2886         </li>
2887     [% END %]
2888     </ol>
2889 </div>
2890 $$
2891 );
2892
2893
2894 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2895     ( 11, 'target_copy'),
2896     ( 11, 'circ_lib.billing_address'),
2897     ( 11, 'circ_lib.hours_of_operation'),
2898     ( 11, 'usr');
2899
2900 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2901     VALUES (
2902         'format.selfcheck.holds',
2903         'ahr',
2904         'Formats holds for self-checkout receipt',
2905         TRUE
2906     );
2907
2908 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2909     VALUES (
2910         12,
2911         TRUE,
2912         1,
2913         'Self-Checkout Holds Receipt',
2914         'format.selfcheck.holds',
2915         'NOOP_True',
2916         'ProcessTemplate',
2917         'usr',
2918         'print-on-demand',
2919 $$
2920 [%- USE date -%]
2921 [%- SET user = target.0.usr -%]
2922 <div>
2923     <style> li { padding: 8px; margin 5px; }</style>
2924     <div>[% date.format %]</div>
2925     <br/>
2926
2927     [% user.family_name %], [% user.first_given_name %]
2928     <ol>
2929     [% FOR hold IN target %]
2930         [%-
2931             SET idx = loop.count - 1;
2932             SET udata =  user_data.$idx
2933         -%]
2934         <li>
2935             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2936             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2937             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2938             <div>Status: 
2939                 [%- IF udata.ready -%]
2940                     Ready for pickup
2941                 [% ELSE %]
2942                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2943                 [% END %]
2944             </div>
2945         </li>
2946     [% END %]
2947     </ol>
2948 </div>
2949 $$
2950 );
2951
2952
2953 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2954     ( 12, 'bib_rec.bib_record.simple_record'),
2955     ( 12, 'pickup_lib'),
2956     ( 12, 'usr');
2957
2958 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2959     VALUES (
2960         'format.selfcheck.fines',
2961         'au',
2962         'Formats fines for self-checkout receipt',
2963         TRUE
2964     );
2965
2966 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2967     VALUES (
2968         13,
2969         TRUE,
2970         1,
2971         'Self-Checkout Fines Receipt',
2972         'format.selfcheck.fines',
2973         'NOOP_True',
2974         'ProcessTemplate',
2975         'print-on-demand',
2976 $$
2977 [%- USE date -%]
2978 [%- SET user = target -%]
2979 <div>
2980     <style> li { padding: 8px; margin 5px; }</style>
2981     <div>[% date.format %]</div>
2982     <br/>
2983
2984     [% user.family_name %], [% user.first_given_name %]
2985     <ol>
2986     [% FOR xact IN user.open_billable_transactions_summary %]
2987         <li>
2988             <div>Details: 
2989                 [% IF xact.xact_type == 'circulation' %]
2990                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2991                 [% ELSE %]
2992                     [%- xact.last_billing_type -%]
2993                 [% END %]
2994             </div>
2995             <div>Total Billed: [% xact.total_owed %]</div>
2996             <div>Total Paid: [% xact.total_paid %]</div>
2997             <div>Balance Owed : [% xact.balance_owed %]</div>
2998         </li>
2999     [% END %]
3000     </ol>
3001 </div>
3002 $$
3003 );
3004
3005 INSERT INTO action_trigger.environment ( event_def, path) VALUES
3006     ( 13, 'open_billable_transactions_summary.circulation' );
3007
3008 INSERT INTO action_trigger.reactor (module,description) VALUES
3009 (   'SendFile',
3010     oils_i18n_gettext(
3011         'SendFile',
3012         'Build and transfer a file to a remote server.  Required parameter "remote_host" specifying target server.  Optional parameters: remote_user, remote_password, remote_account, port, type (FTP, SFTP or SCP), and debug.',
3013         'atreact',
3014         'description'
3015     )
3016 );
3017
3018 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
3019     VALUES (
3020         'format.acqli.html',
3021         'jub',
3022         'Formats lineitem worksheet for titles received',
3023         TRUE
3024     );
3025
3026 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3027     VALUES (
3028         14,
3029         TRUE,
3030         1,
3031         'Lineitem Worksheet',
3032         'format.acqli.html',
3033         'NOOP_True',
3034         'ProcessTemplate',
3035         'print-on-demand',
3036 $$
3037 [%- USE date -%]
3038 [%- SET li = target; -%]
3039 <div class="wrapper">
3040     <div class="summary" style='font-size:110%; font-weight:bold;'>
3041
3042         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3043         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3044         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3045         <div class="lineid">Lineitem ID: [% li.id %]</div>
3046
3047         [% IF li.distribution_formulas.size > 0 %]
3048             [% SET forms = [] %]
3049             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3050             <div>Distribution Formulas: [% forms.join(',') %]</div>
3051         [% END %]
3052
3053         [% IF li.lineitem_notes.size > 0 %]
3054             Lineitem Notes:
3055             <ul>
3056                 [%- FOR note IN li.lineitem_notes -%]
3057                     <li>
3058                     [% IF note.alert_text %]
3059                         [% note.alert_text.code -%] 
3060                         [% IF note.value -%]
3061                             : [% note.value %]
3062                         [% END %]
3063                     [% ELSE %]
3064                         [% note.value -%] 
3065                     [% END %]
3066                     </li>
3067                 [% END %]
3068             </ul>
3069         [% END %]
3070     </div>
3071     <br/>
3072     <table>
3073         <thead>
3074             <tr>
3075                 <th>Branch</th>
3076                 <th>Barcode</th>
3077                 <th>Call Number</th>
3078                 <th>Fund</th>
3079                 <th>Recd.</th>
3080                 <th>Notes</th>
3081             </tr>
3082         </thead>
3083         <tbody>
3084         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3085             [% 
3086                 IF copy.eg_copy_id;
3087                     SET copy = copy.eg_copy_id;
3088                     SET cn_label = copy.call_number.label;
3089                 ELSE; 
3090                     SET copy = detail; 
3091                     SET cn_label = detail.cn_label;
3092                 END 
3093             %]
3094             <tr>
3095                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3096                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3097                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3098                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3099                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3100                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3101                 <td style='padding:5px;'>[% detail.note %]</td>
3102             </tr>
3103         [% END %]
3104         </tbody>
3105     </table>
3106 </div>
3107 $$
3108 );
3109
3110
3111 INSERT INTO action_trigger.environment (event_def, path) VALUES
3112     ( 14, 'attributes' ),
3113     ( 14, 'lineitem_details' ),
3114     ( 14, 'lineitem_details.owning_lib' ),
3115     ( 14, 'lineitem_notes' )
3116 ;
3117
3118 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3119         'aur.ordered',
3120         'aur', 
3121         oils_i18n_gettext(
3122             'aur.ordered',
3123             'A patron acquisition request has been marked On-Order.',
3124             'ath',
3125             'description'
3126         ), 
3127         TRUE
3128     ), (
3129         'aur.received', 
3130         'aur', 
3131         oils_i18n_gettext(
3132             'aur.received', 
3133             'A patron acquisition request has been marked Received.',
3134             'ath',
3135             'description'
3136         ),
3137         TRUE
3138     ), (
3139         'aur.cancelled',
3140         'aur',
3141         oils_i18n_gettext(
3142             'aur.cancelled',
3143             'A patron acquisition request has been marked Cancelled.',
3144             'ath',
3145             'description'
3146         ),
3147         TRUE
3148     )
3149 ;
3150
3151 INSERT INTO action_trigger.validator (module,description) VALUES (
3152         'Acq::UserRequestOrdered',
3153         oils_i18n_gettext(
3154             'Acq::UserRequestOrdered',
3155             'Tests to see if the corresponding Line Item has a state of "on-order".',
3156             'atval',
3157             'description'
3158         )
3159     ), (
3160         'Acq::UserRequestReceived',
3161         oils_i18n_gettext(
3162             'Acq::UserRequestReceived',
3163             'Tests to see if the corresponding Line Item has a state of "received".',
3164             'atval',
3165             'description'
3166         )
3167     ), (
3168         'Acq::UserRequestCancelled',
3169         oils_i18n_gettext(
3170             'Acq::UserRequestCancelled',
3171             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3172             'atval',
3173             'description'
3174         )
3175     )
3176 ;
3177
3178
3179 -- The password reset event_definition in v1.6.1 will be moved to #20.  This
3180 -- renumbering requires some juggling:
3181 --
3182 -- 1. Update any child rows to point to #20.  These updates will temporarily
3183 -- violate foreign key constraints, but that's okay as long as we have a
3184 -- #20 before committing.
3185 --
3186 -- 2. Update the id of the password reset event_definition to 20
3187 --
3188 -- This code might fail in some cases, but should work with all stock 1.6.1
3189 -- instances, whether fresh or via upgrade
3190
3191 UPDATE action_trigger.environment
3192 SET event_def = 20
3193 WHERE event_def = (SELECT id FROM action_trigger.event_definition WHERE hook = 'password.reset_request' ORDER BY id ASC LIMIT 1);
3194
3195 UPDATE action_trigger.event
3196 SET event_def = 20
3197 WHERE event_def = (SELECT id FROM action_trigger.event_definition WHERE hook = 'password.reset_request' ORDER BY id ASC LIMIT 1);
3198
3199 UPDATE action_trigger.event_params
3200 SET event_def = 20
3201 WHERE event_def = (SELECT id FROM action_trigger.event_definition WHERE hook = 'password.reset_request' ORDER BY id ASC LIMIT 1);
3202
3203 UPDATE action_trigger.event_definition
3204 SET id = 20
3205 WHERE id = (SELECT id FROM action_trigger.event_definition WHERE hook = 'password.reset_request' ORDER BY id ASC LIMIT 1);
3206
3207
3208 -- Let's also take the opportunity to rebuild the trigger
3209 -- if it got mangled somehow
3210 INSERT INTO action_trigger.hook (key,core_type,description)
3211  SELECT  'password.reset_request','aupr','Patron has requested a self-serve password reset'
3212    WHERE (SELECT COUNT(*) FROM action_trigger.hook WHERE key = 'password.reset_request') = 0;
3213
3214 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template)
3215     SELECT 20, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
3216 $$
3217 [%- USE date -%]
3218 [%- user = target.usr -%]
3219 To: [%- params.recipient_email || user.email %]
3220 From: [%- params.sender_email || user.home_ou.email || default_sender %]
3221 Subject: [% user.home_ou.name %]: library account password reset request
3222
3223 You have received this message because you, or somebody else, requested a reset
3224 of your library system password. If you did not request a reset of your library
3225 system password, just ignore this message and your current password will
3226 continue to work.
3227
3228 If you did request a reset of your library system password, please perform
3229 the following steps to continue the process of resetting your password:
3230
3231 1. Open the following link in a web browser: https://[% params.hostname %]/opac/password/[% params.locale || 'en-US' %]/[% target.uuid %]
3232 The browser displays a password reset form.
3233
3234 2. Enter your new password in the password reset form in the browser. You must
3235 enter the password twice to ensure that you do not make a mistake. If the
3236 passwords match, you will then be able to log in to your library system account
3237 with the new password.
3238
3239 $$
3240    WHERE (SELECT COUNT(*) FROM action_trigger.event_definition WHERE id = 20) = 0;
3241
3242 INSERT INTO action_trigger.environment ( event_def, path)
3243     SELECT 20, 'usr'
3244                 WHERE (SELECT COUNT(*) FROM action_trigger.environment WHERE event_def = 20 AND path = 'usr') = 0;
3245
3246 INSERT INTO action_trigger.environment ( event_def, path)
3247     SELECT 20, 'usr.home_ou'
3248                 WHERE (SELECT COUNT(*) FROM action_trigger.environment WHERE event_def = 20 AND path = 'usr.home_ou') = 0;
3249
3250 INSERT INTO action_trigger.event_definition (
3251         id,
3252         active,
3253         owner,
3254         name,
3255         hook,
3256         validator,
3257         reactor,
3258         template
3259     ) VALUES (
3260         15,
3261         FALSE,
3262         1,
3263         'Email Notice: Patron Acquisition Request marked On-Order.',
3264         'aur.ordered',
3265         'Acq::UserRequestOrdered',
3266         'SendEmail',
3267 $$
3268 [%- USE date -%]
3269 [%- SET li = target.lineitem; -%]
3270 [%- SET user = target.usr -%]
3271 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3272 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3273 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3274 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3275 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3276 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3277
3278 To: [%- params.recipient_email || user.email %]
3279 From: [%- params.sender_email || default_sender %]
3280 Subject: Acquisition Request Notification
3281
3282 Dear [% user.family_name %], [% user.first_given_name %]
3283 Our records indicate the following acquisition request has been placed on order.
3284
3285 Title: [% title %]
3286 [% IF author %]Author: [% author %][% END %]
3287 [% IF edition %]Edition: [% edition %][% END %]
3288 [% IF isbn %]ISBN: [% isbn %][% END %]
3289 [% IF publisher %]Publisher: [% publisher %][% END %]
3290 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3291 Lineitem ID: [% li.id %]
3292 $$
3293     ), (
3294         16,
3295         FALSE,
3296         1,
3297         'Email Notice: Patron Acquisition Request marked Received.',
3298         'aur.received',
3299         'Acq::UserRequestReceived',
3300         'SendEmail',
3301 $$
3302 [%- USE date -%]
3303 [%- SET li = target.lineitem; -%]
3304 [%- SET user = target.usr -%]
3305 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3306 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3307 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3308 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3309 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3310 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3311
3312 To: [%- params.recipient_email || user.email %]
3313 From: [%- params.sender_email || default_sender %]
3314 Subject: Acquisition Request Notification
3315
3316 Dear [% user.family_name %], [% user.first_given_name %]
3317 Our records indicate the materials for the following acquisition request have been received.
3318
3319 Title: [% title %]
3320 [% IF author %]Author: [% author %][% END %]
3321 [% IF edition %]Edition: [% edition %][% END %]
3322 [% IF isbn %]ISBN: [% isbn %][% END %]
3323 [% IF publisher %]Publisher: [% publisher %][% END %]
3324 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3325 Lineitem ID: [% li.id %]
3326 $$
3327     ), (
3328         17,
3329         FALSE,
3330         1,
3331         'Email Notice: Patron Acquisition Request marked Cancelled.',
3332         'aur.cancelled',
3333         'Acq::UserRequestCancelled',
3334         'SendEmail',
3335 $$
3336 [%- USE date -%]
3337 [%- SET li = target.lineitem; -%]
3338 [%- SET user = target.usr -%]
3339 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3340 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3341 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3342 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3343 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3344 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3345
3346 To: [%- params.recipient_email || user.email %]
3347 From: [%- params.sender_email || default_sender %]
3348 Subject: Acquisition Request Notification
3349
3350 Dear [% user.family_name %], [% user.first_given_name %]
3351 Our records indicate the following acquisition request has been cancelled.
3352
3353 Title: [% title %]
3354 [% IF author %]Author: [% author %][% END %]
3355 [% IF edition %]Edition: [% edition %][% END %]
3356 [% IF isbn %]ISBN: [% isbn %][% END %]
3357 [% IF publisher %]Publisher: [% publisher %][% END %]
3358 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3359 Lineitem ID: [% li.id %]
3360 $$
3361     );
3362
3363 INSERT INTO action_trigger.environment (
3364         event_def,
3365         path
3366     ) VALUES 
3367         ( 15, 'lineitem' ),
3368         ( 15, 'lineitem.attributes' ),
3369         ( 15, 'usr' ),
3370
3371         ( 16, 'lineitem' ),
3372         ( 16, 'lineitem.attributes' ),
3373         ( 16, 'usr' ),
3374
3375         ( 17, 'lineitem' ),
3376         ( 17, 'lineitem.attributes' ),
3377         ( 17, 'usr' )
3378     ;
3379
3380 INSERT INTO action_trigger.event_definition
3381 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3382 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3383 $$
3384 [%- USE date -%]
3385 [%# start JEDI document 
3386   # Vendor specific kludges:
3387   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3388   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3389   # BRODART - vendcode goes to FTX segment (lineitem level)
3390 -%]
3391 [%- 
3392 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3393     xtra_ftx = target.provider.edi_default.vendcode;
3394 END;
3395 -%]
3396 [%- BLOCK big_block -%]
3397 {
3398    "recipient":"[% target.provider.san %]",
3399    "sender":"[% target.ordering_agency.mailing_address.san %]",
3400    "body": [{
3401      "ORDERS":[ "order", {
3402         "po_number":[% target.id %],
3403         "date":"[% date.format(date.now, '%Y%m%d') %]",
3404         "buyer":[
3405             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3406                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3407             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3408                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3409                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3410             [%- ELSE -%]
3411                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3412             [%- END -%]
3413         ],
3414         "vendor":[
3415             [%- # target.provider.name (target.provider.id) -%]
3416             "[% target.provider.san %]",
3417             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3418         ],
3419         "currency":"[% target.provider.currency_type %]",
3420                 
3421         "items":[
3422         [%- FOR li IN target.lineitems %]
3423         {
3424             "line_index":"[% li.id %]",
3425             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3426             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3427                 [% IF isbn.length == 13 -%]
3428                 {"id-qualifier":"EN","id":"[% isbn %]"},
3429                 [% ELSE -%]
3430                 {"id-qualifier":"IB","id":"[% isbn %]"},
3431                 [%- END %]
3432             [% END %]
3433                 {"id-qualifier":"IN","id":"[% li.id %]"}
3434             ],
3435             "price":[% li.estimated_unit_price || '0.00' %],
3436             "desc":[
3437                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3438                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3439                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3440                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3441             ],
3442             [%- ftx_vals = []; 
3443                 FOR note IN li.lineitem_notes; 
3444                     NEXT UNLESS note.vendor_public == 't'; 
3445                     ftx_vals.push(note.value); 
3446                 END; 
3447                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3448                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3449             -%]
3450
3451             "free-text":[ 
3452                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3453             ],            
3454             "quantity":[% li.lineitem_details.size %]
3455         }[% UNLESS loop.last %],[% END %]
3456         [%-# TODO: lineitem details (later) -%]
3457         [% END %]
3458         ],
3459         "line_items":[% target.lineitems.size %]
3460      }]  [%# close ORDERS array %]
3461    }]    [%# close  body  array %]
3462 }
3463 [% END %]
3464 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3465
3466 $$);
3467
3468 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3469   (23, 'lineitems.attributes'), 
3470   (23, 'lineitems.lineitem_details'), 
3471   (23, 'lineitems.lineitem_notes'), 
3472   (23, 'ordering_agency.mailing_address'), 
3473   (23, 'provider');
3474
3475 UPDATE action_trigger.event_definition SET template = 
3476 $$
3477 [%- USE date -%]
3478 [%-
3479     # find a lineitem attribute by name and optional type
3480     BLOCK get_li_attr;
3481         FOR attr IN li.attributes;
3482             IF attr.attr_name == attr_name;
3483                 IF !attr_type OR attr_type == attr.attr_type;
3484                     attr.attr_value;
3485                     LAST;
3486                 END;
3487             END;
3488         END;
3489     END
3490 -%]
3491
3492 <h2>Purchase Order [% target.id %]</h2>
3493 <br/>
3494 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3495 <br/>
3496
3497 <style>
3498     table td { padding:5px; border:1px solid #aaa;}
3499     table { width:95%; border-collapse:collapse; }
3500     #vendor-notes { padding:5px; border:1px solid #aaa; }
3501 </style>
3502 <table id='vendor-table'>
3503   <tr>
3504     <td valign='top'>Vendor</td>
3505     <td>
3506       <div>[% target.provider.name %]</div>
3507       <div>[% target.provider.addresses.0.street1 %]</div>
3508       <div>[% target.provider.addresses.0.street2 %]</div>
3509       <div>[% target.provider.addresses.0.city %]</div>
3510       <div>[% target.provider.addresses.0.state %]</div>
3511       <div>[% target.provider.addresses.0.country %]</div>
3512       <div>[% target.provider.addresses.0.post_code %]</div>
3513     </td>
3514     <td valign='top'>Ship to / Bill to</td>
3515     <td>
3516       <div>[% target.ordering_agency.name %]</div>
3517       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3518       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3519       <div>[% target.ordering_agency.billing_address.city %]</div>
3520       <div>[% target.ordering_agency.billing_address.state %]</div>
3521       <div>[% target.ordering_agency.billing_address.country %]</div>
3522       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3523     </td>
3524   </tr>
3525 </table>
3526
3527 <br/><br/>
3528 <fieldset id='vendor-notes'>
3529     <legend>Notes to the Vendor</legend>
3530     <ul>
3531     [% FOR note IN target.notes %]
3532         [% IF note.vendor_public == 't' %]
3533             <li>[% note.value %]</li>
3534         [% END %]
3535     [% END %]
3536     </ul>
3537 </fieldset>
3538 <br/><br/>
3539
3540 <table>
3541   <thead>
3542     <tr>
3543       <th>PO#</th>
3544       <th>ISBN or Item #</th>
3545       <th>Title</th>
3546       <th>Quantity</th>
3547       <th>Unit Price</th>
3548       <th>Line Total</th>
3549       <th>Notes</th>
3550     </tr>
3551   </thead>
3552   <tbody>
3553
3554   [% subtotal = 0 %]
3555   [% FOR li IN target.lineitems %]
3556
3557   <tr>
3558     [% count = li.lineitem_details.size %]
3559     [% price = li.estimated_unit_price %]
3560     [% litotal = (price * count) %]
3561     [% subtotal = subtotal + litotal %]
3562     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3563     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3564
3565     <td>[% target.id %]</td>
3566     <td>[% isbn || ident %]</td>
3567     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3568     <td>[% count %]</td>
3569     <td>[% price %]</td>
3570     <td>[% litotal %]</td>
3571     <td>
3572         <ul>
3573         [% FOR note IN li.lineitem_notes %]
3574             [% IF note.vendor_public == 't' %]
3575                 <li>[% note.value %]</li>
3576             [% END %]
3577         [% END %]
3578         </ul>
3579     </td>
3580   </tr>
3581   [% END %]
3582   <tr>
3583     <td/><td/><td/><td/>
3584     <td>Subtotal</td>
3585     <td>[% subtotal %]</td>
3586   </tr>
3587   </tbody>
3588 </table>
3589
3590 <br/>
3591
3592 Total Line Item Count: [% target.lineitems.size %]
3593 $$
3594 WHERE id = 4;
3595
3596 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3597     (4, 'lineitems.lineitem_notes'),
3598     (4, 'notes');
3599
3600 INSERT INTO action_trigger.environment (event_def, path) VALUES
3601     ( 14, 'lineitem_notes.alert_text' ),
3602     ( 14, 'distribution_formulas.formula' ),
3603     ( 14, 'lineitem_details.fund' ),
3604     ( 14, 'lineitem_details.eg_copy_id' ),
3605     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3606 ;
3607
3608 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3609         'aur.created',
3610         'aur',
3611         oils_i18n_gettext(
3612             'aur.created',
3613             'A patron has made an acquisitions request.',
3614             'ath',
3615             'description'
3616         ),
3617         TRUE
3618     ), (
3619         'aur.rejected',
3620         'aur',
3621         oils_i18n_gettext(
3622             'aur.rejected',
3623             'A patron acquisition request has been rejected.',
3624             'ath',
3625             'description'
3626         ),
3627         TRUE
3628     )
3629 ;
3630
3631 INSERT INTO action_trigger.event_definition (
3632         id,
3633         active,
3634         owner,
3635         name,
3636         hook,
3637         validator,
3638         reactor,
3639         template
3640     ) VALUES (
3641         18,
3642         FALSE,
3643         1,
3644         'Email Notice: Acquisition Request created.',
3645         'aur.created',
3646         'NOOP_True',
3647         'SendEmail',
3648 $$
3649 [%- USE date -%]
3650 [%- SET user = target.usr -%]
3651 [%- SET title = target.title -%]
3652 [%- SET author = target.author -%]
3653 [%- SET isxn = target.isxn -%]
3654 [%- SET publisher = target.publisher -%]
3655 [%- SET pubdate = target.pubdate -%]
3656
3657 To: [%- params.recipient_email || user.email %]
3658 From: [%- params.sender_email || default_sender %]
3659 Subject: Acquisition Request Notification
3660
3661 Dear [% user.family_name %], [% user.first_given_name %]
3662 Our records indicate that you have made the following acquisition request:
3663
3664 Title: [% title %]
3665 [% IF author %]Author: [% author %][% END %]
3666 [% IF edition %]Edition: [% edition %][% END %]
3667 [% IF isbn %]ISXN: [% isxn %][% END %]
3668 [% IF publisher %]Publisher: [% publisher %][% END %]
3669 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3670 $$
3671     ), (
3672         19,
3673         FALSE,
3674         1,
3675         'Email Notice: Acquisition Request Rejected.',
3676         'aur.rejected',
3677         'NOOP_True',
3678         'SendEmail',
3679 $$
3680 [%- USE date -%]
3681 [%- SET user = target.usr -%]
3682 [%- SET title = target.title -%]
3683 [%- SET author = target.author -%]
3684 [%- SET isxn = target.isxn -%]
3685 [%- SET publisher = target.publisher -%]
3686 [%- SET pubdate = target.pubdate -%]
3687 [%- SET cancel_reason = target.cancel_reason.description -%]
3688
3689 To: [%- params.recipient_email || user.email %]
3690 From: [%- params.sender_email || default_sender %]
3691 Subject: Acquisition Request Notification
3692
3693 Dear [% user.family_name %], [% user.first_given_name %]
3694 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3695
3696 Title: [% title %]
3697 [% IF author %]Author: [% author %][% END %]
3698 [% IF edition %]Edition: [% edition %][% END %]
3699 [% IF isbn %]ISBN: [% isbn %][% END %]
3700 [% IF publisher %]Publisher: [% publisher %][% END %]
3701 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3702 $$
3703     );
3704
3705 INSERT INTO action_trigger.environment (
3706         event_def,
3707         path
3708     ) VALUES 
3709         ( 18, 'usr' ),
3710         ( 19, 'usr' ),
3711         ( 19, 'cancel_reason' )
3712     ;
3713
3714 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3715     VALUES (
3716         'format.acqcle.html',
3717         'acqcle',
3718         'Formats claim events into a voucher',
3719         TRUE
3720     );
3721
3722 INSERT INTO action_trigger.event_definition (
3723         id, active, owner, name, hook, group_field,
3724         validator, reactor, granularity, template
3725     ) VALUES (
3726         21,
3727         TRUE,
3728         1,
3729         'Claim Voucher',
3730         'format.acqcle.html',
3731         'claim',
3732         'NOOP_True',
3733         'ProcessTemplate',
3734         'print-on-demand',
3735 $$
3736 [%- USE date -%]
3737 [%- SET claim = target.0.claim -%]
3738 <!-- This will need refined/prettified. -->
3739 <div class="acq-claim-voucher">
3740     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3741     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3742     <ul>
3743         [% FOR event IN target %]
3744         <li>
3745             Event type: [% event.type.code %]
3746             [% IF event.type.library_initiated %](Library initiated)[% END %]
3747             <br />
3748             Event date: [% event.event_date %]<br />
3749             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3750             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3751             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3752             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3753             [% event.claim.lineitem_detail.fund.code %]
3754             ([% event.claim.lineitem_detail.fund.year %])
3755         </li>
3756         [% END %]
3757     </ul>
3758 </div>
3759 $$
3760 );
3761
3762 INSERT INTO action_trigger.environment (event_def, path) VALUES
3763     (21, 'claim'),
3764     (21, 'claim.type'),
3765     (21, 'claim.lineitem_detail'),
3766     (21, 'claim.lineitem_detail.fund'),
3767     (21, 'claim.lineitem_detail.lineitem.attributes'),
3768     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3769     (21, 'creator'),
3770     (21, 'type')
3771 ;
3772
3773 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3774     VALUES (
3775         'format.acqinv.html',
3776         'acqinv',
3777         'Formats invoices into a voucher',
3778         TRUE
3779     );
3780
3781 INSERT INTO action_trigger.event_definition (
3782         id, active, owner, name, hook,
3783         validator, reactor, granularity, template
3784     ) VALUES (
3785         22,
3786         TRUE,
3787         1,
3788         'Invoice',
3789         'format.acqinv.html',
3790         'NOOP_True',
3791         'ProcessTemplate',
3792         'print-on-demand',
3793 $$
3794 [% FILTER collapse %]
3795 [%- SET invoice = target -%]
3796 <!-- This lacks totals, info about funds (for invoice entries,
3797     funds are per-LID!), and general refinement -->
3798 <div class="acq-invoice-voucher">
3799     <h1>Invoice</h1>
3800     <div>
3801         <strong>No.</strong> [% invoice.inv_ident %]
3802         [% IF invoice.inv_type %]
3803             / <strong>Type:</strong>[% invoice.inv_type %]
3804         [% END %]
3805     </div>
3806     <div>
3807         <dl>
3808             [% BLOCK ent_with_address %]
3809             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3810             <dd>
3811                 [% IF ent.addresses.0 %]
3812                     [% SET addr = ent.addresses.0 %]
3813                     [% addr.street1 %]<br />
3814                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3815                     [% addr.city %],
3816                     [% IF addr.county %] [% addr.county %], [% END %]
3817                     [% IF addr.state %] [% addr.state %] [% END %]
3818                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3819                     [% IF addr.country %] [% addr.country %] [% END %]
3820                 [% END %]
3821                 <p>
3822                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3823                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3824                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3825                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3826                 </p>
3827             </dd>
3828             [% END %]
3829             [% INCLUDE ent_with_address
3830                 ent = invoice.provider
3831                 ent_label = "Provider" %]
3832             [% INCLUDE ent_with_address
3833                 ent = invoice.shipper
3834                 ent_label = "Shipper" %]
3835             <dt>Receiver</dt>
3836             <dd>
3837                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3838             </dd>
3839             <dt>Received</dt>
3840             <dd>
3841                 [% helpers.format_date(invoice.recv_date) %] by
3842                 [% invoice.recv_method %]
3843             </dd>
3844             [% IF invoice.note %]
3845                 <dt>Note</dt>
3846                 <dd>
3847                     [% invoice.note %]
3848                 </dd>
3849             [% END %]
3850         </dl>
3851     </div>
3852     <ul>
3853         [% FOR entry IN invoice.entries %]
3854             <li>
3855                 [% IF entry.lineitem %]
3856                     Title: [% helpers.get_li_attr(
3857                         "title", "", entry.lineitem.attributes
3858                     ) %]<br />
3859                     Author: [% helpers.get_li_attr(
3860                         "author", "", entry.lineitem.attributes
3861                     ) %]
3862                 [% END %]
3863                 [% IF entry.purchase_order %]
3864                     (PO: [% entry.purchase_order.name %])
3865                 [% END %]<br />
3866                 Invoice item count: [% entry.inv_item_count %]
3867                 [% IF entry.phys_item_count %]
3868                     / Physical item count: [% entry.phys_item_count %]
3869                 [% END %]
3870                 <br />
3871                 [% IF entry.cost_billed %]
3872                     Cost billed: [% entry.cost_billed %]
3873                     [% IF entry.billed_per_item %](per item)[% END %]
3874                     <br />
3875                 [% END %]
3876                 [% IF entry.actual_cost %]
3877                     Actual cost: [% entry.actual_cost %]<br />
3878                 [% END %]
3879                 [% IF entry.amount_paid %]
3880                     Amount paid: [% entry.amount_paid %]<br />
3881                 [% END %]
3882                 [% IF entry.note %]Note: [% entry.note %][% END %]
3883             </li>
3884         [% END %]
3885         [% FOR item IN invoice.items %]
3886             <li>
3887                 [% IF item.inv_item_type %]
3888                     Item Type: [% item.inv_item_type %]<br />
3889                 [% END %]
3890                 [% IF item.title %]Title/Description:
3891                     [% item.title %]<br />
3892                 [% END %]
3893                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3894                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3895                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3896                 [% IF item.cost_billed %]
3897                     Cost billed: [% item.cost_billed %]<br />
3898                 [% END %]
3899                 [% IF item.actual_cost %]
3900                     Actual cost: [% item.actual_cost %]<br />
3901                 [% END %]
3902                 [% IF item.amount_paid %]
3903                     Amount paid: [% item.amount_paid %]<br />
3904                 [% END %]
3905             </li>
3906         [% END %]
3907     </ul>
3908 </div>
3909 [% END %]
3910 $$
3911 );
3912
3913 UPDATE action_trigger.event_definition SET template = $$[% FILTER collapse %]
3914 [%- SET invoice = target -%]
3915 <!-- This lacks general refinement -->
3916 <div class="acq-invoice-voucher">
3917     <h1>Invoice</h1>
3918     <div>
3919         <strong>No.</strong> [% invoice.inv_ident %]
3920         [% IF invoice.inv_type %]
3921             / <strong>Type:</strong>[% invoice.inv_type %]
3922         [% END %]
3923     </div>
3924     <div>
3925         <dl>
3926             [% BLOCK ent_with_address %]
3927             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3928             <dd>
3929                 [% IF ent.addresses.0 %]
3930                     [% SET addr = ent.addresses.0 %]
3931                     [% addr.street1 %]<br />
3932                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3933                     [% addr.city %],
3934                     [% IF addr.county %] [% addr.county %], [% END %]
3935                     [% IF addr.state %] [% addr.state %] [% END %]
3936                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3937                     [% IF addr.country %] [% addr.country %] [% END %]
3938                 [% END %]
3939                 <p>
3940                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3941                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3942                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3943                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3944                 </p>
3945             </dd>
3946             [% END %]
3947             [% INCLUDE ent_with_address
3948                 ent = invoice.provider
3949                 ent_label = "Provider" %]
3950             [% INCLUDE ent_with_address
3951                 ent = invoice.shipper
3952                 ent_label = "Shipper" %]
3953             <dt>Receiver</dt>
3954             <dd>
3955                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3956             </dd>
3957             <dt>Received</dt>
3958             <dd>
3959                 [% helpers.format_date(invoice.recv_date) %] by
3960                 [% invoice.recv_method %]
3961             </dd>
3962             [% IF invoice.note %]
3963                 <dt>Note</dt>
3964                 <dd>
3965                     [% invoice.note %]
3966                 </dd>
3967             [% END %]
3968         </dl>
3969     </div>
3970     <ul>
3971         [% FOR entry IN invoice.entries %]
3972             <li>
3973                 [% IF entry.lineitem %]
3974                     Title: [% helpers.get_li_attr(
3975                         "title", "", entry.lineitem.attributes
3976                     ) %]<br />
3977                     Author: [% helpers.get_li_attr(
3978                         "author", "", entry.lineitem.attributes
3979                     ) %]
3980                 [% END %]
3981                 [% IF entry.purchase_order %]
3982                     (PO: [% entry.purchase_order.name %])
3983                 [% END %]<br />
3984                 Invoice item count: [% entry.inv_item_count %]
3985                 [% IF entry.phys_item_count %]
3986                     / Physical item count: [% entry.phys_item_count %]
3987                 [% END %]
3988                 <br />
3989                 [% IF entry.cost_billed %]
3990                     Cost billed: [% entry.cost_billed %]
3991                     [% IF entry.billed_per_item %](per item)[% END %]
3992                     <br />
3993                 [% END %]
3994                 [% IF entry.actual_cost %]
3995                     Actual cost: [% entry.actual_cost %]<br />
3996                 [% END %]
3997                 [% IF entry.amount_paid %]
3998                     Amount paid: [% entry.amount_paid %]<br />
3999                 [% END %]
4000                 [% IF entry.note %]Note: [% entry.note %][% END %]
4001             </li>
4002         [% END %]
4003         [% FOR item IN invoice.items %]
4004             <li>
4005                 [% IF item.inv_item_type %]
4006                     Item Type: [% item.inv_item_type %]<br />
4007                 [% END %]
4008                 [% IF item.title %]Title/Description:
4009                     [% item.title %]<br />
4010                 [% END %]
4011                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
4012                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
4013                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
4014                 [% IF item.cost_billed %]
4015                     Cost billed: [% item.cost_billed %]<br />
4016                 [% END %]
4017                 [% IF item.actual_cost %]
4018                     Actual cost: [% item.actual_cost %]<br />
4019                 [% END %]
4020                 [% IF item.amount_paid %]
4021                     Amount paid: [% item.amount_paid %]<br />
4022                 [% END %]
4023             </li>
4024         [% END %]
4025     </ul>
4026     <div>
4027         Amounts spent per fund:
4028         <table>
4029         [% FOR blob IN user_data %]
4030             <tr>
4031                 <th style="text-align: left;">[% blob.fund.code %] ([% blob.fund.year %]):</th>
4032                 <td>$[% blob.total %]</td>
4033             </tr>
4034         [% END %]
4035         </table>
4036     </div>
4037 </div>
4038 [% END %]$$ WHERE id = 22;
4039 INSERT INTO action_trigger.environment (event_def, path) VALUES
4040     (22, 'provider'),
4041     (22, 'provider.addresses'),
4042     (22, 'shipper'),
4043     (22, 'shipper.addresses'),
4044     (22, 'receiver'),
4045     (22, 'entries'),
4046     (22, 'entries.purchase_order'),
4047     (22, 'entries.lineitem'),
4048     (22, 'entries.lineitem.attributes'),
4049     (22, 'items')
4050 ;
4051
4052 INSERT INTO action_trigger.environment (event_def, path) VALUES 
4053   (23, 'provider.edi_default');
4054
4055 INSERT INTO action_trigger.validator (module, description) 
4056     VALUES (
4057         'Acq::PurchaseOrderEDIRequired',
4058         oils_i18n_gettext(
4059             'Acq::PurchaseOrderEDIRequired',
4060             'Purchase order is delivered via EDI',
4061             'atval',
4062             'description'
4063         )
4064     );
4065
4066 INSERT INTO action_trigger.reactor (module, description)
4067     VALUES (
4068         'GeneratePurchaseOrderJEDI',
4069         oils_i18n_gettext(
4070             'GeneratePurchaseOrderJEDI',
4071             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
4072             'atreact',
4073             'description'
4074         )
4075     );
4076
4077 UPDATE action_trigger.hook 
4078     SET 
4079         key = 'acqpo.activated', 
4080         passive = FALSE,
4081         description = oils_i18n_gettext(
4082             'acqpo.activated',
4083             'Purchase order was activated',
4084             'ath',
4085             'description'
4086         )
4087     WHERE key = 'format.po.jedi';
4088
4089 -- We just changed a key in action_trigger.hook.  Now redirect any
4090 -- child rows to point to the new key.  (There probably aren't any;
4091 -- this is just a precaution against possible local modifications.)
4092
4093 UPDATE action_trigger.event_definition
4094 SET hook = 'acqpo.activated'
4095 WHERE hook = 'format.po.jedi';
4096
4097 INSERT INTO action_trigger.reactor (module, description) VALUES (
4098     'AstCall', 'Possibly place a phone call with Asterisk'
4099 );
4100
4101 INSERT INTO
4102     action_trigger.event_definition (
4103         id, active, owner, name, hook, validator, reactor,
4104         cleanup_success, cleanup_failure, delay, delay_field, group_field,
4105         max_delay, granularity, usr_field, opt_in_setting, template
4106     ) VALUES (
4107         24,
4108         FALSE,
4109         1,
4110         'Telephone Overdue Notice',
4111         'checkout.due', 'NOOP_True', 'AstCall',
4112         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
4113         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
4114         $$
4115 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
4116 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
4117 Channel: [% channel_prefix %]/[% country %][% phone %]
4118 Context: overdue-test
4119 MaxRetries: 1
4120 RetryTime: 60
4121 WaitTime: 30
4122 Extension: 10
4123 Archive: 1
4124 Set: eg_user_id=[% target.0.usr.id %]
4125 Set: items=[% target.size %]
4126 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
4127 $$
4128     );
4129
4130 INSERT INTO
4131     action_trigger.environment (id, event_def, path)
4132     VALUES
4133         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
4134         (DEFAULT, 24, 'usr')
4135     ;
4136
4137 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4138         'circ.format.history.email',
4139         'circ', 
4140         oils_i18n_gettext(
4141             'circ.format.history.email',
4142             'An email has been requested for a circ history.',
4143             'ath',
4144             'description'
4145         ), 
4146         FALSE
4147     )
4148     ,(
4149         'circ.format.history.print',
4150         'circ', 
4151         oils_i18n_gettext(
4152             'circ.format.history.print',
4153             'A circ history needs to be formatted for printing.',
4154             'ath',
4155             'description'
4156         ), 
4157         FALSE
4158     )
4159     ,(
4160         'ahr.format.history.email',
4161         'ahr', 
4162         oils_i18n_gettext(
4163             'ahr.format.history.email',
4164             'An email has been requested for a hold request history.',
4165             'ath',
4166             'description'
4167         ), 
4168         FALSE
4169     )
4170     ,(
4171         'ahr.format.history.print',
4172         'ahr', 
4173         oils_i18n_gettext(
4174             'ahr.format.history.print',
4175             'A hold request history needs to be formatted for printing.',
4176             'ath',
4177             'description'
4178         ), 
4179         FALSE
4180     )
4181
4182 ;
4183
4184 INSERT INTO action_trigger.event_definition (
4185         id,
4186         active,
4187         owner,
4188         name,
4189         hook,
4190         validator,
4191         reactor,
4192         group_field,
4193         granularity,
4194         template
4195     ) VALUES (
4196         25,
4197         TRUE,
4198         1,
4199         'circ.history.email',
4200         'circ.format.history.email',
4201         'NOOP_True',
4202         'SendEmail',
4203         'usr',
4204         NULL,
4205 $$
4206 [%- USE date -%]
4207 [%- SET user = target.0.usr -%]
4208 To: [%- params.recipient_email || user.email %]
4209 From: [%- params.sender_email || default_sender %]
4210 Subject: Circulation History
4211
4212     [% FOR circ IN target %]
4213             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4214             Barcode: [% circ.target_copy.barcode %]
4215             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4216             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4217             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4218     [% END %]
4219 $$
4220     )
4221     ,(
4222         26,
4223         TRUE,
4224         1,
4225         'circ.history.print',
4226         'circ.format.history.print',
4227         'NOOP_True',
4228         'ProcessTemplate',
4229         'usr',
4230         'print-on-demand',
4231 $$
4232 [%- USE date -%]
4233 <div>
4234     <style> li { padding: 8px; margin 5px; }</style>
4235     <div>[% date.format %]</div>
4236     <br/>
4237
4238     [% user.family_name %], [% user.first_given_name %]
4239     <ol>
4240     [% FOR circ IN target %]
4241         <li>
4242             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4243             <div>Barcode: [% circ.target_copy.barcode %]</div>
4244             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4245             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4246             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4247         </li>
4248     [% END %]
4249     </ol>
4250 </div>
4251 $$
4252     )
4253     ,(
4254         27,
4255         TRUE,
4256         1,
4257         'ahr.history.email',
4258         'ahr.format.history.email',
4259         'NOOP_True',
4260         'SendEmail',
4261         'usr',
4262         NULL,
4263 $$
4264 [%- USE date -%]
4265 [%- SET user = target.0.usr -%]
4266 To: [%- params.recipient_email || user.email %]
4267 From: [%- params.sender_email || default_sender %]
4268 Subject: Hold Request History
4269
4270     [% FOR hold IN target %]
4271             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4272             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4273             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4274     [% END %]
4275 $$
4276     )
4277     ,(
4278         28,
4279         TRUE,
4280         1,
4281         'ahr.history.print',
4282         'ahr.format.history.print',
4283         'NOOP_True',
4284         'ProcessTemplate',
4285         'usr',
4286         'print-on-demand',
4287 $$
4288 [%- USE date -%]
4289 <div>
4290     <style> li { padding: 8px; margin 5px; }</style>
4291     <div>[% date.format %]</div>
4292     <br/>
4293
4294     [% user.family_name %], [% user.first_given_name %]
4295     <ol>
4296     [% FOR hold IN target %]
4297         <li>
4298             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4299             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4300             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4301         </li>
4302     [% END %]
4303     </ol>
4304 </div>
4305 $$
4306     )
4307
4308 ;
4309
4310 INSERT INTO action_trigger.environment (
4311         event_def,
4312         path
4313     ) VALUES 
4314          ( 25, 'target_copy')
4315         ,( 25, 'usr' )
4316         ,( 26, 'target_copy' )
4317         ,( 26, 'usr' )
4318         ,( 27, 'current_copy' )
4319         ,( 27, 'usr' )
4320         ,( 28, 'current_copy' )
4321         ,( 28, 'usr' )
4322 ;
4323
4324 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4325         'money.format.payment_receipt.email',
4326         'mp', 
4327         oils_i18n_gettext(
4328             'money.format.payment_receipt.email',
4329             'An email has been requested for a payment receipt.',
4330             'ath',
4331             'description'
4332         ), 
4333         FALSE
4334     )
4335     ,(
4336         'money.format.payment_receipt.print',
4337         'mp', 
4338         oils_i18n_gettext(
4339             'money.format.payment_receipt.print',
4340             'A payment receipt needs to be formatted for printing.',
4341             'ath',
4342             'description'
4343         ), 
4344         FALSE
4345     )
4346 ;
4347
4348 INSERT INTO action_trigger.event_definition (
4349         id,
4350         active,
4351         owner,
4352         name,
4353         hook,
4354         validator,
4355         reactor,
4356         group_field,
4357         granularity,
4358         template
4359     ) VALUES (
4360         29,
4361         TRUE,
4362         1,
4363         'money.payment_receipt.email',
4364         'money.format.payment_receipt.email',
4365         'NOOP_True',
4366         'SendEmail',
4367         'xact.usr',
4368         NULL,
4369 $$
4370 [%- USE date -%]
4371 [%- SET user = target.0.xact.usr -%]
4372 To: [%- params.recipient_email || user.email %]
4373 From: [%- params.sender_email || default_sender %]
4374 Subject: Payment Receipt
4375
4376 [% date.format -%]
4377 [%- SET xact_mp_hash = {} -%]
4378 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4379     [%- SET xact_id = mp.xact.id -%]
4380     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4381     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4382 [%- END -%]
4383 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4384     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4385 Transaction ID: [% xact_id %]
4386     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4387     [% ELSE %]Miscellaneous
4388     [% END %]
4389     Line item billings:
4390         [%- SET mb_type_hash = {} -%]
4391         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4392             [%- IF mb.voided == 'f' -%]
4393                 [%- SET mb_type = mb.btype.id -%]
4394                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4395                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4396                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4397                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4398                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4399             [%- END -%]
4400         [%- END -%]
4401         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4402             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4403                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4404                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4405             [%- ELSE -%][%# all other billings show individually %]
4406                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4407                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4408                 [% END %]
4409             [% END %]
4410         [% END %]
4411     Line item payments:
4412         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4413             Payment ID: [% mp.id %]
4414                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4415                     [% CASE "cash_payment" %]cash
4416                     [% CASE "check_payment" %]check
4417                     [% CASE "credit_card_payment" %]credit card (
4418                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4419                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4420                         [% cc_chunks.last -%]
4421                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4422                     )
4423                     [% CASE "credit_payment" %]credit
4424                     [% CASE "forgive_payment" %]forgiveness
4425                     [% CASE "goods_payment" %]goods
4426                     [% CASE "work_payment" %]work
4427                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4428         [% END %]
4429 [% END %]
4430 $$
4431     )
4432     ,(
4433         30,
4434         TRUE,
4435         1,
4436         'money.payment_receipt.print',
4437         'money.format.payment_receipt.print',
4438         'NOOP_True',
4439         'ProcessTemplate',
4440         'xact.usr',
4441         'print-on-demand',
4442 $$
4443 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4444 <div style="li { padding: 8px; margin 5px; }">
4445     <div>[% date.format %]</div><br/>
4446     <ol>
4447     [% SET xact_mp_hash = {} %]
4448     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4449         [% SET xact_id = mp.xact.id %]
4450         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4451         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4452     [% END %]
4453     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4454         [% SET xact = xact_mp_hash.$xact_id.xact %]
4455         <li>Transaction ID: [% xact_id %]
4456             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4457             [% ELSE %]Miscellaneous
4458             [% END %]
4459             Line item billings:<ol>
4460                 [% SET mb_type_hash = {} %]
4461                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4462                     [% IF mb.voided == 'f' %]
4463                         [% SET mb_type = mb.btype.id %]
4464                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4465                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4466                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4467                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4468                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4469                     [% END %]
4470                 [% END %]
4471                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4472                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4473                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4474                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4475                     [% ELSE %][%# all other billings show individually %]
4476                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4477                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4478                         [% END %]
4479                     [% END %]</li>
4480                 [% END %]
4481             </ol>
4482             Line item payments:<ol>
4483                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4484                     <li>Payment ID: [% mp.id %]
4485                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4486                             [% CASE "cash_payment" %]cash
4487                             [% CASE "check_payment" %]check
4488                             [% CASE "credit_card_payment" %]credit card (
4489                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4490                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4491                                 [% cc_chunks.last -%]
4492                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4493                             )
4494                             [% CASE "credit_payment" %]credit
4495                             [% CASE "forgive_payment" %]forgiveness
4496                             [% CASE "goods_payment" %]goods
4497                             [% CASE "work_payment" %]work
4498                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4499                     </li>
4500                 [% END %]
4501             </ol>
4502         </li>
4503     [% END %]
4504     </ol>
4505 </div>
4506 $$
4507     )
4508 ;
4509
4510 INSERT INTO action_trigger.environment (
4511         event_def,
4512         path
4513     ) VALUES -- for fleshing mp objects
4514          ( 29, 'xact')
4515         ,( 29, 'xact.usr')
4516         ,( 29, 'xact.grocery' )
4517         ,( 29, 'xact.circulation' )
4518         ,( 29, 'xact.summary' )
4519         ,( 30, 'xact')
4520         ,( 30, 'xact.usr')
4521         ,( 30, 'xact.grocery' )
4522         ,( 30, 'xact.circulation' )
4523         ,( 30, 'xact.summary' )
4524 ;
4525
4526 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4527     'DeleteTempBiblioBucket',
4528     oils_i18n_gettext(
4529         'DeleteTempBiblioBucket',
4530         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4531         'atclean',
4532         'description'
4533     )
4534 );
4535
4536 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4537         'biblio.format.record_entry.email',
4538         'cbreb', 
4539         oils_i18n_gettext(
4540             'biblio.format.record_entry.email',
4541             'An email has been requested for one or more biblio record entries.',
4542             'ath',
4543             'description'
4544         ), 
4545         FALSE
4546     )
4547     ,(
4548         'biblio.format.record_entry.print',
4549         'cbreb', 
4550         oils_i18n_gettext(
4551             'biblio.format.record_entry.print',
4552             'One or more biblio record entries need to be formatted for printing.',
4553             'ath',
4554             'description'
4555         ), 
4556         FALSE
4557     )
4558 ;
4559
4560 INSERT INTO action_trigger.event_definition (
4561         id,
4562         active,
4563         owner,
4564         name,
4565         hook,
4566         validator,
4567         reactor,
4568         cleanup_success,
4569         cleanup_failure,
4570         group_field,
4571         granularity,
4572         template
4573     ) VALUES (
4574         31,
4575         TRUE,
4576         1,
4577         'biblio.record_entry.email',
4578         'biblio.format.record_entry.email',
4579         'NOOP_True',
4580         'SendEmail',
4581         'DeleteTempBiblioBucket',
4582         'DeleteTempBiblioBucket',
4583         'owner',
4584         NULL,
4585 $$
4586 [%- USE date -%]
4587 [%- SET user = target.0.owner -%]
4588 To: [%- params.recipient_email || user.email %]
4589 From: [%- params.sender_email || default_sender %]
4590 Subject: Bibliographic Records
4591
4592     [% FOR cbreb IN target %]
4593     [% FOR cbrebi IN cbreb.items %]
4594         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4595         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4596         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4597         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4598
4599     [% END %]
4600     [% END %]
4601 $$
4602     )
4603     ,(
4604         32,
4605         TRUE,
4606         1,
4607         'biblio.record_entry.print',
4608         'biblio.format.record_entry.print',
4609         'NOOP_True',
4610         'ProcessTemplate',
4611         'DeleteTempBiblioBucket',
4612         'DeleteTempBiblioBucket',
4613         'owner',
4614         'print-on-demand',
4615 $$
4616 [%- USE date -%]
4617 <div>
4618     <style> li { padding: 8px; margin 5px; }</style>
4619     <ol>
4620     [% FOR cbreb IN target %]
4621     [% FOR cbrebi IN cbreb.items %]
4622         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4623             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4624             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4625             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4626         </li>
4627     [% END %]
4628     [% END %]
4629     </ol>
4630 </div>
4631 $$
4632     )
4633 ;
4634
4635 INSERT INTO action_trigger.environment (
4636         event_def,
4637         path
4638     ) VALUES -- for fleshing cbreb objects
4639          ( 31, 'owner' )
4640         ,( 31, 'items' )
4641         ,( 31, 'items.target_biblio_record_entry' )
4642         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4643         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4644         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4645         ,( 31, 'items.target_biblio_record_entry.notes' )
4646         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4647         ,( 32, 'owner' )
4648         ,( 32, 'items' )
4649         ,( 32, 'items.target_biblio_record_entry' )
4650         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4651         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4652         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4653         ,( 32, 'items.target_biblio_record_entry.notes' )
4654         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4655 ;
4656
4657 INSERT INTO action_trigger.environment (
4658         event_def,
4659         path
4660     ) VALUES -- for fleshing mp objects
4661          ( 29, 'credit_card_payment')
4662         ,( 29, 'xact.billings')
4663         ,( 29, 'xact.billings.btype')
4664         ,( 30, 'credit_card_payment')
4665         ,( 30, 'xact.billings')
4666         ,( 30, 'xact.billings.btype')
4667 ;
4668
4669 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4670     (   'circ.format.missing_pieces.slip.print',
4671         'circ', 
4672         oils_i18n_gettext(
4673             'circ.format.missing_pieces.slip.print',
4674             'A missing pieces slip needs to be formatted for printing.',
4675             'ath',
4676             'description'
4677         ), 
4678         FALSE
4679     )
4680     ,(  'circ.format.missing_pieces.letter.print',
4681         'circ', 
4682         oils_i18n_gettext(
4683             'circ.format.missing_pieces.letter.print',
4684             'A missing pieces patron letter needs to be formatted for printing.',
4685             'ath',
4686             'description'
4687         ), 
4688         FALSE
4689     )
4690 ;
4691
4692 INSERT INTO action_trigger.event_definition (
4693         id,
4694         active,
4695         owner,
4696         name,
4697         hook,
4698         validator,
4699         reactor,
4700         group_field,
4701         granularity,
4702         template
4703     ) VALUES (
4704         33,
4705         TRUE,
4706         1,
4707         'circ.missing_pieces.slip.print',
4708         'circ.format.missing_pieces.slip.print',
4709         'NOOP_True',
4710         'ProcessTemplate',
4711         'usr',
4712         'print-on-demand',
4713 $$
4714 [%- USE date -%]
4715 [%- SET user = target.0.usr -%]
4716 <div style="li { padding: 8px; margin 5px; }">
4717     <div>[% date.format %]</div><br/>
4718     Missing pieces for:
4719     <ol>
4720     [% FOR circ IN target %]
4721         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4722             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4723         </li>
4724     [% END %]
4725     </ol>
4726 </div>
4727 $$
4728     )
4729     ,(
4730         34,
4731         TRUE,
4732         1,
4733         'circ.missing_pieces.letter.print',
4734         'circ.format.missing_pieces.letter.print',
4735         'NOOP_True',
4736         'ProcessTemplate',
4737         'usr',
4738         'print-on-demand',
4739 $$
4740 [%- USE date -%]
4741 [%- SET user = target.0.usr -%]
4742 [% date.format %]
4743 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4744
4745 We are missing pieces for the following returned items:
4746 [% FOR circ IN target %]
4747 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4748 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4749 [% END %]
4750
4751 Please return these pieces as soon as possible.
4752
4753 Thanks!
4754
4755 Library Staff
4756 $$
4757     )
4758 ;
4759
4760 INSERT INTO action_trigger.environment (
4761         event_def,
4762         path
4763     ) VALUES -- for fleshing circ objects
4764          ( 33, 'usr')
4765         ,( 33, 'target_copy')
4766         ,( 33, 'target_copy.circ_lib')
4767         ,( 33, 'target_copy.circ_lib.mailing_address')
4768         ,( 33, 'target_copy.circ_lib.billing_address')
4769         ,( 33, 'target_copy.call_number')
4770         ,( 33, 'target_copy.call_number.owning_lib')
4771         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4772         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4773         ,( 33, 'circ_lib')
4774         ,( 33, 'circ_lib.mailing_address')
4775         ,( 33, 'circ_lib.billing_address')
4776         ,( 34, 'usr')
4777         ,( 34, 'target_copy')
4778         ,( 34, 'target_copy.circ_lib')
4779         ,( 34, 'target_copy.circ_lib.mailing_address')
4780         ,( 34, 'target_copy.circ_lib.billing_address')
4781         ,( 34, 'target_copy.call_number')
4782         ,( 34, 'target_copy.call_number.owning_lib')
4783         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4784         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4785         ,( 34, 'circ_lib')
4786         ,( 34, 'circ_lib.mailing_address')
4787         ,( 34, 'circ_lib.billing_address')
4788 ;
4789
4790 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4791     VALUES (   
4792         'ahr.format.pull_list',
4793         'ahr', 
4794         oils_i18n_gettext(
4795             'ahr.format.pull_list',
4796             'Format holds pull list for printing',
4797             'ath',
4798             'description'
4799         ), 
4800         FALSE
4801     );
4802
4803 INSERT INTO action_trigger.event_definition (
4804         id,
4805         active,
4806         owner,
4807         name,
4808         hook,
4809         validator,
4810         reactor,
4811         group_field,
4812         granularity,
4813         template
4814     ) VALUES (
4815         35,
4816         TRUE,
4817         1,
4818         'Holds Pull List',
4819         'ahr.format.pull_list',
4820         'NOOP_True',
4821         'ProcessTemplate',
4822         'pickup_lib',
4823         'print-on-demand',
4824 $$
4825 [%- USE date -%]
4826 <style>
4827     table { border-collapse: collapse; } 
4828     td { padding: 5px; border-bottom: 1px solid #888; } 
4829     th { font-weight: bold; }
4830 </style>
4831 [% 
4832     # Sort the holds into copy-location buckets
4833     # In the main print loop, sort each bucket by callnumber before printing
4834     SET holds_list = [];
4835     SET loc_data = [];
4836     SET current_location = target.0.current_copy.location.id;
4837     FOR hold IN target;
4838         IF current_location != hold.current_copy.location.id;
4839             SET current_location = hold.current_copy.location.id;
4840             holds_list.push(loc_data);
4841             SET loc_data = [];
4842         END;
4843         SET hold_data = {
4844             'hold' => hold,
4845             'callnumber' => hold.current_copy.call_number.label
4846         };
4847         loc_data.push(hold_data);
4848     END;
4849     holds_list.push(loc_data)
4850 %]
4851 <table>
4852     <thead>
4853         <tr>
4854             <th>Title</th>
4855             <th>Author</th>
4856             <th>Shelving Location</th>
4857             <th>Call Number</th>
4858             <th>Barcode</th>
4859             <th>Patron</th>
4860         </tr>
4861     </thead>
4862     <tbody>
4863     [% FOR loc_data IN holds_list  %]
4864         [% FOR hold_data IN loc_data.sort('callnumber') %]
4865             [% 
4866                 SET hold = hold_data.hold;
4867                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4868             %]
4869             <tr>
4870                 <td>[% copy_data.title | truncate %]</td>
4871                 <td>[% copy_data.author | truncate %]</td>
4872                 <td>[% hold.current_copy.location.name %]</td>
4873                 <td>[% hold.current_copy.call_number.label %]</td>
4874                 <td>[% hold.current_copy.barcode %]</td>
4875                 <td>[% hold.usr.card.barcode %]</td>
4876             </tr>
4877         [% END %]
4878     [% END %]
4879     <tbody>
4880 </table>
4881 $$
4882 );
4883
4884 INSERT INTO action_trigger.environment (
4885         event_def,
4886         path
4887     ) VALUES
4888         (35, 'current_copy.location'),
4889         (35, 'current_copy.call_number'),
4890         (35, 'usr.card'),
4891         (35, 'pickup_lib')
4892 ;
4893
4894 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4895     'HoldIsCancelled', 
4896     oils_i18n_gettext( 
4897         'HoldIsCancelled', 
4898         'Check whether a hold request is cancelled.', 
4899         'atval', 
4900         'description' 
4901     ) 
4902 );
4903
4904 -- Create the query schema, and the tables and views therein
4905
4906 DROP SCHEMA IF EXISTS sql CASCADE;
4907 DROP SCHEMA IF EXISTS query CASCADE;
4908
4909 CREATE SCHEMA query;
4910
4911 CREATE TABLE query.datatype (
4912         id              SERIAL            PRIMARY KEY,
4913         datatype_name   TEXT              NOT NULL UNIQUE,
4914         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4915         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4916         CONSTRAINT qdt_comp_not_num CHECK
4917         ( is_numeric IS FALSE OR is_composite IS FALSE )
4918 );
4919
4920 -- Define the most common datatypes in query.datatype.  Note that none of
4921 -- these stock datatypes specifies a width or precision.
4922
4923 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4924 -- room for more stock datatypes if we ever want to add them.
4925
4926 SELECT setval( 'query.datatype_id_seq', 1000 );
4927
4928 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4929   VALUES (1, 'SMALLINT', true);
4930  
4931 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4932   VALUES (2, 'INTEGER', true);
4933  
4934 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4935   VALUES (3, 'BIGINT', true);
4936  
4937 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4938   VALUES (4, 'DECIMAL', true);
4939  
4940 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4941   VALUES (5, 'NUMERIC', true);
4942  
4943 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4944   VALUES (6, 'REAL', true);
4945  
4946 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4947   VALUES (7, 'DOUBLE PRECISION', true);
4948  
4949 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4950   VALUES (8, 'SERIAL', true);
4951  
4952 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4953   VALUES (9, 'BIGSERIAL', true);
4954  
4955 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4956   VALUES (10, 'MONEY', false);
4957  
4958 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4959   VALUES (11, 'VARCHAR', false);
4960  
4961 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4962   VALUES (12, 'CHAR', false);
4963  
4964 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4965   VALUES (13, 'TEXT', false);
4966  
4967 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4968   VALUES (14, '"char"', false);
4969  
4970 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4971   VALUES (15, 'NAME', false);
4972  
4973 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4974   VALUES (16, 'BYTEA', false);
4975  
4976 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4977   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4978  
4979 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4980   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4981  
4982 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4983   VALUES (19, 'DATE', false);
4984  
4985 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4986   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4987  
4988 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4989   VALUES (21, 'TIME WITH TIME ZONE', false);
4990  
4991 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4992   VALUES (22, 'INTERVAL', false);
4993  
4994 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4995   VALUES (23, 'BOOLEAN', false);
4996  
4997 CREATE TABLE query.subfield (
4998         id              SERIAL            PRIMARY KEY,
4999         composite_type  INT               NOT NULL
5000                                           REFERENCES query.datatype(id)
5001                                           ON DELETE CASCADE
5002                                           DEFERRABLE INITIALLY DEFERRED,
5003         seq_no          INT               NOT NULL
5004                                           CONSTRAINT qsf_pos_seq_no
5005                                           CHECK( seq_no > 0 ),
5006         subfield_type   INT               NOT NULL
5007                                           REFERENCES query.datatype(id)
5008                                           DEFERRABLE INITIALLY DEFERRED,
5009         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
5010 );
5011
5012 CREATE TABLE query.function_sig (
5013         id              SERIAL            PRIMARY KEY,
5014         function_name   TEXT              NOT NULL,
5015         return_type     INT               REFERENCES query.datatype(id)
5016                                           DEFERRABLE INITIALLY DEFERRED,
5017         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
5018         CONSTRAINT qfd_rtn_or_aggr CHECK
5019         ( return_type IS NULL OR is_aggregate = FALSE )
5020 );
5021
5022 CREATE INDEX query_function_sig_name_idx 
5023         ON query.function_sig (function_name);
5024
5025 CREATE TABLE query.function_param_def (
5026         id              SERIAL            PRIMARY KEY,
5027         function_id     INT               NOT NULL
5028                                           REFERENCES query.function_sig( id )
5029                                           ON DELETE CASCADE
5030                                           DEFERRABLE INITIALLY DEFERRED,
5031         seq_no          INT               NOT NULL
5032                                           CONSTRAINT qfpd_pos_seq_no CHECK
5033                                           ( seq_no > 0 ),
5034         datatype        INT               NOT NULL
5035                                           REFERENCES query.datatype( id )
5036                                           DEFERRABLE INITIALLY DEFERRED,
5037         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
5038 );
5039
5040 CREATE TABLE  query.stored_query (
5041         id            SERIAL         PRIMARY KEY,
5042         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
5043                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
5044         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
5045         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
5046         from_clause   INT            , --REFERENCES query.from_clause
5047                                      --DEFERRABLE INITIALLY DEFERRED,
5048         where_clause  INT            , --REFERENCES query.expression
5049                                      --DEFERRABLE INITIALLY DEFERRED,
5050         having_clause INT            , --REFERENCES query.expression
5051                                      --DEFERRABLE INITIALLY DEFERRED
5052         limit_count   INT            , --REFERENCES query.expression( id )
5053                                      --DEFERRABLE INITIALLY DEFERRED,
5054         offset_count  INT            --REFERENCES query.expression( id )
5055                                      --DEFERRABLE INITIALLY DEFERRED
5056 );
5057
5058 -- (Foreign keys to be defined later after other tables are created)
5059
5060 CREATE TABLE query.query_sequence (
5061         id              SERIAL            PRIMARY KEY,
5062         parent_query    INT               NOT NULL
5063                                           REFERENCES query.stored_query
5064                                                                           ON DELETE CASCADE
5065                                                                           DEFERRABLE INITIALLY DEFERRED,
5066         seq_no          INT               NOT NULL,
5067         child_query     INT               NOT NULL
5068                                           REFERENCES query.stored_query
5069                                                                           ON DELETE CASCADE
5070                                                                           DEFERRABLE INITIALLY DEFERRED,
5071         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
5072 );
5073
5074 CREATE TABLE query.bind_variable (
5075         name          TEXT             PRIMARY KEY,
5076         type          TEXT             NOT NULL
5077                                            CONSTRAINT bind_variable_type CHECK
5078                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
5079         description   TEXT             NOT NULL,
5080         default_value TEXT,            -- to be encoded in JSON
5081         label         TEXT             NOT NULL
5082 );
5083
5084 CREATE TABLE query.expression (
5085         id            SERIAL        PRIMARY KEY,
5086         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
5087                                     ( type IN (
5088                                     'xbet',    -- between
5089                                     'xbind',   -- bind variable
5090                                     'xbool',   -- boolean
5091                                     'xcase',   -- case
5092                                     'xcast',   -- cast
5093                                     'xcol',    -- column
5094                                     'xex',     -- exists
5095                                     'xfunc',   -- function
5096                                     'xin',     -- in
5097                                     'xisnull', -- is null
5098                                     'xnull',   -- null
5099                                     'xnum',    -- number
5100                                     'xop',     -- operator
5101                                     'xser',    -- series
5102                                     'xstr',    -- string
5103                                     'xsubq'    -- subquery
5104                                                                 ) ),
5105         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
5106         parent_expr   INT           REFERENCES query.expression
5107                                     ON DELETE CASCADE
5108                                     DEFERRABLE INITIALLY DEFERRED,
5109         seq_no        INT           NOT NULL DEFAULT 1,
5110         literal       TEXT,
5111         table_alias   TEXT,
5112         column_name   TEXT,
5113         left_operand  INT           REFERENCES query.expression
5114                                     DEFERRABLE INITIALLY DEFERRED,
5115         operator      TEXT,
5116         right_operand INT           REFERENCES query.expression
5117                                     DEFERRABLE INITIALLY DEFERRED,
5118         function_id   INT           REFERENCES query.function_sig
5119                                     DEFERRABLE INITIALLY DEFERRED,
5120         subquery      INT           REFERENCES query.stored_query
5121                                     DEFERRABLE INITIALLY DEFERRED,
5122         cast_type     INT           REFERENCES query.datatype
5123                                     DEFERRABLE INITIALLY DEFERRED,
5124         negate        BOOL          NOT NULL DEFAULT FALSE,
5125         bind_variable TEXT          REFERENCES query.bind_variable
5126                                         DEFERRABLE INITIALLY DEFERRED
5127 );
5128
5129 CREATE UNIQUE INDEX query_expr_parent_seq
5130         ON query.expression( parent_expr, seq_no )
5131         WHERE parent_expr IS NOT NULL;
5132
5133 -- Due to some circular references, the following foreign key definitions
5134 -- had to be deferred until query.expression existed:
5135
5136 ALTER TABLE query.stored_query
5137         ADD FOREIGN KEY ( where_clause )
5138         REFERENCES query.expression( id )
5139         DEFERRABLE INITIALLY DEFERRED;
5140
5141 ALTER TABLE query.stored_query
5142         ADD FOREIGN KEY ( having_clause )
5143         REFERENCES query.expression( id )
5144         DEFERRABLE INITIALLY DEFERRED;
5145
5146 ALTER TABLE query.stored_query
5147     ADD FOREIGN KEY ( limit_count )
5148     REFERENCES query.expression( id )
5149     DEFERRABLE INITIALLY DEFERRED;
5150
5151 ALTER TABLE query.stored_query
5152     ADD FOREIGN KEY ( offset_count )
5153     REFERENCES query.expression( id )
5154     DEFERRABLE INITIALLY DEFERRED;
5155
5156 CREATE TABLE query.case_branch (
5157         id            SERIAL        PRIMARY KEY,
5158         parent_expr   INT           NOT NULL REFERENCES query.expression
5159                                     ON DELETE CASCADE
5160                                     DEFERRABLE INITIALLY DEFERRED,
5161         seq_no        INT           NOT NULL,
5162         condition     INT           REFERENCES query.expression
5163                                     DEFERRABLE INITIALLY DEFERRED,
5164         result        INT           NOT NULL REFERENCES query.expression
5165                                     DEFERRABLE INITIALLY DEFERRED,
5166         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5167 );
5168
5169 CREATE TABLE query.from_relation (
5170         id               SERIAL        PRIMARY KEY,
5171         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5172                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5173         table_name       TEXT,
5174         class_name       TEXT,
5175         subquery         INT           REFERENCES query.stored_query,
5176         function_call    INT           REFERENCES query.expression,
5177         table_alias      TEXT,
5178         parent_relation  INT           REFERENCES query.from_relation
5179                                        ON DELETE CASCADE
5180                                        DEFERRABLE INITIALLY DEFERRED,
5181         seq_no           INT           NOT NULL DEFAULT 1,
5182         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5183                                            join_type IS NULL OR join_type IN
5184                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5185                                        ),
5186         on_clause        INT           REFERENCES query.expression
5187                                        DEFERRABLE INITIALLY DEFERRED,
5188         CONSTRAINT join_or_core CHECK (
5189         ( parent_relation IS NULL AND join_type IS NULL
5190           AND on_clause IS NULL )
5191         OR
5192         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5193           AND on_clause IS NOT NULL )
5194         )
5195 );
5196
5197 CREATE UNIQUE INDEX from_parent_seq
5198         ON query.from_relation( parent_relation, seq_no )
5199         WHERE parent_relation IS NOT NULL;
5200
5201 -- The following foreign key had to be deferred until
5202 -- query.from_relation existed
5203
5204 ALTER TABLE query.stored_query
5205         ADD FOREIGN KEY (from_clause)
5206         REFERENCES query.from_relation
5207         DEFERRABLE INITIALLY DEFERRED;
5208
5209 CREATE TABLE query.record_column (
5210         id            SERIAL            PRIMARY KEY,
5211         from_relation INT               NOT NULL REFERENCES query.from_relation
5212                                         ON DELETE CASCADE
5213                                         DEFERRABLE INITIALLY DEFERRED,
5214         seq_no        INT               NOT NULL,
5215         column_name   TEXT              NOT NULL,
5216         column_type   INT               NOT NULL REFERENCES query.datatype
5217                                         ON DELETE CASCADE
5218                                                                         DEFERRABLE INITIALLY DEFERRED,
5219         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5220 );
5221
5222 CREATE TABLE query.select_item (
5223         id               SERIAL         PRIMARY KEY,
5224         stored_query     INT            NOT NULL REFERENCES query.stored_query
5225                                         ON DELETE CASCADE
5226                                         DEFERRABLE INITIALLY DEFERRED,
5227         seq_no           INT            NOT NULL,
5228         expression       INT            NOT NULL REFERENCES query.expression
5229                                         DEFERRABLE INITIALLY DEFERRED,
5230         column_alias     TEXT,
5231         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5232         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5233 );
5234
5235 CREATE TABLE query.order_by_item (
5236         id               SERIAL         PRIMARY KEY,
5237         stored_query     INT            NOT NULL REFERENCES query.stored_query
5238                                         ON DELETE CASCADE
5239                                         DEFERRABLE INITIALLY DEFERRED,
5240         seq_no           INT            NOT NULL,
5241         expression       INT            NOT NULL REFERENCES query.expression
5242                                         ON DELETE CASCADE
5243                                         DEFERRABLE INITIALLY DEFERRED,
5244         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5245 );
5246
5247 ------------------------------------------------------------
5248 -- Create updatable views for different kinds of expressions
5249 ------------------------------------------------------------
5250
5251 -- Create updatable view for BETWEEN expressions
5252
5253 CREATE OR REPLACE VIEW query.expr_xbet AS
5254     SELECT
5255                 id,
5256                 parenthesize,
5257                 parent_expr,
5258                 seq_no,
5259                 left_operand,
5260                 negate
5261     FROM
5262         query.expression
5263     WHERE
5264         type = 'xbet';
5265
5266 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5267     ON INSERT TO query.expr_xbet
5268     DO INSTEAD
5269     INSERT INTO query.expression (
5270                 id,
5271                 type,
5272                 parenthesize,
5273                 parent_expr,
5274                 seq_no,
5275                 left_operand,
5276                 negate
5277     ) VALUES (
5278         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5279         'xbet',
5280         COALESCE(NEW.parenthesize, FALSE),
5281         NEW.parent_expr,
5282         COALESCE(NEW.seq_no, 1),
5283                 NEW.left_operand,
5284                 COALESCE(NEW.negate, false)
5285     );
5286
5287 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5288     ON UPDATE TO query.expr_xbet
5289     DO INSTEAD
5290     UPDATE query.expression SET
5291         id = NEW.id,
5292         parenthesize = NEW.parenthesize,
5293         parent_expr = NEW.parent_expr,
5294         seq_no = NEW.seq_no,
5295                 left_operand = NEW.left_operand,
5296                 negate = NEW.negate
5297     WHERE
5298         id = OLD.id;
5299
5300 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5301     ON DELETE TO query.expr_xbet
5302     DO INSTEAD
5303     DELETE FROM query.expression WHERE id = OLD.id;
5304
5305 -- Create updatable view for bind variable expressions
5306
5307 CREATE OR REPLACE VIEW query.expr_xbind AS
5308     SELECT
5309                 id,
5310                 parenthesize,
5311                 parent_expr,
5312                 seq_no,
5313                 bind_variable
5314     FROM
5315         query.expression
5316     WHERE
5317         type = 'xbind';
5318
5319 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5320     ON INSERT TO query.expr_xbind
5321     DO INSTEAD
5322     INSERT INTO query.expression (
5323                 id,
5324                 type,
5325                 parenthesize,
5326                 parent_expr,
5327                 seq_no,
5328                 bind_variable
5329     ) VALUES (
5330         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5331         'xbind',
5332         COALESCE(NEW.parenthesize, FALSE),
5333         NEW.parent_expr,
5334         COALESCE(NEW.seq_no, 1),
5335                 NEW.bind_variable
5336     );
5337
5338 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5339     ON UPDATE TO query.expr_xbind
5340     DO INSTEAD
5341     UPDATE query.expression SET
5342         id = NEW.id,
5343         parenthesize = NEW.parenthesize,
5344         parent_expr = NEW.parent_expr,
5345         seq_no = NEW.seq_no,
5346                 bind_variable = NEW.bind_variable
5347     WHERE
5348         id = OLD.id;
5349
5350 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5351     ON DELETE TO query.expr_xbind
5352     DO INSTEAD
5353     DELETE FROM query.expression WHERE id = OLD.id;
5354
5355 -- Create updatable view for boolean expressions
5356
5357 CREATE OR REPLACE VIEW query.expr_xbool AS
5358     SELECT
5359                 id,
5360                 parenthesize,
5361                 parent_expr,
5362                 seq_no,
5363                 literal,
5364                 negate
5365     FROM
5366         query.expression
5367     WHERE
5368         type = 'xbool';
5369
5370 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5371     ON INSERT TO query.expr_xbool
5372     DO INSTEAD
5373     INSERT INTO query.expression (
5374                 id,
5375                 type,
5376                 parenthesize,
5377                 parent_expr,
5378                 seq_no,
5379                 literal,
5380                 negate
5381     ) VALUES (
5382         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5383         'xbool',
5384         COALESCE(NEW.parenthesize, FALSE),
5385         NEW.parent_expr,
5386         COALESCE(NEW.seq_no, 1),
5387         NEW.literal,
5388                 COALESCE(NEW.negate, false)
5389     );
5390
5391 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5392     ON UPDATE TO query.expr_xbool
5393     DO INSTEAD
5394     UPDATE query.expression SET
5395         id = NEW.id,
5396         parenthesize = NEW.parenthesize,
5397         parent_expr = NEW.parent_expr,
5398         seq_no = NEW.seq_no,
5399         literal = NEW.literal,
5400                 negate = NEW.negate
5401     WHERE
5402         id = OLD.id;
5403
5404 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5405     ON DELETE TO query.expr_xbool
5406     DO INSTEAD
5407     DELETE FROM query.expression WHERE id = OLD.id;
5408
5409 -- Create updatable view for CASE expressions
5410
5411 CREATE OR REPLACE VIEW query.expr_xcase AS
5412     SELECT
5413                 id,
5414                 parenthesize,
5415                 parent_expr,
5416                 seq_no,
5417                 left_operand,
5418                 negate
5419     FROM
5420         query.expression
5421     WHERE
5422         type = 'xcase';
5423
5424 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5425     ON INSERT TO query.expr_xcase
5426     DO INSTEAD
5427     INSERT INTO query.expression (
5428                 id,
5429                 type,
5430                 parenthesize,
5431                 parent_expr,
5432                 seq_no,
5433                 left_operand,
5434                 negate
5435     ) VALUES (
5436         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5437         'xcase',
5438         COALESCE(NEW.parenthesize, FALSE),
5439         NEW.parent_expr,
5440         COALESCE(NEW.seq_no, 1),
5441                 NEW.left_operand,
5442                 COALESCE(NEW.negate, false)
5443     );
5444
5445 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5446     ON UPDATE TO query.expr_xcase
5447     DO INSTEAD
5448     UPDATE query.expression SET
5449         id = NEW.id,
5450         parenthesize = NEW.parenthesize,
5451         parent_expr = NEW.parent_expr,
5452         seq_no = NEW.seq_no,
5453                 left_operand = NEW.left_operand,
5454                 negate = NEW.negate
5455     WHERE
5456         id = OLD.id;
5457
5458 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5459     ON DELETE TO query.expr_xcase
5460     DO INSTEAD
5461     DELETE FROM query.expression WHERE id = OLD.id;
5462
5463 -- Create updatable view for cast expressions
5464
5465 CREATE OR REPLACE VIEW query.expr_xcast AS
5466     SELECT
5467                 id,
5468                 parenthesize,
5469                 parent_expr,
5470                 seq_no,
5471                 left_operand,
5472                 cast_type,
5473                 negate
5474     FROM
5475         query.expression
5476     WHERE
5477         type = 'xcast';
5478
5479 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5480     ON INSERT TO query.expr_xcast
5481     DO INSTEAD
5482     INSERT INTO query.expression (
5483         id,
5484         type,
5485         parenthesize,
5486         parent_expr,
5487         seq_no,
5488         left_operand,
5489         cast_type,
5490         negate
5491     ) VALUES (
5492         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5493         'xcast',
5494         COALESCE(NEW.parenthesize, FALSE),
5495         NEW.parent_expr,
5496         COALESCE(NEW.seq_no, 1),
5497         NEW.left_operand,
5498         NEW.cast_type,
5499         COALESCE(NEW.negate, false)
5500     );
5501
5502 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5503     ON UPDATE TO query.expr_xcast
5504     DO INSTEAD
5505     UPDATE query.expression SET
5506         id = NEW.id,
5507         parenthesize = NEW.parenthesize,
5508         parent_expr = NEW.parent_expr,
5509         seq_no = NEW.seq_no,
5510                 left_operand = NEW.left_operand,
5511                 cast_type = NEW.cast_type,
5512                 negate = NEW.negate
5513     WHERE
5514         id = OLD.id;
5515
5516 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5517     ON DELETE TO query.expr_xcast
5518     DO INSTEAD
5519     DELETE FROM query.expression WHERE id = OLD.id;
5520
5521 -- Create updatable view for column expressions
5522
5523 CREATE OR REPLACE VIEW query.expr_xcol AS
5524     SELECT
5525                 id,
5526                 parenthesize,
5527                 parent_expr,
5528                 seq_no,
5529                 table_alias,
5530                 column_name,
5531                 negate
5532     FROM
5533         query.expression
5534     WHERE
5535         type = 'xcol';
5536
5537 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5538     ON INSERT TO query.expr_xcol
5539     DO INSTEAD
5540     INSERT INTO query.expression (
5541                 id,
5542                 type,
5543                 parenthesize,
5544                 parent_expr,
5545                 seq_no,
5546                 table_alias,
5547                 column_name,
5548                 negate
5549     ) VALUES (
5550         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5551         'xcol',
5552         COALESCE(NEW.parenthesize, FALSE),
5553         NEW.parent_expr,
5554         COALESCE(NEW.seq_no, 1),
5555                 NEW.table_alias,
5556                 NEW.column_name,
5557                 COALESCE(NEW.negate, false)
5558     );
5559
5560 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5561     ON UPDATE TO query.expr_xcol
5562     DO INSTEAD
5563     UPDATE query.expression SET
5564         id = NEW.id,
5565         parenthesize = NEW.parenthesize,
5566         parent_expr = NEW.parent_expr,
5567         seq_no = NEW.seq_no,
5568                 table_alias = NEW.table_alias,
5569                 column_name = NEW.column_name,
5570                 negate = NEW.negate
5571     WHERE
5572         id = OLD.id;
5573
5574 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5575     ON DELETE TO query.expr_xcol
5576     DO INSTEAD
5577     DELETE FROM query.expression WHERE id = OLD.id;
5578
5579 -- Create updatable view for EXISTS expressions
5580
5581 CREATE OR REPLACE VIEW query.expr_xex AS
5582     SELECT
5583                 id,
5584                 parenthesize,
5585                 parent_expr,
5586                 seq_no,
5587                 subquery,
5588                 negate
5589     FROM
5590         query.expression
5591     WHERE
5592         type = 'xex';
5593
5594 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5595     ON INSERT TO query.expr_xex
5596     DO INSTEAD
5597     INSERT INTO query.expression (
5598                 id,
5599                 type,
5600                 parenthesize,
5601                 parent_expr,
5602                 seq_no,
5603                 subquery,
5604                 negate
5605     ) VALUES (
5606         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5607         'xex',
5608         COALESCE(NEW.parenthesize, FALSE),
5609         NEW.parent_expr,
5610         COALESCE(NEW.seq_no, 1),
5611                 NEW.subquery,
5612                 COALESCE(NEW.negate, false)
5613     );
5614
5615 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5616     ON UPDATE TO query.expr_xex
5617     DO INSTEAD
5618     UPDATE query.expression SET
5619         id = NEW.id,
5620         parenthesize = NEW.parenthesize,
5621         parent_expr = NEW.parent_expr,
5622         seq_no = NEW.seq_no,
5623                 subquery = NEW.subquery,
5624                 negate = NEW.negate
5625     WHERE
5626         id = OLD.id;
5627
5628 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5629     ON DELETE TO query.expr_xex
5630     DO INSTEAD
5631     DELETE FROM query.expression WHERE id = OLD.id;
5632
5633 -- Create updatable view for function call expressions
5634
5635 CREATE OR REPLACE VIEW query.expr_xfunc AS
5636     SELECT
5637         id,
5638         parenthesize,
5639         parent_expr,
5640         seq_no,
5641         column_name,
5642         function_id,
5643         negate
5644     FROM
5645         query.expression
5646     WHERE
5647         type = 'xfunc';
5648
5649 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5650     ON INSERT TO query.expr_xfunc
5651     DO INSTEAD
5652     INSERT INTO query.expression (
5653         id,
5654         type,
5655         parenthesize,
5656         parent_expr,
5657         seq_no,
5658         column_name,
5659         function_id,
5660         negate
5661     ) VALUES (
5662         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5663         'xfunc',
5664         COALESCE(NEW.parenthesize, FALSE),
5665         NEW.parent_expr,
5666         COALESCE(NEW.seq_no, 1),
5667         NEW.column_name,
5668         NEW.function_id,
5669         COALESCE(NEW.negate, false)
5670     );
5671
5672 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5673     ON UPDATE TO query.expr_xfunc
5674     DO INSTEAD
5675     UPDATE query.expression SET
5676         id = NEW.id,
5677         parenthesize = NEW.parenthesize,
5678         parent_expr = NEW.parent_expr,
5679         seq_no = NEW.seq_no,
5680         column_name = NEW.column_name,
5681         function_id = NEW.function_id,
5682         negate = NEW.negate
5683     WHERE
5684         id = OLD.id;
5685
5686 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5687     ON DELETE TO query.expr_xfunc
5688     DO INSTEAD
5689     DELETE FROM query.expression WHERE id = OLD.id;
5690
5691 -- Create updatable view for IN expressions
5692
5693 CREATE OR REPLACE VIEW query.expr_xin AS
5694     SELECT
5695                 id,
5696                 parenthesize,
5697                 parent_expr,
5698                 seq_no,
5699                 left_operand,
5700                 subquery,
5701                 negate
5702     FROM
5703         query.expression
5704     WHERE
5705         type = 'xin';
5706
5707 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5708     ON INSERT TO query.expr_xin
5709     DO INSTEAD
5710     INSERT INTO query.expression (
5711                 id,
5712                 type,
5713                 parenthesize,
5714                 parent_expr,
5715                 seq_no,
5716                 left_operand,
5717                 subquery,
5718                 negate
5719     ) VALUES (
5720         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5721         'xin',
5722         COALESCE(NEW.parenthesize, FALSE),
5723         NEW.parent_expr,
5724         COALESCE(NEW.seq_no, 1),
5725                 NEW.left_operand,
5726                 NEW.subquery,
5727                 COALESCE(NEW.negate, false)
5728     );
5729
5730 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5731     ON UPDATE TO query.expr_xin
5732     DO INSTEAD
5733     UPDATE query.expression SET
5734         id = NEW.id,
5735         parenthesize = NEW.parenthesize,
5736         parent_expr = NEW.parent_expr,
5737         seq_no = NEW.seq_no,
5738                 left_operand = NEW.left_operand,
5739                 subquery = NEW.subquery,
5740                 negate = NEW.negate
5741     WHERE
5742         id = OLD.id;
5743
5744 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5745     ON DELETE TO query.expr_xin
5746     DO INSTEAD
5747     DELETE FROM query.expression WHERE id = OLD.id;
5748
5749 -- Create updatable view for IS NULL expressions
5750
5751 CREATE OR REPLACE VIEW query.expr_xisnull AS
5752     SELECT
5753                 id,
5754                 parenthesize,
5755                 parent_expr,
5756                 seq_no,
5757                 left_operand,
5758                 negate
5759     FROM
5760         query.expression
5761     WHERE
5762         type = 'xisnull';
5763
5764 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5765     ON INSERT TO query.expr_xisnull
5766     DO INSTEAD
5767     INSERT INTO query.expression (
5768                 id,
5769                 type,
5770                 parenthesize,
5771                 parent_expr,
5772                 seq_no,
5773                 left_operand,
5774                 negate
5775     ) VALUES (
5776         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5777         'xisnull',
5778         COALESCE(NEW.parenthesize, FALSE),
5779         NEW.parent_expr,
5780         COALESCE(NEW.seq_no, 1),
5781                 NEW.left_operand,
5782                 COALESCE(NEW.negate, false)
5783     );
5784
5785 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5786     ON UPDATE TO query.expr_xisnull
5787     DO INSTEAD
5788     UPDATE query.expression SET
5789         id = NEW.id,
5790         parenthesize = NEW.parenthesize,
5791         parent_expr = NEW.parent_expr,
5792         seq_no = NEW.seq_no,
5793                 left_operand = NEW.left_operand,
5794                 negate = NEW.negate
5795     WHERE
5796         id = OLD.id;
5797
5798 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5799     ON DELETE TO query.expr_xisnull
5800     DO INSTEAD
5801     DELETE FROM query.expression WHERE id = OLD.id;
5802
5803 -- Create updatable view for NULL expressions
5804
5805 CREATE OR REPLACE VIEW query.expr_xnull AS
5806     SELECT
5807                 id,
5808                 parenthesize,
5809                 parent_expr,
5810                 seq_no,
5811                 negate
5812     FROM
5813         query.expression
5814     WHERE
5815         type = 'xnull';
5816
5817 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5818     ON INSERT TO query.expr_xnull
5819     DO INSTEAD
5820     INSERT INTO query.expression (
5821                 id,
5822                 type,
5823                 parenthesize,
5824                 parent_expr,
5825                 seq_no,
5826                 negate
5827     ) VALUES (
5828         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5829         'xnull',
5830         COALESCE(NEW.parenthesize, FALSE),
5831         NEW.parent_expr,
5832         COALESCE(NEW.seq_no, 1),
5833                 COALESCE(NEW.negate, false)
5834     );
5835
5836 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5837     ON UPDATE TO query.expr_xnull
5838     DO INSTEAD
5839     UPDATE query.expression SET
5840         id = NEW.id,
5841         parenthesize = NEW.parenthesize,
5842         parent_expr = NEW.parent_expr,
5843         seq_no = NEW.seq_no,
5844                 negate = NEW.negate
5845     WHERE
5846         id = OLD.id;
5847
5848 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5849     ON DELETE TO query.expr_xnull
5850     DO INSTEAD
5851     DELETE FROM query.expression WHERE id = OLD.id;
5852
5853 -- Create updatable view for numeric literal expressions
5854
5855 CREATE OR REPLACE VIEW query.expr_xnum AS
5856     SELECT
5857                 id,
5858                 parenthesize,
5859                 parent_expr,
5860                 seq_no,
5861                 literal
5862     FROM
5863         query.expression
5864     WHERE
5865         type = 'xnum';
5866
5867 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5868     ON INSERT TO query.expr_xnum
5869     DO INSTEAD
5870     INSERT INTO query.expression (
5871                 id,
5872                 type,
5873                 parenthesize,
5874                 parent_expr,
5875                 seq_no,
5876                 literal
5877     ) VALUES (
5878         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5879         'xnum',
5880         COALESCE(NEW.parenthesize, FALSE),
5881         NEW.parent_expr,
5882         COALESCE(NEW.seq_no, 1),
5883         NEW.literal
5884     );
5885
5886 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5887     ON UPDATE TO query.expr_xnum
5888     DO INSTEAD
5889     UPDATE query.expression SET
5890         id = NEW.id,
5891         parenthesize = NEW.parenthesize,
5892         parent_expr = NEW.parent_expr,
5893         seq_no = NEW.seq_no,
5894         literal = NEW.literal
5895     WHERE
5896         id = OLD.id;
5897
5898 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5899     ON DELETE TO query.expr_xnum
5900     DO INSTEAD
5901     DELETE FROM query.expression WHERE id = OLD.id;
5902
5903 -- Create updatable view for operator expressions
5904
5905 CREATE OR REPLACE VIEW query.expr_xop AS
5906     SELECT
5907                 id,
5908                 parenthesize,
5909                 parent_expr,
5910                 seq_no,
5911                 left_operand,
5912                 operator,
5913                 right_operand,
5914                 negate
5915     FROM
5916         query.expression
5917     WHERE
5918         type = 'xop';
5919
5920 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5921     ON INSERT TO query.expr_xop
5922     DO INSTEAD
5923     INSERT INTO query.expression (
5924                 id,
5925                 type,
5926                 parenthesize,
5927                 parent_expr,
5928                 seq_no,
5929                 left_operand,
5930                 operator,
5931                 right_operand,
5932                 negate
5933     ) VALUES (
5934         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5935         'xop',
5936         COALESCE(NEW.parenthesize, FALSE),
5937         NEW.parent_expr,
5938         COALESCE(NEW.seq_no, 1),
5939                 NEW.left_operand,
5940                 NEW.operator,
5941                 NEW.right_operand,
5942                 COALESCE(NEW.negate, false)
5943     );
5944
5945 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5946     ON UPDATE TO query.expr_xop
5947     DO INSTEAD
5948     UPDATE query.expression SET
5949         id = NEW.id,
5950         parenthesize = NEW.parenthesize,
5951         parent_expr = NEW.parent_expr,
5952         seq_no = NEW.seq_no,
5953                 left_operand = NEW.left_operand,
5954                 operator = NEW.operator,
5955                 right_operand = NEW.right_operand,
5956                 negate = NEW.negate
5957     WHERE
5958         id = OLD.id;
5959
5960 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5961     ON DELETE TO query.expr_xop
5962     DO INSTEAD
5963     DELETE FROM query.expression WHERE id = OLD.id;
5964
5965 -- Create updatable view for series expressions
5966 -- i.e. series of expressions separated by operators
5967
5968 CREATE OR REPLACE VIEW query.expr_xser AS
5969     SELECT
5970                 id,
5971                 parenthesize,
5972                 parent_expr,
5973                 seq_no,
5974                 operator,
5975                 negate
5976     FROM
5977         query.expression
5978     WHERE
5979         type = 'xser';
5980
5981 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5982     ON INSERT TO query.expr_xser
5983     DO INSTEAD
5984     INSERT INTO query.expression (
5985                 id,
5986                 type,
5987                 parenthesize,
5988                 parent_expr,
5989                 seq_no,
5990                 operator,
5991                 negate
5992     ) VALUES (
5993         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5994         'xser',
5995         COALESCE(NEW.parenthesize, FALSE),
5996         NEW.parent_expr,
5997         COALESCE(NEW.seq_no, 1),
5998                 NEW.operator,
5999                 COALESCE(NEW.negate, false)
6000     );
6001
6002 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
6003     ON UPDATE TO query.expr_xser
6004     DO INSTEAD
6005     UPDATE query.expression SET
6006         id = NEW.id,
6007         parenthesize = NEW.parenthesize,
6008         parent_expr = NEW.parent_expr,
6009         seq_no = NEW.seq_no,
6010                 operator = NEW.operator,
6011                 negate = NEW.negate
6012     WHERE
6013         id = OLD.id;
6014
6015 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
6016     ON DELETE TO query.expr_xser
6017     DO INSTEAD
6018     DELETE FROM query.expression WHERE id = OLD.id;
6019
6020 -- Create updatable view for string literal expressions
6021
6022 CREATE OR REPLACE VIEW query.expr_xstr AS
6023     SELECT
6024         id,
6025         parenthesize,
6026         parent_expr,
6027         seq_no,
6028         literal
6029     FROM
6030         query.expression
6031     WHERE
6032         type = 'xstr';
6033
6034 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
6035     ON INSERT TO query.expr_xstr
6036     DO INSTEAD
6037     INSERT INTO query.expression (
6038         id,
6039         type,
6040         parenthesize,
6041         parent_expr,
6042         seq_no,
6043         literal
6044     ) VALUES (
6045         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
6046         'xstr',
6047         COALESCE(NEW.parenthesize, FALSE),
6048         NEW.parent_expr,
6049         COALESCE(NEW.seq_no, 1),
6050         NEW.literal
6051     );
6052
6053 CREATE OR REPLACE RULE query_expr_string_update_rule AS
6054     ON UPDATE TO query.expr_xstr
6055     DO INSTEAD
6056     UPDATE query.expression SET
6057         id = NEW.id,
6058         parenthesize = NEW.parenthesize,
6059         parent_expr = NEW.parent_expr,
6060         seq_no = NEW.seq_no,
6061         literal = NEW.literal
6062     WHERE
6063         id = OLD.id;
6064
6065 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
6066     ON DELETE TO query.expr_xstr
6067     DO INSTEAD
6068     DELETE FROM query.expression WHERE id = OLD.id;
6069
6070 -- Create updatable view for subquery expressions
6071
6072 CREATE OR REPLACE VIEW query.expr_xsubq AS
6073     SELECT
6074                 id,
6075                 parenthesize,
6076                 parent_expr,
6077                 seq_no,
6078                 subquery,
6079                 negate
6080     FROM
6081         query.expression
6082     WHERE
6083         type = 'xsubq';
6084
6085 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
6086     ON INSERT TO query.expr_xsubq
6087     DO INSTEAD
6088     INSERT INTO query.expression (
6089                 id,
6090                 type,
6091                 parenthesize,
6092                 parent_expr,
6093                 seq_no,
6094                 subquery,
6095                 negate
6096     ) VALUES (
6097         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
6098         'xsubq',
6099         COALESCE(NEW.parenthesize, FALSE),
6100         NEW.parent_expr,
6101         COALESCE(NEW.seq_no, 1),
6102                 NEW.subquery,
6103                 COALESCE(NEW.negate, false)
6104     );
6105
6106 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
6107     ON UPDATE TO query.expr_xsubq
6108     DO INSTEAD
6109     UPDATE query.expression SET
6110         id = NEW.id,
6111         parenthesize = NEW.parenthesize,
6112         parent_expr = NEW.parent_expr,
6113         seq_no = NEW.seq_no,
6114                 subquery = NEW.subquery,
6115                 negate = NEW.negate
6116     WHERE
6117         id = OLD.id;
6118
6119 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
6120     ON DELETE TO query.expr_xsubq
6121     DO INSTEAD
6122     DELETE FROM query.expression WHERE id = OLD.id;
6123
6124 CREATE TABLE action.fieldset (
6125     id              SERIAL          PRIMARY KEY,
6126     owner           INT             NOT NULL REFERENCES actor.usr (id)
6127                                     DEFERRABLE INITIALLY DEFERRED,
6128     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
6129                                     DEFERRABLE INITIALLY DEFERRED,
6130     status          TEXT            NOT NULL
6131                                     CONSTRAINT valid_status CHECK ( status in
6132                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6133     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6134     scheduled_time  TIMESTAMPTZ,
6135     applied_time    TIMESTAMPTZ,
6136     classname       TEXT            NOT NULL, -- an IDL class name
6137     name            TEXT            NOT NULL,
6138     stored_query    INT             REFERENCES query.stored_query (id)
6139                                     DEFERRABLE INITIALLY DEFERRED,
6140     pkey_value      TEXT,
6141     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6142     CONSTRAINT fieldset_one_or_the_other CHECK (
6143         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6144         (pkey_value IS NOT NULL AND stored_query IS NULL)
6145     )
6146     -- the CHECK constraint means we can update the fields for a single
6147     -- row without all the extra overhead involved in a query
6148 );
6149
6150 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6151 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6152
6153 CREATE TABLE action.fieldset_col_val (
6154     id              SERIAL  PRIMARY KEY,
6155     fieldset        INT     NOT NULL REFERENCES action.fieldset
6156                                          ON DELETE CASCADE
6157                                          DEFERRABLE INITIALLY DEFERRED,
6158     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6159     val             TEXT,              -- value for the column ... NULL means, well, NULL
6160     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6161 );
6162
6163 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6164         fieldset_id IN INT,        -- id from action.fieldset
6165         table_name  IN TEXT,       -- table to be updated
6166         pkey_name   IN TEXT,       -- name of primary key column in that table
6167         query       IN TEXT        -- query constructed by qstore (for query-based
6168                                    --    fieldsets only; otherwise null
6169 )
6170 RETURNS TEXT AS $$
6171 DECLARE
6172         statement TEXT;
6173         fs_status TEXT;
6174         fs_pkey_value TEXT;
6175         fs_query TEXT;
6176         sep CHAR;
6177         status_code TEXT;
6178         msg TEXT;
6179         update_count INT;
6180         cv RECORD;
6181 BEGIN
6182         -- Sanity checks
6183         IF fieldset_id IS NULL THEN
6184                 RETURN 'Fieldset ID parameter is NULL';
6185         END IF;
6186         IF table_name IS NULL THEN
6187                 RETURN 'Table name parameter is NULL';
6188         END IF;
6189         IF pkey_name IS NULL THEN
6190                 RETURN 'Primary key name parameter is NULL';
6191         END IF;
6192         --
6193         statement := 'UPDATE ' || table_name || ' SET';
6194         --
6195         SELECT
6196                 status,
6197                 quote_literal( pkey_value )
6198         INTO
6199                 fs_status,
6200                 fs_pkey_value
6201         FROM
6202                 action.fieldset
6203         WHERE
6204                 id = fieldset_id;
6205         --
6206         IF fs_status IS NULL THEN
6207                 RETURN 'No fieldset found for id = ' || fieldset_id;
6208         ELSIF fs_status = 'APPLIED' THEN
6209                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6210         END IF;
6211         --
6212         sep := '';
6213         FOR cv IN
6214                 SELECT  col,
6215                                 val
6216                 FROM    action.fieldset_col_val
6217                 WHERE   fieldset = fieldset_id
6218         LOOP
6219                 statement := statement || sep || ' ' || cv.col
6220                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6221                 sep := ',';
6222         END LOOP;
6223         --
6224         IF sep = '' THEN
6225                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6226         END IF;
6227         --
6228         -- Add the WHERE clause.  This differs according to whether it's a
6229         -- single-row fieldset or a query-based fieldset.
6230         --
6231         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6232                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6233         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6234             fs_query := rtrim( query, ';' );
6235             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6236                          || fs_query || ' );';
6237         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6238                 statement := statement || ' WHERE ' || pkey_name || ' = '
6239                                      || fs_pkey_value || ';';
6240         ELSE  -- both are not null
6241                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6242         END IF;
6243         --
6244         -- Execute the update
6245         --
6246         BEGIN
6247                 EXECUTE statement;
6248                 GET DIAGNOSTICS update_count = ROW_COUNT;
6249                 --
6250                 IF UPDATE_COUNT > 0 THEN
6251                         status_code := 'APPLIED';
6252                         msg := NULL;
6253                 ELSE
6254                         status_code := 'ERROR';
6255                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6256         END IF;
6257         EXCEPTION WHEN OTHERS THEN
6258                 status_code := 'ERROR';
6259                 msg := 'Unable to apply fieldset ' || fieldset_id
6260                            || ': ' || sqlerrm;
6261         END;
6262         --
6263         -- Update fieldset status
6264         --
6265         UPDATE action.fieldset
6266         SET status       = status_code,
6267             applied_time = now()
6268         WHERE id = fieldset_id;
6269         --
6270         RETURN msg;
6271 END;
6272 $$ LANGUAGE plpgsql;
6273
6274 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6275 /**
6276  * Applies a specified fieldset, using a supplied table name and primary
6277  * key name.  The query parameter should be non-null only for
6278  * query-based fieldsets.
6279  *
6280  * Returns NULL if successful, or an error message if not.
6281  */
6282 $$;
6283
6284 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6285
6286 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6287     SELECT  u.hold,
6288             c.circ_lib,
6289             count(*)
6290       FROM  action.unfulfilled_hold_list u
6291             JOIN asset.copy c ON (c.id = u.current_copy)
6292       GROUP BY 1,2;
6293
6294 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6295     SELECT  hold,
6296             min(count)
6297       FROM  action.unfulfilled_hold_loops
6298       GROUP BY 1;
6299
6300 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6301     SELECT  DISTINCT l.*
6302       FROM  action.unfulfilled_hold_loops l
6303             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6304       WHERE l.count = m.min;
6305
6306 ALTER TABLE asset.copy
6307 ADD COLUMN dummy_isbn TEXT;
6308
6309 ALTER TABLE auditor.asset_copy_history
6310 ADD COLUMN dummy_isbn TEXT;
6311
6312 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6313 -- Add corresponding new column to auditor.asset_copy_history
6314
6315 ALTER TABLE asset.copy
6316         ADD COLUMN status_changed_time TIMESTAMPTZ;
6317
6318 ALTER TABLE auditor.asset_copy_history
6319         ADD COLUMN status_changed_time TIMESTAMPTZ;
6320
6321 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6322 RETURNS TRIGGER AS $$
6323 BEGIN
6324     IF NEW.status <> OLD.status THEN
6325         NEW.status_changed_time := now();
6326     END IF;
6327     RETURN NEW;
6328 END;
6329 $$ LANGUAGE plpgsql;
6330
6331 CREATE TRIGGER acp_status_changed_trig
6332         BEFORE UPDATE ON asset.copy
6333         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6334
6335 ALTER TABLE asset.copy
6336 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6337
6338 ALTER TABLE auditor.asset_copy_history
6339 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6340
6341 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6342 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6343
6344 DROP INDEX IF EXISTS asset.copy_barcode_key;
6345 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6346
6347 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6348 -- AFTER INSERT OR UPDATE ON asset.copy
6349
6350 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6351 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6352
6353 -- Moke mostly parallel changes to action.circulation
6354 -- and action.aged_circulation
6355
6356 ALTER TABLE action.circulation
6357 ADD COLUMN workstation INT
6358     REFERENCES actor.workstation
6359         ON DELETE SET NULL
6360         DEFERRABLE INITIALLY DEFERRED;
6361
6362 ALTER TABLE action.aged_circulation
6363 ADD COLUMN workstation INT;
6364
6365 ALTER TABLE action.circulation
6366 ADD COLUMN parent_circ BIGINT
6367         REFERENCES action.circulation(id)
6368         DEFERRABLE INITIALLY DEFERRED;
6369
6370 CREATE UNIQUE INDEX circ_parent_idx
6371 ON action.circulation( parent_circ )
6372 WHERE parent_circ IS NOT NULL;
6373
6374 ALTER TABLE action.aged_circulation
6375 ADD COLUMN parent_circ BIGINT;
6376
6377 ALTER TABLE action.circulation
6378 ADD COLUMN checkin_workstation INT
6379         REFERENCES actor.workstation(id)
6380         ON DELETE SET NULL
6381         DEFERRABLE INITIALLY DEFERRED;
6382
6383 ALTER TABLE action.aged_circulation
6384 ADD COLUMN checkin_workstation INT;
6385
6386 ALTER TABLE action.circulation
6387 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6388
6389 ALTER TABLE action.aged_circulation
6390 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6391
6392 CREATE INDEX action_circulation_target_copy_idx
6393 ON action.circulation (target_copy);
6394
6395 CREATE INDEX action_aged_circulation_target_copy_idx
6396 ON action.aged_circulation (target_copy);
6397
6398 ALTER TABLE action.circulation
6399 DROP CONSTRAINT circulation_stop_fines_check;
6400
6401 ALTER TABLE action.circulation
6402         ADD CONSTRAINT circulation_stop_fines_check
6403         CHECK (stop_fines IN (
6404         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6405
6406 -- Hard due-date functionality
6407 CREATE TABLE config.hard_due_date (
6408         id          SERIAL      PRIMARY KEY,
6409         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6410         ceiling_date    TIMESTAMPTZ NOT NULL,
6411         forceto     BOOL        NOT NULL,
6412         owner       INT         NOT NULL
6413 );
6414
6415 CREATE TABLE config.hard_due_date_values (
6416     id                  SERIAL      PRIMARY KEY,
6417     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6418                                     DEFERRABLE INITIALLY DEFERRED,
6419     ceiling_date        TIMESTAMPTZ NOT NULL,
6420     active_date         TIMESTAMPTZ NOT NULL
6421 );
6422
6423 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6424
6425 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6426 DECLARE
6427     temp_value  config.hard_due_date_values%ROWTYPE;
6428     updated     INT := 0;
6429 BEGIN
6430     FOR temp_value IN
6431       SELECT  DISTINCT ON (hard_due_date) *
6432         FROM  config.hard_due_date_values
6433         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6434         ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
6435    LOOP
6436         UPDATE  config.hard_due_date
6437           SET   ceiling_date = temp_value.ceiling_date
6438           WHERE id = temp_value.hard_due_date
6439                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6440
6441         IF FOUND THEN
6442             updated := updated + 1;
6443         END IF;
6444     END LOOP;
6445
6446     RETURN updated;
6447 END;
6448 $func$ LANGUAGE plpgsql;
6449
6450 -- Correct some long-standing misspellings involving variations of "recur"
6451
6452 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6453 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6454
6455 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6456 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6457
6458 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6459 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6460
6461 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6462
6463 -- Might as well keep the comment in sync as well
6464 COMMENT ON TABLE config.rule_recurring_fine IS $$
6465 /*
6466  * Copyright (C) 2005  Georgia Public Library Service 
6467  * Mike Rylander <mrylander@gmail.com>
6468  *
6469  * Circulation Recurring Fine rules
6470  *
6471  * Each circulation is given a recurring fine amount based on one of
6472  * these rules.  The recurrence_interval should not be any shorter
6473  * than the interval between runs of the fine_processor.pl script
6474  * (which is run from CRON), or you could miss fines.
6475  * 
6476  *
6477  * ****
6478  *
6479  * This program is free software; you can redistribute it and/or
6480  * modify it under the terms of the GNU General Public License
6481  * as published by the Free Software Foundation; either version 2
6482  * of the License, or (at your option) any later version.
6483  *
6484  * This program is distributed in the hope that it will be useful,
6485  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6486  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6487  * GNU General Public License for more details.
6488  */
6489 $$;
6490
6491 -- Extend the name change to some related views:
6492
6493 DROP VIEW IF EXISTS reporter.overdue_circs;
6494
6495 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6496 SELECT  *
6497   FROM  action.circulation
6498     WHERE checkin_time is null
6499                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6500                                 AND due_date < now();
6501
6502 DROP VIEW IF EXISTS stats.fleshed_circulation;
6503
6504 DROP VIEW IF EXISTS stats.fleshed_copy;
6505
6506 CREATE VIEW stats.fleshed_copy AS
6507         SELECT  cp.*,
6508         CAST(cp.create_date AS DATE) AS create_date_day,
6509         CAST(cp.edit_date AS DATE) AS edit_date_day,
6510         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6511         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6512                 cn.label AS call_number_label,
6513                 cn.owning_lib,
6514                 rd.item_lang,
6515                 rd.item_type,
6516                 rd.item_form
6517         FROM    asset.copy cp
6518                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6519                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6520
6521 CREATE VIEW stats.fleshed_circulation AS
6522         SELECT  c.*,
6523                 CAST(c.xact_start AS DATE) AS start_date_day,
6524                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6525                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6526                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6527                 cp.call_number_label,
6528                 cp.owning_lib,
6529                 cp.item_lang,
6530                 cp.item_type,
6531                 cp.item_form
6532         FROM    action.circulation c
6533                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6534
6535 -- Drop a view temporarily in order to alter action.all_circulation, upon
6536 -- which it is dependent.  We will recreate the view later.
6537
6538 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6539
6540 -- You would think that CREATE OR REPLACE would be enough, but in testing
6541 -- PostgreSQL complained about renaming the columns in the view. So we
6542 -- drop the view first.
6543 DROP VIEW IF EXISTS action.all_circulation;
6544
6545 CREATE OR REPLACE VIEW action.all_circulation AS
6546     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6547         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6548         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6549         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6550         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6551         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6552       FROM  action.aged_circulation
6553             UNION ALL
6554     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,
6555         cp.call_number AS copy_call_number, cp.location AS copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
6556         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6557         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6558         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6559         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6560         circ.parent_circ
6561       FROM  action.circulation circ
6562         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6563         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6564         JOIN actor.usr p ON (circ.usr = p.id)
6565         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6566         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6567
6568 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6569
6570 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6571  SELECT cp.id, COALESCE(sum(c.circ_count), 0::bigint) + COALESCE(count(circ.id), 0::bigint) + COALESCE(count(acirc.id), 0::bigint) AS circ_count
6572    FROM asset."copy" cp
6573    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6574    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6575    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6576   GROUP BY cp.id;
6577
6578 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6579
6580 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6581
6582 -- Rebuild dependent views
6583
6584 DROP VIEW IF EXISTS action.billable_circulations;
6585
6586 CREATE OR REPLACE VIEW action.billable_circulations AS
6587     SELECT  *
6588       FROM  action.circulation
6589       WHERE xact_finish IS NULL;
6590
6591 DROP VIEW IF EXISTS action.open_circulation;
6592
6593 CREATE OR REPLACE VIEW action.open_circulation AS
6594     SELECT  *
6595       FROM  action.circulation
6596       WHERE checkin_time IS NULL
6597       ORDER BY due_date;
6598
6599 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6600 DECLARE
6601 found char := 'N';
6602 BEGIN
6603
6604     -- If there are any renewals for this circulation, don't archive or delete
6605     -- it yet.   We'll do so later, when we archive and delete the renewals.
6606
6607     SELECT 'Y' INTO found
6608     FROM action.circulation
6609     WHERE parent_circ = OLD.id
6610     LIMIT 1;
6611
6612     IF found = 'Y' THEN
6613         RETURN NULL;  -- don't delete
6614         END IF;
6615
6616     -- Archive a copy of the old row to action.aged_circulation
6617
6618     INSERT INTO action.aged_circulation
6619         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6620         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6621         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6622         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6623         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6624         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6625       SELECT
6626         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6627         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6628         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6629         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6630         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6631         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6632         FROM action.all_circulation WHERE id = OLD.id;
6633
6634     RETURN OLD;
6635 END;
6636 $$ LANGUAGE 'plpgsql';
6637
6638 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6639
6640 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6641
6642 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6643
6644 CREATE OR REPLACE FUNCTION action.find_hold_matrix_matchpoint( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS INT AS $func$
6645 DECLARE
6646     current_requestor_group    permission.grp_tree%ROWTYPE;
6647     requestor_object    actor.usr%ROWTYPE;
6648     user_object        actor.usr%ROWTYPE;
6649     item_object        asset.copy%ROWTYPE;
6650     item_cn_object        asset.call_number%ROWTYPE;
6651     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6652     current_mp_weight    FLOAT;
6653     matchpoint_weight    FLOAT;
6654     tmp_weight        FLOAT;
6655     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6656     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6657 BEGIN
6658     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6659     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6660     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6661     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6662     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6663
6664     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6665
6666     IF NOT FOUND THEN
6667         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6668     ELSE
6669         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6670     END IF;
6671
6672     LOOP 
6673         -- for each potential matchpoint for this ou and group ...
6674         FOR current_mp IN
6675             SELECT    m.*
6676               FROM    config.hold_matrix_matchpoint m
6677               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6678               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6679                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6680                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6681                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6682                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6683                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6684
6685             current_mp_weight := 5.0;
6686
6687             IF current_mp.circ_modifier IS NOT NULL THEN
6688                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6689             END IF;
6690
6691             IF current_mp.marc_type IS NOT NULL THEN
6692                 IF item_object.circ_as_type IS NOT NULL THEN
6693                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6694                 ELSE
6695                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6696                 END IF;
6697             END IF;
6698
6699             IF current_mp.marc_form IS NOT NULL THEN
6700                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6701             END IF;
6702
6703             IF current_mp.marc_vr_format IS NOT NULL THEN
6704                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6705             END IF;
6706
6707             IF current_mp.juvenile_flag IS NOT NULL THEN
6708                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6709             END IF;
6710
6711             IF current_mp.ref_flag IS NOT NULL THEN
6712                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6713             END IF;
6714
6715
6716             -- caclulate the rule match weight
6717             IF current_mp.item_owning_ou IS NOT NULL THEN
6718                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6719                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6720                 current_mp_weight := current_mp_weight - tmp_weight;
6721             END IF; 
6722
6723             IF current_mp.item_circ_ou IS NOT NULL THEN
6724                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6725                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6726                 current_mp_weight := current_mp_weight - tmp_weight;
6727             END IF; 
6728
6729             IF current_mp.pickup_ou IS NOT NULL THEN
6730                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6731                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6732                 current_mp_weight := current_mp_weight - tmp_weight;
6733             END IF; 
6734
6735             IF current_mp.request_ou IS NOT NULL THEN
6736                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6737                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6738                 current_mp_weight := current_mp_weight - tmp_weight;
6739             END IF; 
6740
6741             IF current_mp.user_home_ou IS NOT NULL THEN
6742                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6743                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6744                 current_mp_weight := current_mp_weight - tmp_weight;
6745             END IF; 
6746
6747             -- set the matchpoint if we found the best one
6748             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6749                 matchpoint = current_mp;
6750                 matchpoint_weight = current_mp_weight;
6751             END IF;
6752
6753         END LOOP;
6754
6755         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6756
6757         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6758     END LOOP;
6759
6760     RETURN matchpoint.id;
6761 END;
6762 $func$ LANGUAGE plpgsql;
6763
6764 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$
6765 DECLARE
6766     matchpoint_id        INT;
6767     user_object        actor.usr%ROWTYPE;
6768     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6769     standing_penalty    config.standing_penalty%ROWTYPE;
6770     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6771     transit_source        actor.org_unit%ROWTYPE;
6772     item_object        asset.copy%ROWTYPE;
6773     ou_skip              actor.org_unit_setting%ROWTYPE;
6774     result            action.matrix_test_result;
6775     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6776     hold_count        INT;
6777     hold_transit_prox    INT;
6778     frozen_hold_count    INT;
6779     context_org_list    INT[];
6780     done            BOOL := FALSE;
6781 BEGIN
6782     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6783     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6784
6785     result.success := TRUE;
6786
6787     -- Fail if we couldn't find a user
6788     IF user_object.id IS NULL THEN
6789         result.fail_part := 'no_user';
6790         result.success := FALSE;
6791         done := TRUE;
6792         RETURN NEXT result;
6793         RETURN;
6794     END IF;
6795
6796     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6797
6798     -- Fail if we couldn't find a copy
6799     IF item_object.id IS NULL THEN
6800         result.fail_part := 'no_item';
6801         result.success := FALSE;
6802         done := TRUE;
6803         RETURN NEXT result;
6804         RETURN;
6805     END IF;
6806
6807     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6808     result.matchpoint := matchpoint_id;
6809
6810     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6811
6812     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6813     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6814         result.fail_part := 'circ.holds.target_skip_me';
6815         result.success := FALSE;
6816         done := TRUE;
6817         RETURN NEXT result;
6818         RETURN;
6819     END IF;
6820
6821     -- Fail if user is barred
6822     IF user_object.barred IS TRUE THEN
6823         result.fail_part := 'actor.usr.barred';
6824         result.success := FALSE;
6825         done := TRUE;
6826         RETURN NEXT result;
6827         RETURN;
6828     END IF;
6829
6830     -- Fail if we couldn't find any matchpoint (requires a default)
6831     IF matchpoint_id IS NULL THEN
6832         result.fail_part := 'no_matchpoint';
6833         result.success := FALSE;
6834         done := TRUE;
6835         RETURN NEXT result;
6836         RETURN;
6837     END IF;
6838
6839     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6840
6841     IF hold_test.holdable IS FALSE THEN
6842         result.fail_part := 'config.hold_matrix_test.holdable';
6843         result.success := FALSE;
6844         done := TRUE;
6845         RETURN NEXT result;
6846     END IF;
6847
6848     IF hold_test.transit_range IS NOT NULL THEN
6849         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6850         IF hold_test.distance_is_from_owner THEN
6851             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;
6852         ELSE
6853             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6854         END IF;
6855
6856         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6857
6858         IF NOT FOUND THEN
6859             result.fail_part := 'transit_range';
6860             result.success := FALSE;
6861             done := TRUE;
6862             RETURN NEXT result;
6863         END IF;
6864     END IF;
6865  
6866     IF NOT retargetting THEN
6867         FOR standing_penalty IN
6868             SELECT  DISTINCT csp.*
6869               FROM  actor.usr_standing_penalty usp
6870                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6871               WHERE usr = match_user
6872                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6873                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6874                     AND csp.block_list LIKE '%HOLD%' LOOP
6875     
6876             result.fail_part := standing_penalty.name;
6877             result.success := FALSE;
6878             done := TRUE;
6879             RETURN NEXT result;
6880         END LOOP;
6881     
6882         IF hold_test.stop_blocked_user IS TRUE THEN
6883             FOR standing_penalty IN
6884                 SELECT  DISTINCT csp.*
6885                   FROM  actor.usr_standing_penalty usp
6886                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6887                   WHERE usr = match_user
6888                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6889                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6890                         AND csp.block_list LIKE '%CIRC%' LOOP
6891         
6892                 result.fail_part := standing_penalty.name;
6893                 result.success := FALSE;
6894                 done := TRUE;
6895                 RETURN NEXT result;
6896             END LOOP;
6897         END IF;
6898     
6899         IF hold_test.max_holds IS NOT NULL THEN
6900             SELECT    INTO hold_count COUNT(*)
6901               FROM    action.hold_request
6902               WHERE    usr = match_user
6903                 AND fulfillment_time IS NULL
6904                 AND cancel_time IS NULL
6905                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6906     
6907             IF hold_count >= hold_test.max_holds THEN
6908                 result.fail_part := 'config.hold_matrix_test.max_holds';
6909                 result.success := FALSE;
6910                 done := TRUE;
6911                 RETURN NEXT result;
6912             END IF;
6913         END IF;
6914     END IF;
6915
6916     IF item_object.age_protect IS NOT NULL THEN
6917         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6918
6919         IF item_object.create_date + age_protect_object.age > NOW() THEN
6920             IF hold_test.distance_is_from_owner THEN
6921                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6922             ELSE
6923                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6924             END IF;
6925
6926             IF hold_transit_prox > age_protect_object.prox THEN
6927                 result.fail_part := 'config.rule_age_hold_protect.prox';
6928                 result.success := FALSE;
6929                 done := TRUE;
6930                 RETURN NEXT result;
6931             END IF;
6932         END IF;
6933     END IF;
6934
6935     IF NOT done THEN
6936         RETURN NEXT result;
6937     END IF;
6938
6939     RETURN;
6940 END;
6941 $func$ LANGUAGE plpgsql;
6942
6943 CREATE OR REPLACE FUNCTION action.hold_request_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
6944     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6945 $func$ LANGUAGE SQL;
6946
6947 CREATE OR REPLACE FUNCTION action.hold_retarget_permit_test( pickup_ou INT, request_ou INT, match_item BIGINT, match_user INT, match_requestor INT ) RETURNS SETOF action.matrix_test_result AS $func$
6948     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6949 $func$ LANGUAGE SQL;
6950
6951 -- New post-delete trigger to propagate deletions to parent(s)
6952
6953 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6954 BEGIN
6955
6956     -- Having deleted a renewal, we can delete the original circulation (or a previous
6957     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6958     -- deletion of any prior parents, etc. recursively.
6959
6960     IF OLD.parent_circ IS NOT NULL THEN
6961         DELETE FROM action.circulation
6962         WHERE id = OLD.parent_circ;
6963     END IF;
6964
6965     RETURN OLD;
6966 END;
6967 $$ LANGUAGE 'plpgsql';
6968
6969 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6970 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6971
6972 -- This only gets inserted if there are no other id > 100 billing types
6973 INSERT INTO config.billing_type (id, name, owner) SELECT DISTINCT 101, oils_i18n_gettext(101, 'Misc', 'cbt', 'name'), 1 FROM config.billing_type_id_seq WHERE last_value < 101;
6974 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6975
6976 -- Populate xact_type column in the materialized version of billable_xact_summary
6977
6978 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6979 BEGIN
6980         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6981                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6982         RETURN NEW;
6983 END;
6984 $$ LANGUAGE PLPGSQL;
6985  
6986 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6987 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6988  
6989 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6990 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6991
6992 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6993     UPDATE money.payment SET xact = NEW.xact, payment_ts = NEW.payment_ts, voided = NEW.voided, amount = NEW.amount, note = NEW.note WHERE id = NEW.id;
6994
6995 -- Generate the equivalent of compound subject entries from the existing rows
6996 -- so that we don't have to laboriously reindex them
6997
6998 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6999 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
7000 --
7001 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
7002 --
7003 --INSERT INTO metabib.subject_field_entry (source, field, value)
7004 --    SELECT source, (
7005 --            SELECT id 
7006 --            FROM config.metabib_field
7007 --            WHERE field_class = 'subject' AND name = 'complete'
7008 --        ), 
7009 --        ARRAY_TO_STRING ( 
7010 --            ARRAY (
7011 --                SELECT value 
7012 --                FROM metabib.subject_field_entry msfe
7013 --                WHERE msfe.source = groupee.source
7014 --                ORDER BY source 
7015 --            ), ' ' 
7016 --        ) AS grouped
7017 --    FROM ( 
7018 --        SELECT source
7019 --        FROM metabib.subject_field_entry
7020 --        GROUP BY source
7021 --    ) AS groupee;
7022
7023 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
7024 DECLARE
7025         prev_billing    money.billing%ROWTYPE;
7026         old_billing     money.billing%ROWTYPE;
7027 BEGIN
7028         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
7029         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
7030
7031         IF OLD.id = old_billing.id THEN
7032                 UPDATE  money.materialized_billable_xact_summary
7033                   SET   last_billing_ts = prev_billing.billing_ts,
7034                         last_billing_note = prev_billing.note,
7035                         last_billing_type = prev_billing.billing_type
7036                   WHERE id = OLD.xact;
7037         END IF;
7038
7039         IF NOT OLD.voided THEN
7040                 UPDATE  money.materialized_billable_xact_summary
7041                   SET   total_owed = total_owed - OLD.amount,
7042                         balance_owed = balance_owed + OLD.amount
7043                   WHERE id = OLD.xact;
7044         END IF;
7045
7046         RETURN OLD;
7047 END;
7048 $$ LANGUAGE PLPGSQL;
7049
7050 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
7051 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
7052
7053 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
7054
7055     use strict;
7056     use Unicode::Normalize;
7057     use Encode;
7058
7059     my $str = decode_utf8(shift);
7060     my $sf = shift;
7061
7062     # Apply NACO normalization to input string; based on
7063     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
7064     #
7065     # Note that unlike a strict reading of the NACO normalization rules,
7066     # output is returned as lowercase instead of uppercase for compatibility
7067     # with previous versions of the Evergreen naco_normalize routine.
7068
7069     # Convert to upper-case first; even though final output will be lowercase, doing this will
7070     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
7071     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
7072     $str = uc $str;
7073
7074     # remove non-filing strings
7075     $str =~ s/\x{0098}.*?\x{009C}//g;
7076
7077     $str = NFKD($str);
7078
7079     # additional substitutions - 3.6.
7080     $str =~ s/\x{00C6}/AE/g;
7081     $str =~ s/\x{00DE}/TH/g;
7082     $str =~ s/\x{0152}/OE/g;
7083     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}]['/DDOLl/d;
7084
7085     # transformations based on Unicode category codes
7086     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
7087
7088         if ($sf && $sf =~ /^a/o) {
7089                 my $commapos = index($str, ',');
7090                 if ($commapos > -1) {
7091                         if ($commapos != length($str) - 1) {
7092                 $str =~ s/,/\x07/; # preserve first comma
7093                         }
7094                 }
7095         }
7096
7097     # since we've stripped out the control characters, we can now
7098     # use a few as placeholders temporarily
7099     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
7100     $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;
7101     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
7102
7103     # decimal digits
7104     $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/;
7105
7106     # intentionally skipping step 8 of the NACO algorithm; if the string
7107     # gets normalized away, that's fine.
7108
7109     # leading and trailing spaces
7110     $str =~ s/\s+/ /g;
7111     $str =~ s/^\s+//;
7112     $str =~ s/\s+$//g;
7113
7114     return lc $str;
7115 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7116
7117 -- Some handy functions, based on existing ones, to provide optional ingest normalization
7118
7119 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7120         SELECT SUBSTRING($1,$2);
7121 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7122
7123 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7124         SELECT SUBSTRING($1,1,$2);
7125 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7126
7127 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
7128         SELECT public.naco_normalize($1,'a');
7129 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7130
7131 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
7132         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
7133 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7134
7135 -- And ... a table in which to register them
7136
7137 CREATE TABLE config.index_normalizer (
7138         id              SERIAL  PRIMARY KEY,
7139         name            TEXT    UNIQUE NOT NULL,
7140         description     TEXT,
7141         func            TEXT    NOT NULL,
7142         param_count     INT     NOT NULL DEFAULT 0
7143 );
7144
7145 CREATE TABLE config.metabib_field_index_norm_map (
7146         id      SERIAL  PRIMARY KEY,
7147         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7148         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7149         params  TEXT,
7150         pos     INT     NOT NULL DEFAULT 0
7151 );
7152
7153 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7154         'NACO Normalize',
7155         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7156         'naco_normalize',
7157         0
7158 );
7159
7160 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7161         'Normalize date range',
7162         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7163         'split_date_range',
7164         1
7165 );
7166
7167 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7168         'NACO Normalize -- retain first comma',
7169         'Apply NACO normalization rules to the extracted text, retaining the first comma.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7170         'naco_normalize_keep_comma',
7171         0
7172 );
7173
7174 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7175         'Strip Diacritics',
7176         'Convert text to NFD form and remove non-spacing combining marks.',
7177         'remove_diacritics',
7178         0
7179 );
7180
7181 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7182         'Up-case',
7183         'Convert text upper case.',
7184         'uppercase',
7185         0
7186 );
7187
7188 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7189         'Down-case',
7190         'Convert text lower case.',
7191         'lowercase',
7192         0
7193 );
7194
7195 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7196         'Extract Dewey-like number',
7197         'Extract a string of numeric characters ther resembles a DDC number.',
7198         'call_number_dewey',
7199         0
7200 );
7201
7202 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7203         'Left truncation',
7204         'Discard the specified number of characters from the left side of the string.',
7205         'left_trunc',
7206         1
7207 );
7208
7209 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7210         'Right truncation',
7211         'Include only the specified number of characters from the left side of the string.',
7212         'right_trunc',
7213         1
7214 );
7215
7216 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7217         'First word',
7218         'Include only the first space-separated word of a string.',
7219         'first_word',
7220         0
7221 );
7222
7223 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7224         SELECT  m.id,
7225                 i.id
7226           FROM  config.metabib_field m,
7227                 config.index_normalizer i
7228           WHERE i.func IN ('naco_normalize','split_date_range');
7229
7230 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7231 DECLARE
7232     normalizer      RECORD;
7233     value           TEXT := '';
7234 BEGIN
7235
7236     value := NEW.value;
7237
7238     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7239         FOR normalizer IN
7240             SELECT  n.func AS func,
7241                     n.param_count AS param_count,
7242                     m.params AS params
7243               FROM  config.index_normalizer n
7244                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7245               WHERE field = NEW.field AND m.pos < 0
7246               ORDER BY m.pos LOOP
7247                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7248                     quote_literal( value ) ||
7249                     CASE
7250                         WHEN normalizer.param_count > 0
7251                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7252                             ELSE ''
7253                         END ||
7254                     ')' INTO value;
7255
7256         END LOOP;
7257
7258         NEW.value := value;
7259     END IF;
7260
7261     IF NEW.index_vector = ''::tsvector THEN
7262         RETURN NEW;
7263     END IF;
7264
7265     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7266         FOR normalizer IN
7267             SELECT  n.func AS func,
7268                     n.param_count AS param_count,
7269                     m.params AS params
7270               FROM  config.index_normalizer n
7271                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7272               WHERE field = NEW.field AND m.pos >= 0
7273               ORDER BY m.pos LOOP
7274                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7275                     quote_literal( value ) ||
7276                     CASE
7277                         WHEN normalizer.param_count > 0
7278                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7279                             ELSE ''
7280                         END ||
7281                     ')' INTO value;
7282
7283         END LOOP;
7284     END IF;
7285
7286     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7287         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7288     ELSE
7289         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7290     END IF;
7291
7292     RETURN NEW;
7293 END;
7294 $$ LANGUAGE PLPGSQL;
7295
7296 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7297
7298 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7299
7300 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7301     SELECT  ARRAY_TO_STRING(
7302                 oils_xpath(
7303                     $1 ||
7304                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7305                     $2,
7306                     $4
7307                 ),
7308                 $3
7309             );
7310 $func$ LANGUAGE SQL IMMUTABLE;
7311
7312 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7313     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7314 $func$ LANGUAGE SQL IMMUTABLE;
7315
7316 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7317     SELECT oils_xpath_string( $1, $2, '', $3 );
7318 $func$ LANGUAGE SQL IMMUTABLE;
7319
7320 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7321     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7322 $func$ LANGUAGE SQL IMMUTABLE;
7323
7324 CREATE TYPE metabib.field_entry_template AS (
7325         field_class     TEXT,
7326         field           INT,
7327         source          BIGINT,
7328         value           TEXT
7329 );
7330
7331 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7332   use strict;
7333
7334   use XML::LibXSLT;
7335   use XML::LibXML;
7336
7337   my $doc = shift;
7338   my $xslt = shift;
7339
7340   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7341   # methods of parsing XML documents and stylesheets, in the hopes of broader
7342   # compatibility with distributions
7343   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7344
7345   # Cache the XML parser, if we do not already have one
7346   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7347     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7348
7349   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7350
7351   # Cache the XSLT processor, if we do not already have one
7352   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7353     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7354
7355   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7356     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7357
7358   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7359     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7360
7361   return $stylesheet->output_string(
7362     $stylesheet->transform(
7363       $parser->parse_string($doc)
7364     )
7365   );
7366
7367 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7368
7369 -- Add two columns so that the following function will compile.
7370 -- Eventually the label column will be NOT NULL, but not yet.
7371 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7372 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7373
7374 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7375 DECLARE
7376     bib     biblio.record_entry%ROWTYPE;
7377     idx     config.metabib_field%ROWTYPE;
7378     xfrm        config.xml_transform%ROWTYPE;
7379     prev_xfrm   TEXT;
7380     transformed_xml TEXT;
7381     xml_node    TEXT;
7382     xml_node_list   TEXT[];
7383     facet_text  TEXT;
7384     raw_text    TEXT;
7385     curr_text   TEXT;
7386     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7387     output_row  metabib.field_entry_template%ROWTYPE;
7388 BEGIN
7389
7390     -- Get the record
7391     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7392
7393     -- Loop over the indexing entries
7394     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7395
7396         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7397
7398         -- See if we can skip the XSLT ... it's expensive
7399         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7400             -- Can't skip the transform
7401             IF xfrm.xslt <> '---' THEN
7402                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7403             ELSE
7404                 transformed_xml := bib.marc;
7405             END IF;
7406
7407             prev_xfrm := xfrm.name;
7408         END IF;
7409
7410         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7411
7412         raw_text := NULL;
7413         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7414             CONTINUE WHEN xml_node !~ E'^\\s*<';
7415
7416             curr_text := ARRAY_TO_STRING(
7417                 oils_xpath( '//text()',
7418                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7419                         REGEXP_REPLACE( -- This escapes embeded <s
7420                             xml_node,
7421                             $re$(>[^<]+)(<)([^>]+<)$re$,
7422                             E'\\1&lt;\\3',
7423                             'g'
7424                         ),
7425                         '&(?!amp;)',
7426                         '&amp;',
7427                         'g'
7428                     )
7429                 ),
7430                 ' '
7431             );
7432
7433             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7434
7435             IF raw_text IS NOT NULL THEN
7436                 raw_text := raw_text || joiner;
7437             END IF;
7438
7439             raw_text := COALESCE(raw_text,'') || curr_text;
7440
7441             -- insert raw node text for faceting
7442             IF idx.facet_field THEN
7443
7444                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7445                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7446                 ELSE
7447                     facet_text := curr_text;
7448                 END IF;
7449
7450                 output_row.field_class = idx.field_class;
7451                 output_row.field = -1 * idx.id;
7452                 output_row.source = rid;
7453                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7454
7455                 RETURN NEXT output_row;
7456             END IF;
7457
7458         END LOOP;
7459
7460         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7461
7462         -- insert combined node text for searching
7463         IF idx.search_field THEN
7464             output_row.field_class = idx.field_class;
7465             output_row.field = idx.id;
7466             output_row.source = rid;
7467             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7468
7469             RETURN NEXT output_row;
7470         END IF;
7471
7472     END LOOP;
7473
7474 END;
7475 $func$ LANGUAGE PLPGSQL;
7476
7477 -- default to a space joiner
7478 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7479         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7480 $func$ LANGUAGE SQL;
7481
7482 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7483
7484 use MARC::Record;
7485 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7486
7487 my $xml = shift;
7488 my $r = MARC::Record->new_from_xml( $xml );
7489
7490 return_next( { tag => 'LDR', value => $r->leader } );
7491
7492 for my $f ( $r->fields ) {
7493     if ($f->is_control_field) {
7494         return_next({ tag => $f->tag, value => $f->data });
7495     } else {
7496         for my $s ($f->subfields) {
7497             return_next({
7498                 tag      => $f->tag,
7499                 ind1     => $f->indicator(1),
7500                 ind2     => $f->indicator(2),
7501                 subfield => $s->[0],
7502                 value    => $s->[1]
7503             });
7504
7505             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7506                 my $trim = $f->indicator(2) || 0;
7507                 return_next({
7508                     tag      => 'tnf',
7509                     ind1     => $f->indicator(1),
7510                     ind2     => $f->indicator(2),
7511                     subfield => 'a',
7512                     value    => substr( $s->[1], $trim )
7513                 });
7514             }
7515         }
7516     }
7517 }
7518
7519 return undef;
7520
7521 $func$ LANGUAGE PLPERLU;
7522
7523 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7524 DECLARE
7525     bib biblio.record_entry%ROWTYPE;
7526     output  metabib.full_rec%ROWTYPE;
7527     field   RECORD;
7528 BEGIN
7529     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7530
7531     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7532         output.record := rid;
7533         output.ind1 := field.ind1;
7534         output.ind2 := field.ind2;
7535         output.tag := field.tag;
7536         output.subfield := field.subfield;
7537         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7538             output.value := naco_normalize(field.value, field.subfield);
7539         ELSE
7540             output.value := field.value;
7541         END IF;
7542
7543         CONTINUE WHEN output.value IS NULL;
7544
7545         RETURN NEXT output;
7546     END LOOP;
7547 END;
7548 $func$ LANGUAGE PLPGSQL;
7549
7550 -- functions to create auditor objects
7551
7552 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7553 BEGIN
7554     EXECUTE $$
7555         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7556     $$;
7557         RETURN TRUE;
7558 END;
7559 $creator$ LANGUAGE 'plpgsql';
7560
7561 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7562 BEGIN
7563     EXECUTE $$
7564         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7565             audit_id    BIGINT                          PRIMARY KEY,
7566             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7567             audit_action        TEXT                            NOT NULL,
7568             LIKE $$ || sch || $$.$$ || tbl || $$
7569         );
7570     $$;
7571         RETURN TRUE;
7572 END;
7573 $creator$ LANGUAGE 'plpgsql';
7574
7575 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7576 BEGIN
7577     EXECUTE $$
7578         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7579         RETURNS TRIGGER AS $func$
7580         BEGIN
7581             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7582                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7583                     now(),
7584                     SUBSTR(TG_OP,1,1),
7585                     OLD.*;
7586             RETURN NULL;
7587         END;
7588         $func$ LANGUAGE 'plpgsql';
7589     $$;
7590         RETURN TRUE;
7591 END;
7592 $creator$ LANGUAGE 'plpgsql';
7593
7594 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7595 BEGIN
7596     EXECUTE $$
7597         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7598             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7599             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7600     $$;
7601         RETURN TRUE;
7602 END;
7603 $creator$ LANGUAGE 'plpgsql';
7604
7605 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7606 BEGIN
7607     EXECUTE $$
7608         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7609             SELECT      -1, now() as audit_time, '-' as audit_action, *
7610               FROM      $$ || sch || $$.$$ || tbl || $$
7611                 UNION ALL
7612             SELECT      *
7613               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7614     $$;
7615         RETURN TRUE;
7616 END;
7617 $creator$ LANGUAGE 'plpgsql';
7618
7619 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7620
7621 -- The main event
7622
7623 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7624 BEGIN
7625     PERFORM auditor.create_auditor_seq(sch, tbl);
7626     PERFORM auditor.create_auditor_history(sch, tbl);
7627     PERFORM auditor.create_auditor_func(sch, tbl);
7628     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7629     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7630     RETURN TRUE;
7631 END;
7632 $creator$ LANGUAGE 'plpgsql';
7633
7634 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7635
7636 ALTER TABLE action.hold_request
7637 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7638
7639 ALTER TABLE action.hold_request
7640 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7641
7642 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7643
7644 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7645
7646 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7647
7648 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7649
7650 -- Add claims_never_checked_out_count to actor.usr, related history
7651
7652 ALTER TABLE actor.usr ADD COLUMN
7653         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7654
7655 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7656         claims_never_checked_out_count INT;
7657
7658 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7659
7660 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7661
7662 -----------
7663
7664 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7665 BEGIN
7666         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7667                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7668                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7669                 END IF;
7670                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7671                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7672                 END IF;
7673                 IF NEW.stop_fines = 'LOST' THEN
7674                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7675                 END IF;
7676         END IF;
7677         RETURN NEW;
7678 END;
7679 $$ LANGUAGE 'plpgsql';
7680
7681 -- Create new table acq.fund_allocation_percent
7682 -- Populate it from acq.fund_allocation
7683 -- Convert all percentages to amounts in acq.fund_allocation
7684
7685 CREATE TABLE acq.fund_allocation_percent
7686 (
7687     id                   SERIAL            PRIMARY KEY,
7688     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7689                                                DEFERRABLE INITIALLY DEFERRED,
7690     org                  INT               NOT NULL REFERENCES actor.org_unit
7691                                                DEFERRABLE INITIALLY DEFERRED,
7692     fund_code            TEXT,
7693     percent              NUMERIC           NOT NULL,
7694     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7695                                                DEFERRABLE INITIALLY DEFERRED,
7696     note                 TEXT,
7697     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7698     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7699     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7700 );
7701
7702 -- Trigger function to validate combination of org_unit and fund_code
7703
7704 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7705 RETURNS TRIGGER AS $$
7706 --
7707 DECLARE
7708 --
7709 dummy int := 0;
7710 --
7711 BEGIN
7712     SELECT
7713         1
7714     INTO
7715         dummy
7716     FROM
7717         acq.fund
7718     WHERE
7719         org = NEW.org
7720         AND code = NEW.fund_code
7721         LIMIT 1;
7722     --
7723     IF dummy = 1 then
7724         RETURN NEW;
7725     ELSE
7726         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7727     END IF;
7728 END;
7729 $$ LANGUAGE plpgsql;
7730
7731 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7732     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7733     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7734
7735 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7736 RETURNS TRIGGER AS $$
7737 DECLARE
7738 --
7739 total_percent numeric;
7740 --
7741 BEGIN
7742     SELECT
7743         sum( percent )
7744     INTO
7745         total_percent
7746     FROM
7747         acq.fund_allocation_percent AS fap
7748     WHERE
7749         fap.funding_source = NEW.funding_source;
7750     --
7751     IF total_percent > 100 THEN
7752         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7753             NEW.funding_source;
7754     ELSE
7755         RETURN NEW;
7756     END IF;
7757 END;
7758 $$ LANGUAGE plpgsql;
7759
7760 CREATE TRIGGER acqfap_limit_100_trig
7761     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7762     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7763
7764 -- Populate new table from acq.fund_allocation
7765
7766 INSERT INTO acq.fund_allocation_percent
7767 (
7768     funding_source,
7769     org,
7770     fund_code,
7771     percent,
7772     allocator,
7773     note,
7774     create_time
7775 )
7776     SELECT
7777         fa.funding_source,
7778         fund.org,
7779         fund.code,
7780         fa.percent,
7781         fa.allocator,
7782         fa.note,
7783         fa.create_time
7784     FROM
7785         acq.fund_allocation AS fa
7786             INNER JOIN acq.fund AS fund
7787                 ON ( fa.fund = fund.id )
7788     WHERE
7789         fa.percent is not null
7790     ORDER BY
7791         fund.org;
7792
7793 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7794
7795 -- Algorithm to apply to each funding source:
7796
7797 -- 1. Add up the credits.
7798 -- 2. Add up the percentages.
7799 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7800 --    fractional cents from the result.  This is the total amount to be allocated.
7801 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7802 --    fractional cents to get a preliminary amount.
7803 -- 5. Add up the preliminary amounts for all the allocations.
7804 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7805 --    number of residual cents (resulting from having dropped fractional cents) that
7806 --    must be distributed across the funds in order to make the total of the amounts
7807 --    match the total allocation.
7808 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7809 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7810 --    for each successive fund, until all the residual cents have been exhausted.
7811
7812 -- Result: the sum of the individual allocations now equals the total to be allocated,
7813 -- to the penny.  The individual amounts match the percentages as closely as possible,
7814 -- given the constraint that the total must match.
7815
7816 CREATE OR REPLACE FUNCTION acq.apply_percents()
7817 RETURNS VOID AS $$
7818 declare
7819 --
7820 tot              RECORD;
7821 fund             RECORD;
7822 tot_cents        INTEGER;
7823 src              INTEGER;
7824 id               INTEGER[];
7825 curr_id          INTEGER;
7826 pennies          NUMERIC[];
7827 curr_amount      NUMERIC;
7828 i                INTEGER;
7829 total_of_floors  INTEGER;
7830 total_percent    NUMERIC;
7831 total_allocation INTEGER;
7832 residue          INTEGER;
7833 --
7834 begin
7835         RAISE NOTICE 'Applying percents';
7836         FOR tot IN
7837                 SELECT
7838                         fsrc.funding_source,
7839                         sum( fsrc.amount ) AS total
7840                 FROM
7841                         acq.funding_source_credit AS fsrc
7842                 WHERE fsrc.funding_source IN
7843                         ( SELECT DISTINCT fa.funding_source
7844                           FROM acq.fund_allocation AS fa
7845                           WHERE fa.percent IS NOT NULL )
7846                 GROUP BY
7847                         fsrc.funding_source
7848         LOOP
7849                 tot_cents = floor( tot.total * 100 );
7850                 src = tot.funding_source;
7851                 RAISE NOTICE 'Funding source % total %',
7852                         src, tot_cents;
7853                 i := 0;
7854                 total_of_floors := 0;
7855                 total_percent := 0;
7856                 --
7857                 FOR fund in
7858                         SELECT
7859                                 fa.id,
7860                                 fa.percent,
7861                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7862                         FROM
7863                                 acq.fund_allocation AS fa
7864                         WHERE
7865                                 fa.funding_source = src
7866                                 AND fa.percent IS NOT NULL
7867                         ORDER BY
7868                                 mod( fa.percent * tot_cents / 100, 1 ),
7869                                 fa.fund,
7870                                 fa.id
7871                 LOOP
7872                         RAISE NOTICE '   %: %',
7873                                 fund.id,
7874                                 fund.floor_pennies;
7875                         i := i + 1;
7876                         id[i] = fund.id;
7877                         pennies[i] = fund.floor_pennies;
7878                         total_percent := total_percent + fund.percent;
7879                         total_of_floors := total_of_floors + pennies[i];
7880                 END LOOP;
7881                 total_allocation := floor( total_percent * tot_cents /100 );
7882                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7883                 residue := total_allocation - total_of_floors;
7884                 RAISE NOTICE 'Residue: %', residue;
7885                 --
7886                 -- Post the calculated amounts, revising as needed to
7887                 -- distribute the rounding error
7888                 --
7889                 WHILE i > 0 LOOP
7890                         IF residue > 0 THEN
7891                                 pennies[i] = pennies[i] + 1;
7892                                 residue := residue - 1;
7893                         END IF;
7894                         --
7895                         -- Post amount
7896                         --
7897                         curr_id     := id[i];
7898                         curr_amount := trunc( pennies[i] / 100, 2 );
7899                         --
7900                         UPDATE
7901                                 acq.fund_allocation AS fa
7902                         SET
7903                                 amount = curr_amount,
7904                                 percent = NULL
7905                         WHERE
7906                                 fa.id = curr_id;
7907                         --
7908                         RAISE NOTICE '   ID % and amount %',
7909                                 curr_id,
7910                                 curr_amount;
7911                         i = i - 1;
7912                 END LOOP;
7913         END LOOP;
7914 end;
7915 $$ LANGUAGE 'plpgsql';
7916
7917 -- Run the temporary function
7918
7919 select * from acq.apply_percents();
7920
7921 -- Drop the temporary function now that we're done with it
7922
7923 DROP FUNCTION IF EXISTS acq.apply_percents();
7924
7925 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7926
7927 -- If the following step fails, it's probably because there are still some non-null percent values in
7928 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7929 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7930 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7931 -- slipped in afterwards.
7932
7933 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7934 -- procedure acq.apply_percents() as defined above.
7935
7936 ALTER TABLE acq.fund_allocation
7937 ALTER COLUMN amount SET NOT NULL;
7938
7939 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7940     SELECT  fund,
7941             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7942     FROM acq.fund_allocation a
7943          JOIN acq.fund f ON (a.fund = f.id)
7944          JOIN acq.funding_source s ON (a.funding_source = s.id)
7945     GROUP BY 1;
7946
7947 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7948     SELECT  funding_source,
7949             SUM(a.amount)::NUMERIC(100,2) AS amount
7950     FROM  acq.fund_allocation a
7951     GROUP BY 1;
7952
7953 ALTER TABLE acq.fund_allocation
7954 DROP COLUMN percent;
7955
7956 CREATE TABLE asset.copy_location_order
7957 (
7958         id              SERIAL           PRIMARY KEY,
7959         location        INT              NOT NULL
7960                                              REFERENCES asset.copy_location
7961                                              ON DELETE CASCADE
7962                                              DEFERRABLE INITIALLY DEFERRED,
7963         org             INT              NOT NULL
7964                                              REFERENCES actor.org_unit
7965                                              ON DELETE CASCADE
7966                                              DEFERRABLE INITIALLY DEFERRED,
7967         position        INT              NOT NULL DEFAULT 0,
7968         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7969 );
7970
7971 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7972
7973 -- If you ran this before its most recent incarnation:
7974 -- delete from config.upgrade_log where version = '0328';
7975 -- alter table money.credit_card_payment drop column cc_name;
7976
7977 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7978 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7979
7980 CREATE OR REPLACE FUNCTION action.find_circ_matrix_matchpoint( context_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS config.circ_matrix_matchpoint AS $func$
7981 DECLARE
7982     current_group    permission.grp_tree%ROWTYPE;
7983     user_object    actor.usr%ROWTYPE;
7984     item_object    asset.copy%ROWTYPE;
7985     cn_object    asset.call_number%ROWTYPE;
7986     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7987     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7988     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7989 BEGIN
7990     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7991     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7992     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7993     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7994     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7995
7996     LOOP 
7997         -- for each potential matchpoint for this ou and group ...
7998         FOR current_mp IN
7999             SELECT  m.*
8000               FROM  config.circ_matrix_matchpoint m
8001                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
8002                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
8003               WHERE m.grp = current_group.id
8004                     AND m.active
8005                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
8006                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
8007               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
8008                     CASE WHEN m.copy_owning_lib IS NOT NULL
8009                         THEN 256 / ( SELECT COALESCE(prox, 255) + 1 FROM actor.org_unit_proximity WHERE to_org = cn_object.owning_lib AND from_org = m.copy_owning_lib LIMIT 1 )
8010                         ELSE 0
8011                     END +
8012                     CASE WHEN m.copy_circ_lib IS NOT NULL
8013                         THEN 256 / ( SELECT COALESCE(prox, 255) + 1 FROM actor.org_unit_proximity WHERE to_org = item_object.circ_lib AND from_org = m.copy_circ_lib LIMIT 1 )
8014                         ELSE 0
8015                     END +
8016                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
8017                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
8018                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
8019                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
8020                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
8021                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
8022                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
8023                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
8024                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
8025
8026             IF current_mp.is_renewal IS NOT NULL THEN
8027                 CONTINUE WHEN current_mp.is_renewal <> renewal;
8028             END IF;
8029
8030             IF current_mp.circ_modifier IS NOT NULL THEN
8031                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
8032             END IF;
8033
8034             IF current_mp.marc_type IS NOT NULL THEN
8035                 IF item_object.circ_as_type IS NOT NULL THEN
8036                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
8037                 ELSE
8038                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
8039                 END IF;
8040             END IF;
8041
8042             IF current_mp.marc_form IS NOT NULL THEN
8043                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
8044             END IF;
8045
8046             IF current_mp.marc_vr_format IS NOT NULL THEN
8047                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
8048             END IF;
8049
8050             IF current_mp.ref_flag IS NOT NULL THEN
8051                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
8052             END IF;
8053
8054             IF current_mp.juvenile_flag IS NOT NULL THEN
8055                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
8056             END IF;
8057
8058             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
8059                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
8060             END IF;
8061
8062             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
8063                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
8064             END IF;
8065
8066
8067             -- everything was undefined or matched
8068             matchpoint = current_mp;
8069
8070             EXIT WHEN matchpoint.id IS NOT NULL;
8071         END LOOP;
8072
8073         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
8074
8075         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
8076     END LOOP;
8077
8078     RETURN matchpoint;
8079 END;
8080 $func$ LANGUAGE plpgsql;
8081
8082 CREATE TYPE action.hold_stats AS (
8083     hold_count              INT,
8084     copy_count              INT,
8085     available_count         INT,
8086     total_copy_ratio        FLOAT,
8087     available_copy_ratio    FLOAT
8088 );
8089
8090 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
8091 DECLARE
8092     output          action.hold_stats%ROWTYPE;
8093     hold_count      INT := 0;
8094     copy_count      INT := 0;
8095     available_count INT := 0;
8096     hold_map_data   RECORD;
8097 BEGIN
8098
8099     output.hold_count := 0;
8100     output.copy_count := 0;
8101     output.available_count := 0;
8102
8103     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
8104       FROM  action.hold_copy_map m
8105             JOIN action.hold_request h ON (m.hold = h.id)
8106       WHERE m.target_copy = copy_id
8107             AND NOT h.frozen;
8108
8109     output.hold_count := hold_count;
8110
8111     IF output.hold_count > 0 THEN
8112         FOR hold_map_data IN
8113             SELECT  DISTINCT m.target_copy,
8114                     acp.status
8115               FROM  action.hold_copy_map m
8116                     JOIN asset.copy acp ON (m.target_copy = acp.id)
8117                     JOIN action.hold_request h ON (m.hold = h.id)
8118               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
8119         LOOP
8120             output.copy_count := output.copy_count + 1;
8121             IF hold_map_data.status IN (0,7,12) THEN
8122                 output.available_count := output.available_count + 1;
8123             END IF;
8124         END LOOP;
8125         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
8126         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
8127
8128     END IF;
8129
8130     RETURN output;
8131
8132 END;
8133 $func$ LANGUAGE PLPGSQL;
8134
8135 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
8136 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
8137
8138 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
8139
8140 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8141 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8142
8143 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8144     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8145     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8146     copy_owning_lib
8147 );
8148
8149 -- Return the correct fail_part when the item can't be found
8150 CREATE OR REPLACE FUNCTION action.item_user_circ_test( circ_ou INT, match_item BIGINT, match_user INT, renewal BOOL ) RETURNS SETOF action.matrix_test_result AS $func$
8151 DECLARE
8152     user_object        actor.usr%ROWTYPE;
8153     standing_penalty    config.standing_penalty%ROWTYPE;
8154     item_object        asset.copy%ROWTYPE;
8155     item_status_object    config.copy_status%ROWTYPE;
8156     item_location_object    asset.copy_location%ROWTYPE;
8157     result            action.matrix_test_result;
8158     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8159     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8160     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8161     hold_ratio          action.hold_stats%ROWTYPE;
8162     penalty_type         TEXT;
8163     tmp_grp         INT;
8164     items_out        INT;
8165     context_org_list        INT[];
8166     done            BOOL := FALSE;
8167 BEGIN
8168     result.success := TRUE;
8169
8170     -- Fail if the user is BARRED
8171     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8172
8173     -- Fail if we couldn't find the user 
8174     IF user_object.id IS NULL THEN
8175         result.fail_part := 'no_user';
8176         result.success := FALSE;
8177         done := TRUE;
8178         RETURN NEXT result;
8179         RETURN;
8180     END IF;
8181
8182     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8183
8184     -- Fail if we couldn't find the item 
8185     IF item_object.id IS NULL THEN
8186         result.fail_part := 'no_item';
8187         result.success := FALSE;
8188         done := TRUE;
8189         RETURN NEXT result;
8190         RETURN;
8191     END IF;
8192
8193     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8194     result.matchpoint := circ_test.id;
8195
8196     -- Fail if we couldn't find a matchpoint
8197     IF result.matchpoint IS NULL THEN
8198         result.fail_part := 'no_matchpoint';
8199         result.success := FALSE;
8200         done := TRUE;
8201         RETURN NEXT result;
8202     END IF;
8203
8204     IF user_object.barred IS TRUE THEN
8205         result.fail_part := 'actor.usr.barred';
8206         result.success := FALSE;
8207         done := TRUE;
8208         RETURN NEXT result;
8209     END IF;
8210
8211     -- Fail if the item can't circulate
8212     IF item_object.circulate IS FALSE THEN
8213         result.fail_part := 'asset.copy.circulate';
8214         result.success := FALSE;
8215         done := TRUE;
8216         RETURN NEXT result;
8217     END IF;
8218
8219     -- Fail if the item isn't in a circulateable status on a non-renewal
8220     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8221         result.fail_part := 'asset.copy.status';
8222         result.success := FALSE;
8223         done := TRUE;
8224         RETURN NEXT result;
8225     ELSIF renewal AND item_object.status <> 1 THEN
8226         result.fail_part := 'asset.copy.status';
8227         result.success := FALSE;
8228         done := TRUE;
8229         RETURN NEXT result;
8230     END IF;
8231
8232     -- Fail if the item can't circulate because of the shelving location
8233     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8234     IF item_location_object.circulate IS FALSE THEN
8235         result.fail_part := 'asset.copy_location.circulate';
8236         result.success := FALSE;
8237         done := TRUE;
8238         RETURN NEXT result;
8239     END IF;
8240
8241     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8242
8243     -- Fail if the test is set to hard non-circulating
8244     IF circ_test.circulate IS FALSE THEN
8245         result.fail_part := 'config.circ_matrix_test.circulate';
8246         result.success := FALSE;
8247         done := TRUE;
8248         RETURN NEXT result;
8249     END IF;
8250
8251     -- Fail if the total copy-hold ratio is too low
8252     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8253         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8254         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8255             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8256             result.success := FALSE;
8257             done := TRUE;
8258             RETURN NEXT result;
8259         END IF;
8260     END IF;
8261
8262     -- Fail if the available copy-hold ratio is too low
8263     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8264         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8265         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8266             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8267             result.success := FALSE;
8268             done := TRUE;
8269             RETURN NEXT result;
8270         END IF;
8271     END IF;
8272
8273     IF renewal THEN
8274         penalty_type = '%RENEW%';
8275     ELSE
8276         penalty_type = '%CIRC%';
8277     END IF;
8278
8279     FOR standing_penalty IN
8280         SELECT  DISTINCT csp.*
8281           FROM  actor.usr_standing_penalty usp
8282                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8283           WHERE usr = match_user
8284                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8285                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8286                 AND csp.block_list LIKE penalty_type LOOP
8287
8288         result.fail_part := standing_penalty.name;
8289         result.success := FALSE;
8290         done := TRUE;
8291         RETURN NEXT result;
8292     END LOOP;
8293
8294     -- Fail if the user has too many items with specific circ_modifiers checked out
8295     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8296         SELECT  INTO items_out COUNT(*)
8297           FROM  action.circulation circ
8298             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8299           WHERE circ.usr = match_user
8300                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8301             AND circ.checkin_time IS NULL
8302             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8303             AND cp.circ_modifier IN (SELECT circ_mod FROM config.circ_matrix_circ_mod_test_map WHERE circ_mod_test = out_by_circ_mod.id);
8304         IF items_out >= out_by_circ_mod.items_out THEN
8305             result.fail_part := 'config.circ_matrix_circ_mod_test';
8306             result.success := FALSE;
8307             done := TRUE;
8308             RETURN NEXT result;
8309         END IF;
8310     END LOOP;
8311
8312     -- If we passed everything, return the successful matchpoint id
8313     IF NOT done THEN
8314         RETURN NEXT result;
8315     END IF;
8316
8317     RETURN;
8318 END;
8319 $func$ LANGUAGE plpgsql;
8320
8321 CREATE TABLE config.remote_account (
8322     id          SERIAL  PRIMARY KEY,
8323     label       TEXT    NOT NULL,
8324     host        TEXT    NOT NULL,   -- name or IP, :port optional
8325     username    TEXT,               -- optional, since we could default to $USER
8326     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8327     account     TEXT,               -- aka profile or FTP "account" command
8328     path        TEXT,               -- aka directory
8329     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8330     last_activity TIMESTAMP WITH TIME ZONE
8331 );
8332
8333 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8334     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8335     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8336         vendcode    TEXT,
8337         vendacct    TEXT
8338
8339 ) INHERITS (config.remote_account);
8340
8341 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8342
8343 CREATE TABLE acq.claim_type (
8344         id             SERIAL           PRIMARY KEY,
8345         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8346                                                  DEFERRABLE INITIALLY DEFERRED,
8347         code           TEXT             NOT NULL,
8348         description    TEXT             NOT NULL,
8349         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8350 );
8351
8352 CREATE TABLE acq.claim (
8353         id             SERIAL           PRIMARY KEY,
8354         type           INT              NOT NULL REFERENCES acq.claim_type
8355                                                  DEFERRABLE INITIALLY DEFERRED,
8356         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8357                                                  DEFERRABLE INITIALLY DEFERRED
8358 );
8359
8360 CREATE TABLE acq.claim_policy (
8361         id              SERIAL       PRIMARY KEY,
8362         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8363                                      DEFERRABLE INITIALLY DEFERRED,
8364         name            TEXT         NOT NULL,
8365         description     TEXT         NOT NULL,
8366         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8367 );
8368
8369 -- Add a san column for EDI. 
8370 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8371
8372 ALTER TABLE acq.provider ADD COLUMN san INT;
8373
8374 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8375
8376 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8377 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8378
8379 ALTER TABLE acq.provider
8380         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8381
8382 ALTER TABLE acq.provider
8383         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8384
8385 ALTER TABLE acq.provider
8386         ADD COLUMN url TEXT;
8387
8388 ALTER TABLE acq.provider
8389         ADD COLUMN email TEXT;
8390
8391 ALTER TABLE acq.provider
8392         ADD COLUMN phone TEXT;
8393
8394 ALTER TABLE acq.provider
8395         ADD COLUMN fax_phone TEXT;
8396
8397 ALTER TABLE acq.provider
8398         ADD COLUMN default_claim_policy INT
8399                 REFERENCES acq.claim_policy
8400                 DEFERRABLE INITIALLY DEFERRED;
8401
8402 ALTER TABLE action.transit_copy
8403 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8404                                                          DEFERRABLE INITIALLY DEFERRED;
8405
8406 -- represents a circ chain summary
8407 CREATE TYPE action.circ_chain_summary AS (
8408     num_circs INTEGER,
8409     start_time TIMESTAMP WITH TIME ZONE,
8410     checkout_workstation TEXT,
8411     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8412     last_stop_fines TEXT,
8413     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8414     last_renewal_workstation TEXT, -- NULL if no renewals
8415     last_checkin_workstation TEXT,
8416     last_checkin_time TIMESTAMP WITH TIME ZONE,
8417     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8418 );
8419
8420 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8421 DECLARE
8422     tmp_circ action.circulation%ROWTYPE;
8423     circ_0 action.circulation%ROWTYPE;
8424 BEGIN
8425
8426     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8427
8428     IF tmp_circ IS NULL THEN
8429         RETURN NEXT tmp_circ;
8430     END IF;
8431     circ_0 := tmp_circ;
8432
8433     -- find the front of the chain
8434     WHILE TRUE LOOP
8435         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8436         IF tmp_circ IS NULL THEN
8437             EXIT;
8438         END IF;
8439         circ_0 := tmp_circ;
8440     END LOOP;
8441
8442     -- now send the circs to the caller, oldest to newest
8443     tmp_circ := circ_0;
8444     WHILE TRUE LOOP
8445         IF tmp_circ IS NULL THEN
8446             EXIT;
8447         END IF;
8448         RETURN NEXT tmp_circ;
8449         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8450     END LOOP;
8451
8452 END;
8453 $$ LANGUAGE 'plpgsql';
8454
8455 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8456
8457 DECLARE
8458
8459     -- first circ in the chain
8460     circ_0 action.circulation%ROWTYPE;
8461
8462     -- last circ in the chain
8463     circ_n action.circulation%ROWTYPE;
8464
8465     -- circ chain under construction
8466     chain action.circ_chain_summary;
8467     tmp_circ action.circulation%ROWTYPE;
8468
8469 BEGIN
8470     
8471     chain.num_circs := 0;
8472     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8473
8474         IF chain.num_circs = 0 THEN
8475             circ_0 := tmp_circ;
8476         END IF;
8477
8478         chain.num_circs := chain.num_circs + 1;
8479         circ_n := tmp_circ;
8480     END LOOP;
8481
8482     chain.start_time := circ_0.xact_start;
8483     chain.last_stop_fines := circ_n.stop_fines;
8484     chain.last_stop_fines_time := circ_n.stop_fines_time;
8485     chain.last_checkin_time := circ_n.checkin_time;
8486     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8487     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8488     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8489
8490     IF chain.num_circs > 1 THEN
8491         chain.last_renewal_time := circ_n.xact_start;
8492         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8493     END IF;
8494
8495     RETURN chain;
8496
8497 END;
8498 $$ LANGUAGE 'plpgsql';
8499
8500 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON booking.reservation;
8501 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8502 DROP TRIGGER IF EXISTS mat_summary_change_tgr ON booking.reservation;
8503 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8504 DROP TRIGGER IF EXISTS mat_summary_remove_tgr ON booking.reservation;
8505 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8506
8507 ALTER TABLE config.standing_penalty
8508         ADD COLUMN org_depth   INTEGER;
8509
8510 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8511 DECLARE
8512     user_object         actor.usr%ROWTYPE;
8513     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8514     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8515     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8516     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8517     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8518     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8519     tmp_grp             INT;
8520     items_overdue       INT;
8521     items_out           INT;
8522     context_org_list    INT[];
8523     current_fines        NUMERIC(8,2) := 0.0;
8524     tmp_fines            NUMERIC(8,2);
8525     tmp_groc            RECORD;
8526     tmp_circ            RECORD;
8527     tmp_org             actor.org_unit%ROWTYPE;
8528     tmp_penalty         config.standing_penalty%ROWTYPE;
8529     tmp_depth           INTEGER;
8530 BEGIN
8531     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8532
8533     -- Max fines
8534     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8535
8536     -- Fail if the user has a high fine balance
8537     LOOP
8538         tmp_grp := user_object.profile;
8539         LOOP
8540             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8541
8542             IF max_fines.threshold IS NULL THEN
8543                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8544             ELSE
8545                 EXIT;
8546             END IF;
8547
8548             IF tmp_grp IS NULL THEN
8549                 EXIT;
8550             END IF;
8551         END LOOP;
8552
8553         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8554             EXIT;
8555         END IF;
8556
8557         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8558
8559     END LOOP;
8560
8561     IF max_fines.threshold IS NOT NULL THEN
8562
8563         RETURN QUERY
8564             SELECT  *
8565               FROM  actor.usr_standing_penalty
8566               WHERE usr = match_user
8567                     AND org_unit = max_fines.org_unit
8568                     AND (stop_date IS NULL or stop_date > NOW())
8569                     AND standing_penalty = 1;
8570
8571         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8572
8573         SELECT  SUM(f.balance_owed) INTO current_fines
8574           FROM  money.materialized_billable_xact_summary f
8575                 JOIN (
8576                     SELECT  r.id
8577                       FROM  booking.reservation r
8578                       WHERE r.usr = match_user
8579                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8580                             AND xact_finish IS NULL
8581                                 UNION ALL
8582                     SELECT  g.id
8583                       FROM  money.grocery g
8584                       WHERE g.usr = match_user
8585                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8586                             AND xact_finish IS NULL
8587                                 UNION ALL
8588                     SELECT  circ.id
8589                       FROM  action.circulation circ
8590                       WHERE circ.usr = match_user
8591                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8592                             AND xact_finish IS NULL ) l USING (id);
8593
8594         IF current_fines >= max_fines.threshold THEN
8595             new_sp_row.usr := match_user;
8596             new_sp_row.org_unit := max_fines.org_unit;
8597             new_sp_row.standing_penalty := 1;
8598             RETURN NEXT new_sp_row;
8599         END IF;
8600     END IF;
8601
8602     -- Start over for max overdue
8603     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8604
8605     -- Fail if the user has too many overdue items
8606     LOOP
8607         tmp_grp := user_object.profile;
8608         LOOP
8609
8610             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8611
8612             IF max_overdue.threshold IS NULL THEN
8613                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8614             ELSE
8615                 EXIT;
8616             END IF;
8617
8618             IF tmp_grp IS NULL THEN
8619                 EXIT;
8620             END IF;
8621         END LOOP;
8622
8623         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8624             EXIT;
8625         END IF;
8626
8627         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8628
8629     END LOOP;
8630
8631     IF max_overdue.threshold IS NOT NULL THEN
8632
8633         RETURN QUERY
8634             SELECT  *
8635               FROM  actor.usr_standing_penalty
8636               WHERE usr = match_user
8637                     AND org_unit = max_overdue.org_unit
8638                     AND (stop_date IS NULL or stop_date > NOW())
8639                     AND standing_penalty = 2;
8640
8641         SELECT  INTO items_overdue COUNT(*)
8642           FROM  action.circulation circ
8643                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8644           WHERE circ.usr = match_user
8645             AND circ.checkin_time IS NULL
8646             AND circ.due_date < NOW()
8647             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8648
8649         IF items_overdue >= max_overdue.threshold::INT THEN
8650             new_sp_row.usr := match_user;
8651             new_sp_row.org_unit := max_overdue.org_unit;
8652             new_sp_row.standing_penalty := 2;
8653             RETURN NEXT new_sp_row;
8654         END IF;
8655     END IF;
8656
8657     -- Start over for max out
8658     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8659
8660     -- Fail if the user has too many checked out items
8661     LOOP
8662         tmp_grp := user_object.profile;
8663         LOOP
8664             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8665
8666             IF max_items_out.threshold IS NULL THEN
8667                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8668             ELSE
8669                 EXIT;
8670             END IF;
8671
8672             IF tmp_grp IS NULL THEN
8673                 EXIT;
8674             END IF;
8675         END LOOP;
8676
8677         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8678             EXIT;
8679         END IF;
8680
8681         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8682
8683     END LOOP;
8684
8685
8686     -- Fail if the user has too many items checked out
8687     IF max_items_out.threshold IS NOT NULL THEN
8688
8689         RETURN QUERY
8690             SELECT  *
8691               FROM  actor.usr_standing_penalty
8692               WHERE usr = match_user
8693                     AND org_unit = max_items_out.org_unit
8694                     AND (stop_date IS NULL or stop_date > NOW())
8695                     AND standing_penalty = 3;
8696
8697         SELECT  INTO items_out COUNT(*)
8698           FROM  action.circulation circ
8699                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8700           WHERE circ.usr = match_user
8701                 AND circ.checkin_time IS NULL
8702                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8703
8704            IF items_out >= max_items_out.threshold::INT THEN
8705             new_sp_row.usr := match_user;
8706             new_sp_row.org_unit := max_items_out.org_unit;
8707             new_sp_row.standing_penalty := 3;
8708             RETURN NEXT new_sp_row;
8709            END IF;
8710     END IF;
8711
8712     -- Start over for collections warning
8713     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8714
8715     -- Fail if the user has a collections-level fine balance
8716     LOOP
8717         tmp_grp := user_object.profile;
8718         LOOP
8719             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8720
8721             IF max_fines.threshold IS NULL THEN
8722                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8723             ELSE
8724                 EXIT;
8725             END IF;
8726
8727             IF tmp_grp IS NULL THEN
8728                 EXIT;
8729             END IF;
8730         END LOOP;
8731
8732         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8733             EXIT;
8734         END IF;
8735
8736         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8737
8738     END LOOP;
8739
8740     IF max_fines.threshold IS NOT NULL THEN
8741
8742         RETURN QUERY
8743             SELECT  *
8744               FROM  actor.usr_standing_penalty
8745               WHERE usr = match_user
8746                     AND org_unit = max_fines.org_unit
8747                     AND (stop_date IS NULL or stop_date > NOW())
8748                     AND standing_penalty = 4;
8749
8750         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8751
8752         SELECT  SUM(f.balance_owed) INTO current_fines
8753           FROM  money.materialized_billable_xact_summary f
8754                 JOIN (
8755                     SELECT  r.id
8756                       FROM  booking.reservation r
8757                       WHERE r.usr = match_user
8758                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8759                             AND r.xact_finish IS NULL
8760                                 UNION ALL
8761                     SELECT  g.id
8762                       FROM  money.grocery g
8763                       WHERE g.usr = match_user
8764                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8765                             AND g.xact_finish IS NULL
8766                                 UNION ALL
8767                     SELECT  circ.id
8768                       FROM  action.circulation circ
8769                       WHERE circ.usr = match_user
8770                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8771                             AND circ.xact_finish IS NULL ) l USING (id);
8772
8773         IF current_fines >= max_fines.threshold THEN
8774             new_sp_row.usr := match_user;
8775             new_sp_row.org_unit := max_fines.org_unit;
8776             new_sp_row.standing_penalty := 4;
8777             RETURN NEXT new_sp_row;
8778         END IF;
8779     END IF;
8780
8781     -- Start over for in collections
8782     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8783
8784     -- Remove the in-collections penalty if the user has paid down enough
8785     -- This penalty is different, because this code is not responsible for creating 
8786     -- new in-collections penalties, only for removing them
8787     LOOP
8788         tmp_grp := user_object.profile;
8789         LOOP
8790             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8791
8792             IF max_fines.threshold IS NULL THEN
8793                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8794             ELSE
8795                 EXIT;
8796             END IF;
8797
8798             IF tmp_grp IS NULL THEN
8799                 EXIT;
8800             END IF;
8801         END LOOP;
8802
8803         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8804             EXIT;
8805         END IF;
8806
8807         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8808
8809     END LOOP;
8810
8811     IF max_fines.threshold IS NOT NULL THEN
8812
8813         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8814
8815         -- first, see if the user had paid down to the threshold
8816         SELECT  SUM(f.balance_owed) INTO current_fines
8817           FROM  money.materialized_billable_xact_summary f
8818                 JOIN (
8819                     SELECT  r.id
8820                       FROM  booking.reservation r
8821                       WHERE r.usr = match_user
8822                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8823                             AND r.xact_finish IS NULL
8824                                 UNION ALL
8825                     SELECT  g.id
8826                       FROM  money.grocery g
8827                       WHERE g.usr = match_user
8828                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8829                             AND g.xact_finish IS NULL
8830                                 UNION ALL
8831                     SELECT  circ.id
8832                       FROM  action.circulation circ
8833                       WHERE circ.usr = match_user
8834                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8835                             AND circ.xact_finish IS NULL ) l USING (id);
8836
8837         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8838             -- patron has paid down enough
8839
8840             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8841
8842             IF tmp_penalty.org_depth IS NOT NULL THEN
8843
8844                 -- since this code is not responsible for applying the penalty, it can't 
8845                 -- guarantee the current context org will match the org at which the penalty 
8846                 --- was applied.  search up the org tree until we hit the configured penalty depth
8847                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8848                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8849
8850                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8851
8852                     RETURN QUERY
8853                         SELECT  *
8854                           FROM  actor.usr_standing_penalty
8855                           WHERE usr = match_user
8856                                 AND org_unit = tmp_org.id
8857                                 AND (stop_date IS NULL or stop_date > NOW())
8858                                 AND standing_penalty = 30;
8859
8860                     IF tmp_org.parent_ou IS NULL THEN
8861                         EXIT;
8862                     END IF;
8863
8864                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8865                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8866                 END LOOP;
8867
8868             ELSE
8869
8870                 -- no penalty depth is defined, look for exact matches
8871
8872                 RETURN QUERY
8873                     SELECT  *
8874                       FROM  actor.usr_standing_penalty
8875                       WHERE usr = match_user
8876                             AND org_unit = max_fines.org_unit
8877                             AND (stop_date IS NULL or stop_date > NOW())
8878                             AND standing_penalty = 30;
8879             END IF;
8880     
8881         END IF;
8882
8883     END IF;
8884
8885     RETURN;
8886 END;
8887 $func$ LANGUAGE plpgsql;
8888
8889 -- Create a default row in acq.fiscal_calendar
8890 -- Add a column in actor.org_unit to point to it
8891
8892 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8893
8894 ALTER TABLE actor.org_unit
8895 ADD COLUMN fiscal_calendar INT NOT NULL
8896         REFERENCES acq.fiscal_calendar( id )
8897         DEFERRABLE INITIALLY DEFERRED
8898         DEFAULT 1;
8899
8900 ALTER TABLE auditor.actor_org_unit_history
8901         ADD COLUMN fiscal_calendar INT;
8902
8903 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8904
8905 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8906
8907 ALTER TABLE acq.funding_source_credit
8908 ADD COLUMN deadline_date TIMESTAMPTZ;
8909
8910 ALTER TABLE acq.funding_source_credit
8911 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8912
8913 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8914
8915 CREATE TABLE acq.fund_transfer (
8916         id               SERIAL         PRIMARY KEY,
8917         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8918                                         DEFERRABLE INITIALLY DEFERRED,
8919         src_amount       NUMERIC        NOT NULL,
8920         dest_fund        INT            REFERENCES acq.fund( id )
8921                                         DEFERRABLE INITIALLY DEFERRED,
8922         dest_amount      NUMERIC,
8923         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8924         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8925                                         DEFERRABLE INITIALLY DEFERRED,
8926         note             TEXT,
8927     funding_source_credit INTEGER   NOT NULL
8928                                         REFERENCES acq.funding_source_credit(id)
8929                                         DEFERRABLE INITIALLY DEFERRED
8930 );
8931
8932 CREATE INDEX acqftr_usr_idx
8933 ON acq.fund_transfer( transfer_user );
8934
8935 COMMENT ON TABLE acq.fund_transfer IS $$
8936 /*
8937  * Copyright (C) 2009  Georgia Public Library Service
8938  * Scott McKellar <scott@esilibrary.com>
8939  *
8940  * Fund Transfer
8941  *
8942  * Each row represents the transfer of money from a source fund
8943  * to a destination fund.  There should be corresponding entries
8944  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
8945  * to record how much money moved from which fund to which other
8946  * fund.
8947  * 
8948  * The presence of two amount fields, rather than one, reflects
8949  * the possibility that the two funds are denominated in different
8950  * currencies.  If they use the same currency type, the two
8951  * amounts should be the same.
8952  *
8953  * ****
8954  *
8955  * This program is free software; you can redistribute it and/or
8956  * modify it under the terms of the GNU General Public License
8957  * as published by the Free Software Foundation; either version 2
8958  * of the License, or (at your option) any later version.
8959  *
8960  * This program is distributed in the hope that it will be useful,
8961  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8962  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8963  * GNU General Public License for more details.
8964  */
8965 $$;
8966
8967 CREATE TABLE acq.claim_event_type (
8968         id             SERIAL           PRIMARY KEY,
8969         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8970                                                  DEFERRABLE INITIALLY DEFERRED,
8971         code           TEXT             NOT NULL,
8972         description    TEXT             NOT NULL,
8973         library_initiated BOOL          NOT NULL DEFAULT FALSE,
8974         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
8975 );
8976
8977 CREATE TABLE acq.claim_event (
8978         id             BIGSERIAL        PRIMARY KEY,
8979         type           INT              NOT NULL REFERENCES acq.claim_event_type
8980                                                  DEFERRABLE INITIALLY DEFERRED,
8981         claim          SERIAL           NOT NULL REFERENCES acq.claim
8982                                                  DEFERRABLE INITIALLY DEFERRED,
8983         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
8984         creator        INT              NOT NULL REFERENCES actor.usr
8985                                                  DEFERRABLE INITIALLY DEFERRED,
8986         note           TEXT
8987 );
8988
8989 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
8990
8991 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
8992         src_usr  IN INTEGER,
8993         dest_usr IN INTEGER
8994 ) RETURNS VOID AS $$
8995 DECLARE
8996         suffix TEXT;
8997         renamable_row RECORD;
8998 BEGIN
8999
9000         UPDATE actor.usr SET
9001                 active = FALSE,
9002                 card = NULL,
9003                 mailing_address = NULL,
9004                 billing_address = NULL
9005         WHERE id = src_usr;
9006
9007         -- acq.*
9008         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9009         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9010         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9011         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9012         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9013         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9014         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9015
9016         -- Update with a rename to avoid collisions
9017         FOR renamable_row in
9018                 SELECT id, name
9019                 FROM   acq.picklist
9020                 WHERE  owner = src_usr
9021         LOOP
9022                 suffix := ' (' || src_usr || ')';
9023                 LOOP
9024                         BEGIN
9025                                 UPDATE  acq.picklist
9026                                 SET     owner = dest_usr, name = name || suffix
9027                                 WHERE   id = renamable_row.id;
9028                         EXCEPTION WHEN unique_violation THEN
9029                                 suffix := suffix || ' ';
9030                                 CONTINUE;
9031                         END;
9032                         EXIT;
9033                 END LOOP;
9034         END LOOP;
9035
9036         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9037         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9038         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9039         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9040         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9041         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9042         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9043         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9044
9045         -- action.*
9046         DELETE FROM action.circulation WHERE usr = src_usr;
9047         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9048         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9049         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9050         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9051         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9052         DELETE FROM action.hold_request WHERE usr = src_usr;
9053         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9054         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9055         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9056         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9057         DELETE FROM action.survey_response WHERE usr = src_usr;
9058         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9059
9060         -- actor.*
9061         DELETE FROM actor.card WHERE usr = src_usr;
9062         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9063
9064         -- The following update is intended to avoid transient violations of a foreign
9065         -- key constraint, whereby actor.usr_address references itself.  It may not be
9066         -- necessary, but it does no harm.
9067         UPDATE actor.usr_address SET replaces = NULL
9068                 WHERE usr = src_usr AND replaces IS NOT NULL;
9069         DELETE FROM actor.usr_address WHERE usr = src_usr;
9070         DELETE FROM actor.usr_note WHERE usr = src_usr;
9071         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9072         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9073         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9074         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9075         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9076         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9077
9078         -- asset.*
9079         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9080         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9081         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9082         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9083         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9084         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9085
9086         -- auditor.*
9087         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9088         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9089         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9090         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9091         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9092         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9093         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9094         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9095
9096         -- biblio.*
9097         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9098         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9099         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9100         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9101
9102         -- container.*
9103         -- Update buckets with a rename to avoid collisions
9104         FOR renamable_row in
9105                 SELECT id, name
9106                 FROM   container.biblio_record_entry_bucket
9107                 WHERE  owner = src_usr
9108         LOOP
9109                 suffix := ' (' || src_usr || ')';
9110                 LOOP
9111                         BEGIN
9112                                 UPDATE  container.biblio_record_entry_bucket
9113                                 SET     owner = dest_usr, name = name || suffix
9114                                 WHERE   id = renamable_row.id;
9115                         EXCEPTION WHEN unique_violation THEN
9116                                 suffix := suffix || ' ';
9117                                 CONTINUE;
9118                         END;
9119                         EXIT;
9120                 END LOOP;
9121         END LOOP;
9122
9123         FOR renamable_row in
9124                 SELECT id, name
9125                 FROM   container.call_number_bucket
9126                 WHERE  owner = src_usr
9127         LOOP
9128                 suffix := ' (' || src_usr || ')';
9129                 LOOP
9130                         BEGIN
9131                                 UPDATE  container.call_number_bucket
9132                                 SET     owner = dest_usr, name = name || suffix
9133                                 WHERE   id = renamable_row.id;
9134                         EXCEPTION WHEN unique_violation THEN
9135                                 suffix := suffix || ' ';
9136                                 CONTINUE;
9137                         END;
9138                         EXIT;
9139                 END LOOP;
9140         END LOOP;
9141
9142         FOR renamable_row in
9143                 SELECT id, name
9144                 FROM   container.copy_bucket
9145                 WHERE  owner = src_usr
9146         LOOP
9147                 suffix := ' (' || src_usr || ')';
9148                 LOOP
9149                         BEGIN
9150                                 UPDATE  container.copy_bucket
9151                                 SET     owner = dest_usr, name = name || suffix
9152                                 WHERE   id = renamable_row.id;
9153                         EXCEPTION WHEN unique_violation THEN
9154                                 suffix := suffix || ' ';
9155                                 CONTINUE;
9156                         END;
9157                         EXIT;
9158                 END LOOP;
9159         END LOOP;
9160
9161         FOR renamable_row in
9162                 SELECT id, name
9163                 FROM   container.user_bucket
9164                 WHERE  owner = src_usr
9165         LOOP
9166                 suffix := ' (' || src_usr || ')';
9167                 LOOP
9168                         BEGIN
9169                                 UPDATE  container.user_bucket
9170                                 SET     owner = dest_usr, name = name || suffix
9171                                 WHERE   id = renamable_row.id;
9172                         EXCEPTION WHEN unique_violation THEN
9173                                 suffix := suffix || ' ';
9174                                 CONTINUE;
9175                         END;
9176                         EXIT;
9177                 END LOOP;
9178         END LOOP;
9179
9180         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9181
9182         -- money.*
9183         DELETE FROM money.billable_xact WHERE usr = src_usr;
9184         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9185         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9186
9187         -- permission.*
9188         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9189         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9190         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9191         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9192
9193         -- reporter.*
9194         -- Update with a rename to avoid collisions
9195         BEGIN
9196                 FOR renamable_row in
9197                         SELECT id, name
9198                         FROM   reporter.output_folder
9199                         WHERE  owner = src_usr
9200                 LOOP
9201                         suffix := ' (' || src_usr || ')';
9202                         LOOP
9203                                 BEGIN
9204                                         UPDATE  reporter.output_folder
9205                                         SET     owner = dest_usr, name = name || suffix
9206                                         WHERE   id = renamable_row.id;
9207                                 EXCEPTION WHEN unique_violation THEN
9208                                         suffix := suffix || ' ';
9209                                         CONTINUE;
9210                                 END;
9211                                 EXIT;
9212                         END LOOP;
9213                 END LOOP;
9214         EXCEPTION WHEN undefined_table THEN
9215                 -- do nothing
9216         END;
9217
9218         BEGIN
9219                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9220         EXCEPTION WHEN undefined_table THEN
9221                 -- do nothing
9222         END;
9223
9224         -- Update with a rename to avoid collisions
9225         BEGIN
9226                 FOR renamable_row in
9227                         SELECT id, name
9228                         FROM   reporter.report_folder
9229                         WHERE  owner = src_usr
9230                 LOOP
9231                         suffix := ' (' || src_usr || ')';
9232                         LOOP
9233                                 BEGIN
9234                                         UPDATE  reporter.report_folder
9235                                         SET     owner = dest_usr, name = name || suffix
9236                                         WHERE   id = renamable_row.id;
9237                                 EXCEPTION WHEN unique_violation THEN
9238                                         suffix := suffix || ' ';
9239                                         CONTINUE;
9240                                 END;
9241                                 EXIT;
9242                         END LOOP;
9243                 END LOOP;
9244         EXCEPTION WHEN undefined_table THEN
9245                 -- do nothing
9246         END;
9247
9248         BEGIN
9249                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9250         EXCEPTION WHEN undefined_table THEN
9251                 -- do nothing
9252         END;
9253
9254         BEGIN
9255                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9256         EXCEPTION WHEN undefined_table THEN
9257                 -- do nothing
9258         END;
9259
9260         -- Update with a rename to avoid collisions
9261         BEGIN
9262                 FOR renamable_row in
9263                         SELECT id, name
9264                         FROM   reporter.template_folder
9265                         WHERE  owner = src_usr
9266                 LOOP
9267                         suffix := ' (' || src_usr || ')';
9268                         LOOP
9269                                 BEGIN
9270                                         UPDATE  reporter.template_folder
9271                                         SET     owner = dest_usr, name = name || suffix
9272                                         WHERE   id = renamable_row.id;
9273                                 EXCEPTION WHEN unique_violation THEN
9274                                         suffix := suffix || ' ';
9275                                         CONTINUE;
9276                                 END;
9277                                 EXIT;
9278                         END LOOP;
9279                 END LOOP;
9280         EXCEPTION WHEN undefined_table THEN
9281         -- do nothing
9282         END;
9283
9284         -- vandelay.*
9285         -- Update with a rename to avoid collisions
9286         FOR renamable_row in
9287                 SELECT id, name
9288                 FROM   vandelay.queue
9289                 WHERE  owner = src_usr
9290         LOOP
9291                 suffix := ' (' || src_usr || ')';
9292                 LOOP
9293                         BEGIN
9294                                 UPDATE  vandelay.queue
9295                                 SET     owner = dest_usr, name = name || suffix
9296                                 WHERE   id = renamable_row.id;
9297                         EXCEPTION WHEN unique_violation THEN
9298                                 suffix := suffix || ' ';
9299                                 CONTINUE;
9300                         END;
9301                         EXIT;
9302                 END LOOP;
9303         END LOOP;
9304
9305 END;
9306 $$ LANGUAGE plpgsql;
9307
9308 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9309 /**
9310  * Finds rows dependent on a given row in actor.usr and either deletes them
9311  * or reassigns them to a different user.
9312  */
9313 $$;
9314
9315 CREATE OR REPLACE FUNCTION actor.usr_delete(
9316         src_usr  IN INTEGER,
9317         dest_usr IN INTEGER
9318 ) RETURNS VOID AS $$
9319 DECLARE
9320         old_profile actor.usr.profile%type;
9321         old_home_ou actor.usr.home_ou%type;
9322         new_profile actor.usr.profile%type;
9323         new_home_ou actor.usr.home_ou%type;
9324         new_name    text;
9325         new_dob     actor.usr.dob%type;
9326 BEGIN
9327         SELECT
9328                 id || '-PURGED-' || now(),
9329                 profile,
9330                 home_ou,
9331                 dob
9332         INTO
9333                 new_name,
9334                 old_profile,
9335                 old_home_ou,
9336                 new_dob
9337         FROM
9338                 actor.usr
9339         WHERE
9340                 id = src_usr;
9341         --
9342         -- Quit if no such user
9343         --
9344         IF old_profile IS NULL THEN
9345                 RETURN;
9346         END IF;
9347         --
9348         perform actor.usr_purge_data( src_usr, dest_usr );
9349         --
9350         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9351         -- could assume that there is only one root.  Theoretically, someday, maybe,
9352         -- there could be multiple roots, so we take extra trouble to get the right ones.
9353         --
9354         SELECT
9355                 id
9356         INTO
9357                 new_profile
9358         FROM
9359                 permission.grp_ancestors( old_profile )
9360         WHERE
9361                 parent is null;
9362         --
9363         SELECT
9364                 id
9365         INTO
9366                 new_home_ou
9367         FROM
9368                 actor.org_unit_ancestors( old_home_ou )
9369         WHERE
9370                 parent_ou is null;
9371         --
9372         -- Truncate date of birth
9373         --
9374         IF new_dob IS NOT NULL THEN
9375                 new_dob := date_trunc( 'year', new_dob );
9376         END IF;
9377         --
9378         UPDATE
9379                 actor.usr
9380                 SET
9381                         card = NULL,
9382                         profile = new_profile,
9383                         usrname = new_name,
9384                         email = NULL,
9385                         passwd = random()::text,
9386                         standing = DEFAULT,
9387                         ident_type = 
9388                         (
9389                                 SELECT MIN( id )
9390                                 FROM config.identification_type
9391                         ),
9392                         ident_value = NULL,
9393                         ident_type2 = NULL,
9394                         ident_value2 = NULL,
9395                         net_access_level = DEFAULT,
9396                         photo_url = NULL,
9397                         prefix = NULL,
9398                         first_given_name = new_name,
9399                         second_given_name = NULL,
9400                         family_name = new_name,
9401                         suffix = NULL,
9402                         alias = NULL,
9403                         day_phone = NULL,
9404                         evening_phone = NULL,
9405                         other_phone = NULL,
9406                         mailing_address = NULL,
9407                         billing_address = NULL,
9408                         home_ou = new_home_ou,
9409                         dob = new_dob,
9410                         active = FALSE,
9411                         master_account = DEFAULT, 
9412                         super_user = DEFAULT,
9413                         barred = FALSE,
9414                         deleted = TRUE,
9415                         juvenile = DEFAULT,
9416                         usrgroup = 0,
9417                         claims_returned_count = DEFAULT,
9418                         credit_forward_balance = DEFAULT,
9419                         last_xact_id = DEFAULT,
9420                         alert_message = NULL,
9421                         create_date = now(),
9422                         expire_date = now()
9423         WHERE
9424                 id = src_usr;
9425 END;
9426 $$ LANGUAGE plpgsql;
9427
9428 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9429 /**
9430  * Logically deletes a user.  Removes personally identifiable information,
9431  * and purges associated data in other tables.
9432  */
9433 $$;
9434
9435 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9436
9437 ALTER TABLE acq.fund
9438 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9439
9440 ALTER TABLE acq.fund
9441         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9442
9443 -- A fund can't roll over if it doesn't propagate from one year to the next
9444
9445 ALTER TABLE acq.fund
9446         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9447         ( propagate OR NOT rollover );
9448
9449 ALTER TABLE acq.fund
9450         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9451
9452 ALTER TABLE acq.fund
9453     ADD COLUMN balance_warning_percent INT;
9454
9455 ALTER TABLE acq.fund
9456     ADD COLUMN balance_stop_percent INT;
9457
9458 CREATE VIEW acq.ordered_funding_source_credit AS
9459         SELECT
9460                 CASE WHEN deadline_date IS NULL THEN
9461                         2
9462                 ELSE
9463                         1
9464                 END AS sort_priority,
9465                 CASE WHEN deadline_date IS NULL THEN
9466                         effective_date
9467                 ELSE
9468                         deadline_date
9469                 END AS sort_date,
9470                 id,
9471                 funding_source,
9472                 amount,
9473                 note
9474         FROM
9475                 acq.funding_source_credit;
9476
9477 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9478 /*
9479  * Copyright (C) 2009  Georgia Public Library Service
9480  * Scott McKellar <scott@gmail.com>
9481  *
9482  * The acq.ordered_funding_source_credit view is a prioritized
9483  * ordering of funding source credits.  When ordered by the first
9484  * three columns, this view defines the order in which the various
9485  * credits are to be tapped for spending, subject to the allocations
9486  * in the acq.fund_allocation table.
9487  *
9488  * The first column reflects the principle that we should spend
9489  * money with deadlines before spending money without deadlines.
9490  *
9491  * The second column reflects the principle that we should spend the
9492  * oldest money first.  For money with deadlines, that means that we
9493  * spend first from the credit with the earliest deadline.  For
9494  * money without deadlines, we spend first from the credit with the
9495  * earliest effective date.  
9496  *
9497  * The third column is a tie breaker to ensure a consistent
9498  * ordering.
9499  *
9500  * ****
9501  *
9502  * This program is free software; you can redistribute it and/or
9503  * modify it under the terms of the GNU General Public License
9504  * as published by the Free Software Foundation; either version 2
9505  * of the License, or (at your option) any later version.
9506  *
9507  * This program is distributed in the hope that it will be useful,
9508  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9509  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9510  * GNU General Public License for more details.
9511  */
9512 $$;
9513
9514 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9515     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9516       FROM  money.materialized_billable_xact_summary m
9517             LEFT JOIN action.circulation c ON (c.id = m.id)
9518             LEFT JOIN money.grocery g ON (g.id = m.id)
9519             LEFT JOIN booking.reservation r ON (r.id = m.id);
9520
9521 CREATE TABLE config.marc21_rec_type_map (
9522     code        TEXT    PRIMARY KEY,
9523     type_val    TEXT    NOT NULL,
9524     blvl_val    TEXT    NOT NULL
9525 );
9526
9527 CREATE TABLE config.marc21_ff_pos_map (
9528     id          SERIAL  PRIMARY KEY,
9529     fixed_field TEXT    NOT NULL,
9530     tag         TEXT    NOT NULL,
9531     rec_type    TEXT    NOT NULL,
9532     start_pos   INT     NOT NULL,
9533     length      INT     NOT NULL,
9534     default_val TEXT    NOT NULL DEFAULT ' '
9535 );
9536
9537 CREATE TABLE config.marc21_physical_characteristic_type_map (
9538     ptype_key   TEXT    PRIMARY KEY,
9539     label       TEXT    NOT NULL -- I18N
9540 );
9541
9542 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9543     id          SERIAL  PRIMARY KEY,
9544     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9545     subfield    TEXT    NOT NULL,
9546     start_pos   INT     NOT NULL,
9547     length      INT     NOT NULL,
9548     label       TEXT    NOT NULL -- I18N
9549 );
9550
9551 CREATE TABLE config.marc21_physical_characteristic_value_map (
9552     id              SERIAL  PRIMARY KEY,
9553     value           TEXT    NOT NULL,
9554     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9555     label           TEXT    NOT NULL -- I18N
9556 );
9557
9558 ----------------------------------
9559 -- MARC21 record structure data --
9560 ----------------------------------
9561
9562 -- Record type map
9563 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9564 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9565 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9566 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9567 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9568 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9569 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9570 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9571
9572 ------ Physical Characteristics
9573
9574 -- Map
9575 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9576 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9577 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9578 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9579 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9580 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9581 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9582 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote-sensing image');
9583 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9584 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9585 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9586 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9587 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9588 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'One color');
9589 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9590 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9591 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9592 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9593 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9594 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9595 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9596 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9597 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9598 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9599 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Flexible base photographic medium, positive');
9600 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Flexible base photographic medium, negative');
9601 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Non-flexible base photographic medium, positive');
9602 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Non-flexible base photographic medium, negative');
9603 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9604 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other photographic medium');
9605 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9606 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9607 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9608 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9609 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9610 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9611 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9612 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy, blueline print');
9613 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9614 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Pre-production');
9615 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9616 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9617 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9618 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9619 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9620 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9621 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9622 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9623
9624 -- Electronic Resource
9625 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9626 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9627 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Tape Cartridge');
9628 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chip cartridge');
9629 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Computer optical disk cartridge');
9630 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Tape cassette');
9631 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Tape reel');
9632 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic disk');
9633 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magneto-optical disk');
9634 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical disk');
9635 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9636 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9637 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9638 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9639 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'One color');
9640 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9641 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9642 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Gray scale');
9643 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9644 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9645 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9646 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9647 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 1/2 in.');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'12 in.');
9650 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'4 3/4 in. or 12 cm.');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1 1/8 x 2 3/8 in.');
9652 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 7/8 x 2 1/2 in.');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'5 1/4 in.');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 in.');
9657 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9658 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES (' ',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'No sound (Silent)');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9662 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9663 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('nnn',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9666 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'One file format');
9668 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple file formats');
9669 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9670 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9671 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9672 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9675 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'File reproduced from original');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'File reproduced from microform');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'File reproduced from electronic resource');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'File reproduced from an intermediate (not microform)');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9681 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9682 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9683 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9684 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9685 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9687 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9688 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9689 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9690 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9691 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9692 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9693 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9694 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9695
9696 -- Globe
9697 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9698 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9699 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Celestial globe');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Planetary or lunar globe');
9701 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Terrestrial globe');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Earth moon globe');
9703 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9704 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9705 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'One color');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9708 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9709 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9710 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9712 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9714 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9716 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9717 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9718 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9719 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9720 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9721 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9723 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9724
9725 -- Tactile Material
9726 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9727 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9728 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9729 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9730 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9731 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Tactile, with no writing system');
9732 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9733 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9734 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9735 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Literary braille');
9736 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Format code braille');
9737 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mathematics and scientific braille');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Computer braille');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Music braille');
9740 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple braille types');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9742 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9744 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9746 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9747 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9748 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9749 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9751 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bar over bar');
9753 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bar by bar');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Line over line');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9756 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Single line');
9757 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section by section');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Line by line');
9759 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Open score');
9760 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spanner short form scoring');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Short form scoring');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9763 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical score');
9764 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9765 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9766 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9767 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print/braille');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Jumbo or enlarged braille');
9770 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9772 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9773
9774 -- Projected Graphic
9775 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9776 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9777 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film cartridge');
9778 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film filmstrip type');
9780 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip roll');
9781 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9782 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9783 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9784 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hand-colored');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9789 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9792 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9794 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9795 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety film');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film base, other than safety film');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9798 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9799 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9800 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9801 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','f','5','1','Sound on medium or separate');
9802 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound on medium');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound separate from medium');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9805 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9806 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical sound track on motion picture film');
9807 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic sound track on motion picture film');
9808 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cartridge');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound disc');
9810 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape on reel');
9811 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cassette');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical and magnetic sound track on film');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9815 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9816 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9817 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9818 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Standard 8 mm.');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Super 8 mm./single 8 mm.');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'9.5 mm.');
9821 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 mm.');
9822 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'28 mm.');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'35 mm.');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70 mm.');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'2 x 2 in. (5 x 5 cm.)');
9826 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'2 1/4 x 2 1/4 in. (6 x 6 cm.)');
9827 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'4 x 5 in. (10 x 13 cm.)');
9828 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'5 x 7 in. (13 x 18 cm.)');
9829 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 x 10 in. (21 x 26 cm.)');
9831 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('w',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'9 x 9 in. (23 x 23 cm.)');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('x',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'10 x 10 in. (26 x 26 cm.)');
9833 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'7 x 7 in. (18 x 18 cm.)');
9834 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9835 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9837 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9838 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9839 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9840 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal and glass');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics and glass');
9842 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9843 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9844 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9845
9846 -- Microform
9847 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9848 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9849 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Aperture card');
9850 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfilm cartridge');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfilm cassette');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfilm reel');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9854 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche cassette');
9855 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9857 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9858 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9859 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9863 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9864 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 mm.');
9865 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 mm.');
9866 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'35 mm.');
9867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9868 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'105 mm.');
9869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 x 5 in. (8 x 13 cm.)');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'4 x 6 in. (11 x 15 cm.)');
9871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'6 x 9 in. (16 x 23 cm.)');
9872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 1/4 x 7 3/8 in. (9 x 19 cm.)');
9873 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9874 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9875 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','f','5','4','Reduction ratio range/Reduction ratio');
9876 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Low (1-16x)');
9877 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Normal (16-30x)');
9878 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'High (31-60x)');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Very high (61-90x)');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Ultra (90x-)');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9882 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reduction ratio varies');
9883 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9886 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9887 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9888 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9889 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9890 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Silver halide');
9891 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9892 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9893 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9897 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9898 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1st gen. master');
9899 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Printing master');
9900 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Service copy');
9901 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed generation');
9902 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9903 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, undetermined');
9905 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, acetate undetermined');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, diacetate');
9907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Nitrate base');
9908 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed base');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, polyester');
9911 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, mixed');
9912 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Safety base, triacetate');
9913 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9915
9916 -- Non-projected Graphic
9917 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9918 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9921 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9922 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photo-mechanical print');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Technical drawing');
9928 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Flash/activity card');
9930 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9932 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
9933 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'One color');
9934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9935 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hand-colored');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9938 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9939 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9940 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
9941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bristol board');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard/illustration board');
9944 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9945 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9946 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9947 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9949 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9950 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9952 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9953 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9957 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9958 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
9959 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
9960 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bristol board');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard/illustration board');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9964 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9967 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed collection');
9968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9970 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
9971 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
9972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9974 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9975 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9976
9977 -- Motion Picture
9978 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
9979 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
9980 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film cartridge');
9981 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film cassette');
9982 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film reel');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9984 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9985 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
9986 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
9987 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9988 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hand-colored');
9989 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9990 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9992 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Standard sound aperture, reduced frame');
9994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Nonanamorphic (wide-screen)');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Anamorphic (wide-screen)');
9997 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other-wide screen format');
9998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Standard. silent aperture, full frame');
9999 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10000 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10001 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','f','5','1','Sound on medium or separate');
10002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound on medium');
10003 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound separate from medium');
10004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10005 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical sound track on motion picture film');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic sound track on motion picture film');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cartridge');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound disc');
10010 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape on reel');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cassette');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical and magnetic sound track on film');
10013 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10014 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10017 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Standard 8 mm.');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Super 8 mm./single 8 mm.');
10020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'9.5 mm.');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 mm.');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'28 mm.');
10023 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'35 mm.');
10024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70 mm.');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10027 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10030 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multichannel, surround or quadraphonic');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10035 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Work print');
10037 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10038 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10039 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixing tracks');
10041 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Title bands/inter-title rolls');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Production rolls');
10043 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10045
10046 -- Remote-sensing Image
10047 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10048 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10050 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10055 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10056 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10057 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10058 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Low oblique');
10059 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'High oblique');
10060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10061 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10062 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10063 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10064 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('0',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'0-09%');
10065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('1',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'10-19%');
10066 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('2',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'20-29%');
10067 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('3',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'30-39%');
10068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('4',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'40-49%');
10069 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('5',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'50-59%');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('6',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'60-69%');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('7',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70-79%');
10072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('8',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'80-89%');
10073 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('9',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'90-100%');
10074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10075 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10076 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10077 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10078 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Aircraft-low altitude');
10079 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Aircraft-medium altitude');
10080 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Aircraft-high altitude');
10081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Manned spacecraft');
10082 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unmanned spacecraft');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Land-based remote-sensing device');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Water surface-based remote-sensing device');
10085 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Submersible remote-sensing device');
10086 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10087 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10088 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10089 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10090 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10091 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface observing');
10092 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Space observing');
10093 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed uses');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10097 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10098 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10099 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10100 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10101 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10102 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('aa',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Visible light');
10104 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('da',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Near infrared');
10105 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('db',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Middle infrared');
10106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Far infrared');
10107 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Thermal infrared');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('de',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Shortwave infrared (SWIR)');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('df',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Reflective infrared');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10111 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other infrared data');
10112 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ga',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sidelooking airborne radar (SLAR)');
10113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gb',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetic aperture radar (SAR-single frequency)');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'SAR-multi-frequency (multichannel)');
10115 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'SAR-like polarization');
10116 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ge',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'SAR-cross polarization');
10117 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gf',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Infometric SAR');
10118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gg',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Polarmetric SAR');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive microwave mapping');
10120 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('gz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other microwave data');
10121 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ja',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Far ultraviolet');
10122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('jb',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Middle ultraviolet');
10123 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('jc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Near ultraviolet');
10124 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('jv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Ultraviolet combinations');
10125 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('jz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other ultraviolet data');
10126 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ma',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multi-spectral, multidata');
10127 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mb',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multi-temporal');
10128 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination of various data types');
10129 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('nn',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10130 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pa',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sonar-water depth');
10131 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pb',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sonar-bottom topography images, sidescan');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sonar-bottom topography, near-surface');
10133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sonar-bottom topography, near-bottom');
10134 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pe',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Seismic surveys');
10135 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('pz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other acoustical data');
10136 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ra',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Gravity anomales (general)');
10137 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rb',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Free-air');
10138 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10139 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10140 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('sa',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic field');
10141 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('ta',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Radiometric surveys');
10142 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10143 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10144
10145 -- Sound Recording
10146 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10147 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10148 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound disc');
10149 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10150 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound cartridge');
10151 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound-track film');
10152 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10153 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound cassette');
10154 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound-tape reel');
10155 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10156 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('w',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wire recording');
10157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10158 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10159 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 rpm');
10160 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'33 1/3 rpm');
10161 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'45 rpm');
10162 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'78 rpm');
10163 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 rpm');
10164 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1.4 mps');
10165 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'120 rpm');
10166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'160 rpm');
10167 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'15/16 ips');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1 7/8 ips');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 3/4 ips');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'7 1/2 ips');
10171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'15 ips');
10172 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'30 ips');
10173 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10174 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10175 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10177 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10178 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10179 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10180 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10181 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10182 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microgroove/fine');
10183 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10184 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Coarse/standard');
10185 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10186 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10187 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10188 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 in.');
10189 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'5 in.');
10190 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'7 in.');
10191 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'10 in.');
10192 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'12 in.');
10193 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 in.');
10194 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'4 3/4 in. (12 cm.)');
10195 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3 7/8 x 2 1/2 in.');
10196 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10197 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'5 1/4 x 3 7/8 in.');
10198 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'2 3/4 x 4 in.');
10199 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10200 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10201 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10202 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('l',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/8 in.');
10203 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/4in.');
10204 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10205 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/2 in.');
10206 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1 in.');
10207 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10208 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10209 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10210 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Full (1) track');
10211 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Half (2) track');
10212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quarter (4) track');
10213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 track');
10214 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'12 track');
10215 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'16 track');
10216 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10217 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10218 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10219 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'NAB standard');
10221 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CCIR standard');
10222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Dolby-B encoded, standard Dolby');
10223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'dbx encoded');
10224 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Digital recording');
10225 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Dolby-A encoded');
10226 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Dolby-C encoded');
10227 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CX encoded');
10228 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10229 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10231 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10232 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Acoustical capture, direct storage');
10233 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Direct storage, not acoustical');
10234 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Digital storage');
10235 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Analog electrical storage');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10237 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10238
10239 -- Videorecording
10240 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10241 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10242 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10245 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10248 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Black-and-white');
10250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10251 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10252 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10253 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10254 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10255 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10256 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'U-matic');
10259 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10260 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Type C');
10261 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10262 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10263 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10264 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10265 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam SP');
10266 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Super-VHS');
10267 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'M-II');
10268 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'D-2');
10269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 mm.');
10270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hi-8 mm.');
10271 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10272 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10273 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10274 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','f','5','1','Sound on medium or separate');
10275 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound on medium');
10276 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound separate from medium');
10277 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10278 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10279 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical sound track on motion picture film');
10280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic sound track on motion picture film');
10281 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cartridge');
10282 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound disc');
10283 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape on reel');
10284 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Magnetic audio tape in cassette');
10285 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Optical and magnetic sound track on motion picture film');
10286 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10288 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10289 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10290 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10291 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'8 mm.');
10292 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/4 in.');
10293 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1/2 in.');
10294 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'1 in.');
10295 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'2 in.');
10296 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3/4 in.');
10297 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10299 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10301 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10302 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Not applicable');
10303 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multichannel, surround or quadraphonic');
10304 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10305 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10306 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10307
10308 -- Fixed Field position data -- 0-based!
10309 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10310 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10311 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10312 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10313 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10314 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10315 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10316 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10317 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10318 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10319 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10320 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10321 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10322 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10323 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10324 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10325 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10326 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10327 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10328 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10329 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10330 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10331 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10332 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10333 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10334 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10335 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10336 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10337 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10338 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10339 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10340 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10341 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10342 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10343 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10344 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10345 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10346 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10347 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10348 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10349 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10350 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10351 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10352 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10353 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10354 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10355 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10356 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10357 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10358 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10359 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10360 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10361 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10362 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10363 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10364 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10365 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10366 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10367 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10368 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10369 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10370 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10371 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10372 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10373 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10374 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10375 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10376 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10377 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10378 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('S/L', '006', 'SER', 17, 1, '0');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('S/L', '008', 'SER', 34, 1, '0');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10451 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10452 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10453 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10454 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10455
10456 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10457 DECLARE
10458         ldr         RECORD;
10459         tval        TEXT;
10460         tval_rec    RECORD;
10461         bval        TEXT;
10462         bval_rec    RECORD;
10463     retval      config.marc21_rec_type_map%ROWTYPE;
10464 BEGIN
10465     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10466
10467     IF ldr.id IS NULL THEN
10468         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10469         RETURN retval;
10470     END IF;
10471
10472     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10473     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10474
10475
10476     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10477     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10478
10479     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10480
10481     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10482
10483
10484     IF retval.code IS NULL THEN
10485         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10486     END IF;
10487
10488     RETURN retval;
10489 END;
10490 $func$ LANGUAGE PLPGSQL;
10491
10492 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10493 DECLARE
10494     rtype       TEXT;
10495     ff_pos      RECORD;
10496     tag_data    RECORD;
10497     val         TEXT;
10498 BEGIN
10499     rtype := (biblio.marc21_record_type( rid )).code;
10500     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10501         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10502             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10503             RETURN val;
10504         END LOOP;
10505         val := REPEAT( ff_pos.default_val, ff_pos.length );
10506         RETURN val;
10507     END LOOP;
10508
10509     RETURN NULL;
10510 END;
10511 $func$ LANGUAGE PLPGSQL;
10512
10513 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10514 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10515 DECLARE
10516     rowid   INT := 0;
10517     _007    RECORD;
10518     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10519     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10520     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10521     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10522 BEGIN
10523
10524     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10525
10526     IF _007.id IS NOT NULL THEN
10527         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10528
10529         IF ptype.ptype_key IS NOT NULL THEN
10530             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10531                 SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007.value, psf.start_pos + 1, psf.length );
10532
10533                 IF pval.id IS NOT NULL THEN
10534                     rowid := rowid + 1;
10535                     retval.id := rowid;
10536                     retval.record := rid;
10537                     retval.ptype := ptype.ptype_key;
10538                     retval.subfield := psf.id;
10539                     retval.value := pval.id;
10540                     RETURN NEXT retval;
10541                 END IF;
10542
10543             END LOOP;
10544         END IF;
10545     END IF;
10546
10547     RETURN;
10548 END;
10549 $func$ LANGUAGE PLPGSQL;
10550
10551 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10552 DROP VIEW IF EXISTS money.open_usr_summary;
10553 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10554
10555 -- The view should supply defaults for numeric (amount) columns
10556 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10557     SELECT  xact.id,
10558         xact.usr,
10559         xact.xact_start,
10560         xact.xact_finish,
10561         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10562         credit.payment_ts AS last_payment_ts,
10563         credit.note AS last_payment_note,
10564         credit.payment_type AS last_payment_type,
10565         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10566         debit.billing_ts AS last_billing_ts,
10567         debit.note AS last_billing_note,
10568         debit.billing_type AS last_billing_type,
10569         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10570         p.relname AS xact_type
10571       FROM  money.billable_xact xact
10572         JOIN pg_class p ON xact.tableoid = p.oid
10573         LEFT JOIN (
10574             SELECT  billing.xact,
10575                 sum(billing.amount) AS amount,
10576                 max(billing.billing_ts) AS billing_ts,
10577                 last(billing.note) AS note,
10578                 last(billing.billing_type) AS billing_type
10579               FROM  money.billing
10580               WHERE billing.voided IS FALSE
10581               GROUP BY billing.xact
10582             ) debit ON xact.id = debit.xact
10583         LEFT JOIN (
10584             SELECT  payment_view.xact,
10585                 sum(payment_view.amount) AS amount,
10586                 max(payment_view.payment_ts) AS payment_ts,
10587                 last(payment_view.note) AS note,
10588                 last(payment_view.payment_type) AS payment_type
10589               FROM  money.payment_view
10590               WHERE payment_view.voided IS FALSE
10591               GROUP BY payment_view.xact
10592             ) credit ON xact.id = credit.xact
10593       ORDER BY debit.billing_ts, credit.payment_ts;
10594
10595 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10596     SELECT * FROM money.billable_xact_summary_location_view
10597     WHERE xact_finish IS NULL;
10598
10599 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10600     SELECT 
10601         usr,
10602         SUM(total_paid) AS total_paid,
10603         SUM(total_owed) AS total_owed,
10604         SUM(balance_owed) AS balance_owed
10605     FROM  money.materialized_billable_xact_summary
10606     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10607     GROUP BY usr;
10608
10609 CREATE OR REPLACE VIEW money.usr_summary AS
10610     SELECT 
10611         usr, 
10612         sum(total_paid) AS total_paid, 
10613         sum(total_owed) AS total_owed, 
10614         sum(balance_owed) AS balance_owed
10615     FROM money.materialized_billable_xact_summary
10616     GROUP BY usr;
10617
10618 CREATE OR REPLACE VIEW money.open_usr_summary AS
10619     SELECT 
10620         usr, 
10621         sum(total_paid) AS total_paid, 
10622         sum(total_owed) AS total_owed, 
10623         sum(balance_owed) AS balance_owed
10624     FROM money.materialized_billable_xact_summary
10625     WHERE xact_finish IS NULL
10626     GROUP BY usr;
10627
10628 -- CREATE RULE protect_mfhd_delete AS ON DELETE TO serial.record_entry DO INSTEAD UPDATE serial.record_entry SET deleted = true WHERE old.id = serial.record_entry.id;
10629
10630 CREATE TABLE config.biblio_fingerprint (
10631         id                      SERIAL  PRIMARY KEY,
10632         name            TEXT    NOT NULL, 
10633         xpath           TEXT    NOT NULL,
10634     first_word  BOOL    NOT NULL DEFAULT FALSE,
10635         format          TEXT    NOT NULL DEFAULT 'marcxml'
10636 );
10637
10638 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10639     VALUES (
10640         'Title',
10641         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10642             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10643             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10644             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10645             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10646         'marcxml'
10647     );
10648
10649 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10650     VALUES (
10651         'Author',
10652         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10653             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10654             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10655             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10656             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10657         'marcxml',
10658         TRUE
10659     );
10660
10661 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10662 DECLARE
10663     qual        INT;
10664     ldr         TEXT;
10665     tval        TEXT;
10666     tval_rec    RECORD;
10667     bval        TEXT;
10668     bval_rec    RECORD;
10669     type_map    RECORD;
10670     ff_pos      RECORD;
10671     ff_tag_data TEXT;
10672 BEGIN
10673
10674     IF marc IS NULL OR marc = '' THEN
10675         RETURN NULL;
10676     END IF;
10677
10678     -- First, the count of tags
10679     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10680
10681     -- now go through a bunch of pain to get the record type
10682     IF best_type IS NOT NULL THEN
10683         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10684
10685         IF ldr IS NOT NULL THEN
10686             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10687             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10688
10689
10690             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10691             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10692
10693             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10694
10695             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10696
10697             IF type_map.code IS NOT NULL THEN
10698                 IF best_type = type_map.code THEN
10699                     qual := qual + qual / 2;
10700                 END IF;
10701
10702                 FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = 'Lang' AND rec_type = type_map.code ORDER BY tag DESC LOOP
10703                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10704                     IF ff_tag_data = best_lang THEN
10705                             qual := qual + 100;
10706                     END IF;
10707                 END LOOP;
10708             END IF;
10709         END IF;
10710     END IF;
10711
10712     -- Now look for some quality metrics
10713     -- DCL record?
10714     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10715         qual := qual + 10;
10716     END IF;
10717
10718     -- From OCLC?
10719     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10720         qual := qual + 10;
10721     END IF;
10722
10723     RETURN qual;
10724
10725 END;
10726 $func$ LANGUAGE PLPGSQL;
10727
10728 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10729 DECLARE
10730     idx     config.biblio_fingerprint%ROWTYPE;
10731     xfrm        config.xml_transform%ROWTYPE;
10732     prev_xfrm   TEXT;
10733     transformed_xml TEXT;
10734     xml_node    TEXT;
10735     xml_node_list   TEXT[];
10736     raw_text    TEXT;
10737     output_text TEXT := '';
10738 BEGIN
10739
10740     IF marc IS NULL OR marc = '' THEN
10741         RETURN NULL;
10742     END IF;
10743
10744     -- Loop over the indexing entries
10745     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10746
10747         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10748
10749         -- See if we can skip the XSLT ... it's expensive
10750         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10751             -- Can't skip the transform
10752             IF xfrm.xslt <> '---' THEN
10753                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10754             ELSE
10755                 transformed_xml := marc;
10756             END IF;
10757
10758             prev_xfrm := xfrm.name;
10759         END IF;
10760
10761         raw_text := COALESCE(
10762             naco_normalize(
10763                 ARRAY_TO_STRING(
10764                     oils_xpath(
10765                         '//text()',
10766                         (oils_xpath(
10767                             idx.xpath,
10768                             transformed_xml,
10769                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10770                         ))[1]
10771                     ),
10772                     ''
10773                 )
10774             ),
10775             ''
10776         );
10777
10778         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10779         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10780
10781         IF idx.first_word IS TRUE THEN
10782             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10783         END IF;
10784
10785         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10786
10787     END LOOP;
10788
10789     RETURN output_text;
10790
10791 END;
10792 $func$ LANGUAGE PLPGSQL;
10793
10794 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10795 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10796 BEGIN
10797
10798     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10799
10800     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10801         RETURN NEW;
10802     END IF;
10803
10804     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10805     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10806
10807     RETURN NEW;
10808
10809 END;
10810 $func$ LANGUAGE PLPGSQL;
10811
10812 CREATE TABLE config.internal_flag (
10813     name    TEXT    PRIMARY KEY,
10814     value   TEXT,
10815     enabled BOOL    NOT NULL DEFAULT FALSE
10816 );
10817 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10818 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10819 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10820 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10821 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10822 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10823 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10824 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10825 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10826 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10827
10828 CREATE TABLE authority.bib_linking (
10829     id          BIGSERIAL   PRIMARY KEY,
10830     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10831     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10832 );
10833 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10834 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10835
10836 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10837     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10838 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10839
10840 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10841     DELETE FROM authority.bib_linking WHERE bib = $1;
10842     INSERT INTO authority.bib_linking (bib, authority)
10843         SELECT  y.bib,
10844                 y.authority
10845           FROM (    SELECT  DISTINCT $1 AS bib,
10846                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10847                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10848                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10849                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10850     SELECT $1;
10851 $func$ LANGUAGE SQL;
10852
10853 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10854 BEGIN
10855     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10856     IF NOT FOUND THEN
10857         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10858     END IF;
10859     INSERT INTO metabib.rec_descriptor (record, item_type, item_form, bib_level, control_type, enc_level, audience, lit_form, type_mat, cat_form, pub_status, item_lang, vr_format, date1, date2)
10860         SELECT  bib_id,
10861                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10862                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10863                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10864                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10865                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10866                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10867                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10868                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10869                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10870                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10871                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10872                 (   SELECT  v.value
10873                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10874                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10875                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10876                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10877                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10878                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10879
10880     RETURN;
10881 END;
10882 $func$ LANGUAGE PLPGSQL;
10883
10884 CREATE TABLE config.metabib_class (
10885     name    TEXT    PRIMARY KEY,
10886     label   TEXT    NOT NULL UNIQUE
10887 );
10888
10889 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10890 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10891 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10892 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10893 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10894
10895 CREATE TABLE metabib.facet_entry (
10896         id              BIGSERIAL       PRIMARY KEY,
10897         source          BIGINT          NOT NULL,
10898         field           INT             NOT NULL,
10899         value           TEXT            NOT NULL
10900 );
10901
10902 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10903 DECLARE
10904     fclass          RECORD;
10905     ind_data        metabib.field_entry_template%ROWTYPE;
10906 BEGIN
10907     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10908     IF NOT FOUND THEN
10909         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10910             -- RAISE NOTICE 'Emptying out %', fclass.name;
10911             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10912         END LOOP;
10913         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10914     END IF;
10915
10916     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10917         IF ind_data.field < 0 THEN
10918             ind_data.field = -1 * ind_data.field;
10919             INSERT INTO metabib.facet_entry (field, source, value)
10920                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10921         ELSE
10922             EXECUTE $$
10923                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10924                     VALUES ($$ ||
10925                         quote_literal(ind_data.field) || $$, $$ ||
10926                         quote_literal(ind_data.source) || $$, $$ ||
10927                         quote_literal(ind_data.value) ||
10928                     $$);$$;
10929         END IF;
10930
10931     END LOOP;
10932
10933     RETURN;
10934 END;
10935 $func$ LANGUAGE PLPGSQL;
10936
10937 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
10938 DECLARE
10939     uris            TEXT[];
10940     uri_xml         TEXT;
10941     uri_label       TEXT;
10942     uri_href        TEXT;
10943     uri_use         TEXT;
10944     uri_owner       TEXT;
10945     uri_owner_id    INT;
10946     uri_id          INT;
10947     uri_cn_id       INT;
10948     uri_map_id      INT;
10949 BEGIN
10950
10951     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
10952     IF ARRAY_UPPER(uris,1) > 0 THEN
10953         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
10954             -- First we pull info out of the 856
10955             uri_xml     := uris[i];
10956
10957             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
10958             CONTINUE WHEN uri_href IS NULL;
10959
10960             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10961             CONTINUE WHEN uri_label IS NULL;
10962
10963             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
10964             CONTINUE WHEN uri_owner IS NULL;
10965
10966             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
10967
10968             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
10969
10970             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
10971             CONTINUE WHEN NOT FOUND;
10972
10973             -- now we look for a matching uri
10974             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10975             IF NOT FOUND THEN -- create one
10976                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
10977                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
10978             END IF;
10979
10980             -- we need a call number to link through
10981             SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
10982             IF NOT FOUND THEN
10983                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
10984                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
10985                 SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
10986             END IF;
10987
10988             -- now, link them if they're not already
10989             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
10990             IF NOT FOUND THEN
10991                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
10992             END IF;
10993
10994         END LOOP;
10995     END IF;
10996
10997     RETURN;
10998 END;
10999 $func$ LANGUAGE PLPGSQL;
11000
11001 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
11002 DECLARE
11003     source_count    INT;
11004     old_mr          BIGINT;
11005     tmp_mr          metabib.metarecord%ROWTYPE;
11006     deleted_mrs     BIGINT[];
11007 BEGIN
11008
11009     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11010
11011     FOR tmp_mr IN SELECT  m.* FROM  metabib.metarecord m JOIN metabib.metarecord_source_map s ON (s.metarecord = m.id) WHERE s.source = bib_id LOOP
11012
11013         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11014             old_mr := tmp_mr.id;
11015         ELSE
11016             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11017             IF source_count = 0 THEN -- No other records
11018                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11019                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11020             END IF;
11021         END IF;
11022
11023     END LOOP;
11024
11025     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11026         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11027         IF old_mr IS NULL THEN -- nope, create one and grab its id
11028             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11029             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11030         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11031             UPDATE  metabib.metarecord
11032               SET   mods = NULL,
11033                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11034               WHERE id = old_mr;
11035         END IF;
11036     ELSE -- there was one we already attached to, update its mods cache and master_record
11037         UPDATE  metabib.metarecord
11038           SET   mods = NULL,
11039                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11040           WHERE id = old_mr;
11041     END IF;
11042
11043     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11044
11045     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11046         UPDATE action.hold_request SET target = old_mr WHERE target IN ( SELECT explode_array(deleted_mrs) ) AND hold_type = 'M'; -- if we had to delete any MRs above, make sure their holds are moved
11047     END IF;
11048
11049     RETURN old_mr;
11050
11051 END;
11052 $func$ LANGUAGE PLPGSQL;
11053
11054 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11055 BEGIN
11056     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11057     IF NOT FOUND THEN
11058         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11059     END IF;
11060     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11061         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11062
11063     RETURN;
11064 END;
11065 $func$ LANGUAGE PLPGSQL;
11066
11067 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11068 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11069 BEGIN
11070
11071     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11072         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11073         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11074         RETURN NEW; -- and we're done
11075     END IF;
11076
11077     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11078         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11079
11080         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11081             RETURN NEW;
11082         END IF;
11083         -- Propagate these updates to any linked bib records
11084         PERFORM authority.propagate_changes(NEW.id) FROM authority.record_entry WHERE id = NEW.id;
11085     END IF;
11086
11087     -- Record authority linking
11088     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11089     IF NOT FOUND THEN
11090         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11091     END IF;
11092
11093     -- Flatten and insert the mfr data
11094     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11095     IF NOT FOUND THEN
11096         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11097         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11098         IF NOT FOUND THEN
11099             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11100         END IF;
11101     END IF;
11102
11103     -- Gather and insert the field entry data
11104     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11105
11106     -- Located URI magic
11107     IF TG_OP = 'INSERT' THEN
11108         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11109         IF NOT FOUND THEN
11110             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11111         END IF;
11112     ELSE
11113         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11114         IF NOT FOUND THEN
11115             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11116         END IF;
11117     END IF;
11118
11119     -- (re)map metarecord-bib linking
11120     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11121         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11122         IF NOT FOUND THEN
11123             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11124         END IF;
11125     ELSE -- we're doing an update, and we're not deleted, remap
11126         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11127         IF NOT FOUND THEN
11128             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11129         END IF;
11130     END IF;
11131
11132     RETURN NEW;
11133 END;
11134 $func$ LANGUAGE PLPGSQL;
11135
11136 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11137 CREATE TRIGGER aaa_indexing_ingest_or_delete AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.indexing_ingest_or_delete ();
11138
11139 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
11140 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11141
11142 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11143 DECLARE
11144     xpath_list  TEXT[];
11145     select_list TEXT[];
11146     where_list  TEXT[];
11147     q           TEXT;
11148     out_record  RECORD;
11149     empty_test  RECORD;
11150 BEGIN
11151     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11152  
11153     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11154  
11155     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11156         IF xpath_list[i] = 'null()' THEN
11157             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11158         ELSE
11159             select_list := ARRAY_APPEND(
11160                 select_list,
11161                 $sel$
11162                 EXPLODE_ARRAY(
11163                     COALESCE(
11164                         NULLIF(
11165                             oils_xpath(
11166                                 $sel$ ||
11167                                     quote_literal(
11168                                         CASE
11169                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11170                                             ELSE xpath_list[i] || '//text()'
11171                                         END
11172                                     ) ||
11173                                 $sel$,
11174                                 $sel$ || document_field || $sel$
11175                             ),
11176                            '{}'::TEXT[]
11177                         ),
11178                         '{NULL}'::TEXT[]
11179                     )
11180                 ) AS c_$sel$ || i
11181             );
11182             where_list := ARRAY_APPEND(
11183                 where_list,
11184                 'c_' || i || ' IS NOT NULL'
11185             );
11186         END IF;
11187     END LOOP;
11188  
11189     q := $q$
11190 SELECT * FROM (
11191     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11192 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' OR ' );
11193     -- RAISE NOTICE 'query: %', q;
11194  
11195     FOR out_record IN EXECUTE q LOOP
11196         RETURN NEXT out_record;
11197     END LOOP;
11198  
11199     RETURN;
11200 END;
11201 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11202
11203 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11204 DECLARE
11205
11206     owning_lib      TEXT;
11207     circ_lib        TEXT;
11208     call_number     TEXT;
11209     copy_number     TEXT;
11210     status          TEXT;
11211     location        TEXT;
11212     circulate       TEXT;
11213     deposit         TEXT;
11214     deposit_amount  TEXT;
11215     ref             TEXT;
11216     holdable        TEXT;
11217     price           TEXT;
11218     barcode         TEXT;
11219     circ_modifier   TEXT;
11220     circ_as_type    TEXT;
11221     alert_message   TEXT;
11222     opac_visible    TEXT;
11223     pub_note        TEXT;
11224     priv_note       TEXT;
11225
11226     attr_def        RECORD;
11227     tmp_attr_set    RECORD;
11228     attr_set        vandelay.import_item%ROWTYPE;
11229
11230     xpath           TEXT;
11231
11232 BEGIN
11233
11234     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11235
11236     IF FOUND THEN
11237
11238         attr_set.definition := attr_def.id; 
11239     
11240         -- Build the combined XPath
11241     
11242         owning_lib :=
11243             CASE
11244                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11245                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11246                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11247             END;
11248     
11249         circ_lib :=
11250             CASE
11251                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11252                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11253                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11254             END;
11255     
11256         call_number :=
11257             CASE
11258                 WHEN attr_def.call_number IS NULL THEN 'null()'
11259                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11260                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11261             END;
11262     
11263         copy_number :=
11264             CASE
11265                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11266                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11267                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11268             END;
11269     
11270         status :=
11271             CASE
11272                 WHEN attr_def.status IS NULL THEN 'null()'
11273                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11274                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11275             END;
11276     
11277         location :=
11278             CASE
11279                 WHEN attr_def.location IS NULL THEN 'null()'
11280                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11281                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11282             END;
11283     
11284         circulate :=
11285             CASE
11286                 WHEN attr_def.circulate IS NULL THEN 'null()'
11287                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11288                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11289             END;
11290     
11291         deposit :=
11292             CASE
11293                 WHEN attr_def.deposit IS NULL THEN 'null()'
11294                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11295                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11296             END;
11297     
11298         deposit_amount :=
11299             CASE
11300                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11301                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11302                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11303             END;
11304     
11305         ref :=
11306             CASE
11307                 WHEN attr_def.ref IS NULL THEN 'null()'
11308                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11309                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11310             END;
11311     
11312         holdable :=
11313             CASE
11314                 WHEN attr_def.holdable IS NULL THEN 'null()'
11315                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11316                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11317             END;
11318     
11319         price :=
11320             CASE
11321                 WHEN attr_def.price IS NULL THEN 'null()'
11322                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11323                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11324             END;
11325     
11326         barcode :=
11327             CASE
11328                 WHEN attr_def.barcode IS NULL THEN 'null()'
11329                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11330                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11331             END;
11332     
11333         circ_modifier :=
11334             CASE
11335                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11336                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11337                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11338             END;
11339     
11340         circ_as_type :=
11341             CASE
11342                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11343                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11344                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11345             END;
11346     
11347         alert_message :=
11348             CASE
11349                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11350                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11351                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11352             END;
11353     
11354         opac_visible :=
11355             CASE
11356                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11357                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11358                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11359             END;
11360
11361         pub_note :=
11362             CASE
11363                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11364                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11365                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11366             END;
11367         priv_note :=
11368             CASE
11369                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11370                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11371                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11372             END;
11373     
11374     
11375         xpath := 
11376             owning_lib      || '|' || 
11377             circ_lib        || '|' || 
11378             call_number     || '|' || 
11379             copy_number     || '|' || 
11380             status          || '|' || 
11381             location        || '|' || 
11382             circulate       || '|' || 
11383             deposit         || '|' || 
11384             deposit_amount  || '|' || 
11385             ref             || '|' || 
11386             holdable        || '|' || 
11387             price           || '|' || 
11388             barcode         || '|' || 
11389             circ_modifier   || '|' || 
11390             circ_as_type    || '|' || 
11391             alert_message   || '|' || 
11392             pub_note        || '|' || 
11393             priv_note       || '|' || 
11394             opac_visible;
11395
11396         -- RAISE NOTICE 'XPath: %', xpath;
11397         
11398         FOR tmp_attr_set IN
11399                 SELECT  *
11400                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11401                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11402                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11403                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11404         LOOP
11405     
11406             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11407             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11408
11409             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11410             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11411     
11412             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11413             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11414             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11415     
11416             SELECT  id INTO attr_set.location
11417               FROM  asset.copy_location
11418               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11419                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11420     
11421             attr_set.circulate      :=
11422                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11423                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11424
11425             attr_set.deposit        :=
11426                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11427                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11428
11429             attr_set.holdable       :=
11430                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11431                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11432
11433             attr_set.opac_visible   :=
11434                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11435                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11436
11437             attr_set.ref            :=
11438                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11439                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11440     
11441             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11442             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11443             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11444     
11445             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11446             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11447             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11448             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11449             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11450             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11451             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11452             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11453     
11454             RETURN NEXT attr_set;
11455     
11456         END LOOP;
11457     
11458     END IF;
11459
11460     RETURN;
11461
11462 END;
11463 $$ LANGUAGE PLPGSQL;
11464
11465 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11466 DECLARE
11467     attr_def    BIGINT;
11468     item_data   vandelay.import_item%ROWTYPE;
11469 BEGIN
11470
11471     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11472
11473     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11474         INSERT INTO vandelay.import_item (
11475             record,
11476             definition,
11477             owning_lib,
11478             circ_lib,
11479             call_number,
11480             copy_number,
11481             status,
11482             location,
11483             circulate,
11484             deposit,
11485             deposit_amount,
11486             ref,
11487             holdable,
11488             price,
11489             barcode,
11490             circ_modifier,
11491             circ_as_type,
11492             alert_message,
11493             pub_note,
11494             priv_note,
11495             opac_visible
11496         ) VALUES (
11497             NEW.id,
11498             item_data.definition,
11499             item_data.owning_lib,
11500             item_data.circ_lib,
11501             item_data.call_number,
11502             item_data.copy_number,
11503             item_data.status,
11504             item_data.location,
11505             item_data.circulate,
11506             item_data.deposit,
11507             item_data.deposit_amount,
11508             item_data.ref,
11509             item_data.holdable,
11510             item_data.price,
11511             item_data.barcode,
11512             item_data.circ_modifier,
11513             item_data.circ_as_type,
11514             item_data.alert_message,
11515             item_data.pub_note,
11516             item_data.priv_note,
11517             item_data.opac_visible
11518         );
11519     END LOOP;
11520
11521     RETURN NULL;
11522 END;
11523 $func$ LANGUAGE PLPGSQL;
11524
11525 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11526 BEGIN
11527     EXECUTE $$
11528         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11529     $$;
11530         RETURN TRUE;
11531 END;
11532 $creator$ LANGUAGE 'plpgsql';
11533
11534 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11535 BEGIN
11536     EXECUTE $$
11537         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11538             audit_id    BIGINT                          PRIMARY KEY,
11539             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11540             audit_action        TEXT                            NOT NULL,
11541             LIKE $$ || sch || $$.$$ || tbl || $$
11542         );
11543     $$;
11544         RETURN TRUE;
11545 END;
11546 $creator$ LANGUAGE 'plpgsql';
11547
11548 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11549 BEGIN
11550     EXECUTE $$
11551         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11552         RETURNS TRIGGER AS $func$
11553         BEGIN
11554             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11555                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11556                     now(),
11557                     SUBSTR(TG_OP,1,1),
11558                     OLD.*;
11559             RETURN NULL;
11560         END;
11561         $func$ LANGUAGE 'plpgsql';
11562     $$;
11563         RETURN TRUE;
11564 END;
11565 $creator$ LANGUAGE 'plpgsql';
11566
11567 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11568 BEGIN
11569     EXECUTE $$
11570         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11571             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11572             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11573     $$;
11574         RETURN TRUE;
11575 END;
11576 $creator$ LANGUAGE 'plpgsql';
11577
11578 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11579 BEGIN
11580     EXECUTE $$
11581         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11582             SELECT      -1, now() as audit_time, '-' as audit_action, *
11583               FROM      $$ || sch || $$.$$ || tbl || $$
11584                 UNION ALL
11585             SELECT      *
11586               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11587     $$;
11588         RETURN TRUE;
11589 END;
11590 $creator$ LANGUAGE 'plpgsql';
11591
11592 -- The main event
11593
11594 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11595 BEGIN
11596     PERFORM acq.create_acq_seq(sch, tbl);
11597     PERFORM acq.create_acq_history(sch, tbl);
11598     PERFORM acq.create_acq_func(sch, tbl);
11599     PERFORM acq.create_acq_update_trigger(sch, tbl);
11600     PERFORM acq.create_acq_lifecycle(sch, tbl);
11601     RETURN TRUE;
11602 END;
11603 $creator$ LANGUAGE 'plpgsql';
11604
11605 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11606
11607 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11608     SELECT  fund.id AS fund,
11609             fund_debit.encumbrance AS encumbrance,
11610             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11611       FROM acq.fund AS fund
11612                         LEFT JOIN acq.fund_debit AS fund_debit
11613                                 ON ( fund.id = fund_debit.fund )
11614       GROUP BY 1,2;
11615
11616 CREATE TABLE acq.debit_attribution (
11617         id                     INT         NOT NULL PRIMARY KEY,
11618         fund_debit             INT         NOT NULL
11619                                            REFERENCES acq.fund_debit
11620                                            DEFERRABLE INITIALLY DEFERRED,
11621     debit_amount           NUMERIC     NOT NULL,
11622         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11623                                            DEFERRABLE INITIALLY DEFERRED,
11624     credit_amount          NUMERIC
11625 );
11626
11627 CREATE INDEX acq_attribution_debit_idx
11628         ON acq.debit_attribution( fund_debit );
11629
11630 CREATE INDEX acq_attribution_credit_idx
11631         ON acq.debit_attribution( funding_source_credit );
11632
11633 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11634 /*
11635 Function to attribute expenditures and encumbrances to funding source credits,
11636 and thereby to funding sources.
11637
11638 Read the debits in chonological order, attributing each one to one or
11639 more funding source credits.  Constraints:
11640
11641 1. Don't attribute more to a credit than the amount of the credit.
11642
11643 2. For a given fund, don't attribute more to a funding source than the
11644 source has allocated to that fund.
11645
11646 3. Attribute debits to credits with deadlines before attributing them to
11647 credits without deadlines.  Otherwise attribute to the earliest credits
11648 first, based on the deadline date when present, or on the effective date
11649 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11650 This ordering is defined by an ORDER BY clause on the view
11651 acq.ordered_funding_source_credit.
11652
11653 Start by truncating the table acq.debit_attribution.  Then insert a row
11654 into that table for each attribution.  If a debit cannot be fully
11655 attributed, insert a row for the unattributable balance, with the 
11656 funding_source_credit and credit_amount columns NULL.
11657 */
11658 DECLARE
11659         curr_fund_source_bal RECORD;
11660         seqno                INT;     -- sequence num for credits applicable to a fund
11661         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11662         fc                   RECORD;  -- used for loading t_fund_credit table
11663         sc                   RECORD;  -- used for loading t_fund_credit table
11664         --
11665         -- Used exclusively in the main loop:
11666         --
11667         deb                 RECORD;   -- current row from acq.fund_debit table
11668         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11669         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11670         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11671         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11672         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11673         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11674         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11675         attrib_count        INT;      -- populates id of acq.debit_attribution
11676 BEGIN
11677         --
11678         -- Load a temporary table.  For each combination of fund and funding source,
11679         -- load an entry with the total amount allocated to that fund by that source.
11680         -- This sum may reflect transfers as well as original allocations.  We will
11681         -- reduce this balance whenever we attribute debits to it.
11682         --
11683         CREATE TEMP TABLE t_fund_source_bal
11684         ON COMMIT DROP AS
11685                 SELECT
11686                         fund AS fund,
11687                         funding_source AS source,
11688                         sum( amount ) AS balance
11689                 FROM
11690                         acq.fund_allocation
11691                 GROUP BY
11692                         fund,
11693                         funding_source
11694                 HAVING
11695                         sum( amount ) > 0;
11696         --
11697         CREATE INDEX t_fund_source_bal_idx
11698                 ON t_fund_source_bal( fund, source );
11699         -------------------------------------------------------------------------------
11700         --
11701         -- Load another temporary table.  For each fund, load zero or more
11702         -- funding source credits from which that fund can get money.
11703         --
11704         CREATE TEMP TABLE t_fund_credit (
11705                 fund        INT,
11706                 seq         INT,
11707                 credit      INT
11708         ) ON COMMIT DROP;
11709         --
11710         FOR fc IN
11711                 SELECT DISTINCT fund
11712                 FROM acq.fund_allocation
11713                 ORDER BY fund
11714         LOOP                  -- Loop over the funds
11715                 seqno := 1;
11716                 FOR sc IN
11717                         SELECT
11718                                 ofsc.id
11719                         FROM
11720                                 acq.ordered_funding_source_credit AS ofsc
11721                         WHERE
11722                                 ofsc.funding_source IN
11723                                 (
11724                                         SELECT funding_source
11725                                         FROM acq.fund_allocation
11726                                         WHERE fund = fc.fund
11727                                 )
11728                 ORDER BY
11729                     ofsc.sort_priority,
11730                     ofsc.sort_date,
11731                     ofsc.id
11732                 LOOP                        -- Add each credit to the list
11733                         INSERT INTO t_fund_credit (
11734                                 fund,
11735                                 seq,
11736                                 credit
11737                         ) VALUES (
11738                                 fc.fund,
11739                                 seqno,
11740                                 sc.id
11741                         );
11742                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11743                         seqno := seqno + 1;
11744                 END LOOP;     -- Loop over credits for a given fund
11745         END LOOP;         -- Loop over funds
11746         --
11747         CREATE INDEX t_fund_credit_idx
11748                 ON t_fund_credit( fund, seq );
11749         -------------------------------------------------------------------------------
11750         --
11751         -- Load yet another temporary table.  This one is a list of funding source
11752         -- credits, with their balances.  We shall reduce those balances as we
11753         -- attribute debits to them.
11754         --
11755         CREATE TEMP TABLE t_credit
11756         ON COMMIT DROP AS
11757         SELECT
11758             fsc.id AS credit,
11759             fsc.funding_source AS source,
11760             fsc.amount AS balance,
11761             fs.currency_type AS currency_type
11762         FROM
11763             acq.funding_source_credit AS fsc,
11764             acq.funding_source fs
11765         WHERE
11766             fsc.funding_source = fs.id
11767                         AND fsc.amount > 0;
11768         --
11769         CREATE INDEX t_credit_idx
11770                 ON t_credit( credit );
11771         --
11772         -------------------------------------------------------------------------------
11773         --
11774         -- Now that we have loaded the lookup tables: loop through the debits,
11775         -- attributing each one to one or more funding source credits.
11776         -- 
11777         truncate table acq.debit_attribution;
11778         --
11779         attrib_count := 0;
11780         FOR deb in
11781                 SELECT
11782                         fd.id,
11783                         fd.fund,
11784                         fd.amount,
11785                         f.currency_type,
11786                         fd.encumbrance
11787                 FROM
11788                         acq.fund_debit fd,
11789                         acq.fund f
11790                 WHERE
11791                         fd.fund = f.id
11792                 ORDER BY
11793                         fd.id
11794         LOOP
11795                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11796                 --
11797                 debit_balance := deb.amount;
11798                 --
11799                 -- Loop over the funding source credits that are eligible
11800                 -- to pay for this debit
11801                 --
11802                 FOR fund_credit IN
11803                         SELECT
11804                                 credit
11805                         FROM
11806                                 t_fund_credit
11807                         WHERE
11808                                 fund = deb.fund
11809                         ORDER BY
11810                                 seq
11811                 LOOP
11812                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11813                         --
11814                         -- Look up the balance for this credit.  If it's zero, then
11815                         -- it's not useful, so treat it as if you didn't find it.
11816                         -- (Actually there shouldn't be any zero balances in the table,
11817                         -- but we check just to make sure.)
11818                         --
11819                         SELECT *
11820                         INTO curr_credit_bal
11821                         FROM t_credit
11822                         WHERE
11823                                 credit = fund_credit.credit
11824                                 AND balance > 0;
11825                         --
11826                         IF curr_credit_bal IS NULL THEN
11827                                 --
11828                                 -- This credit is exhausted; try the next one.
11829                                 --
11830                                 CONTINUE;
11831                         END IF;
11832                         --
11833                         --
11834                         -- At this point we have an applicable credit with some money left.
11835                         -- Now see if the relevant funding_source has any money left.
11836                         --
11837                         -- Look up the balance of the allocation for this combination of
11838                         -- fund and source.  If you find such an entry, but it has a zero
11839                         -- balance, then it's not useful, so treat it as unfound.
11840                         -- (Actually there shouldn't be any zero balances in the table,
11841                         -- but we check just to make sure.)
11842                         --
11843                         SELECT *
11844                         INTO curr_fund_source_bal
11845                         FROM t_fund_source_bal
11846                         WHERE
11847                                 fund = deb.fund
11848                                 AND source = curr_credit_bal.source
11849                                 AND balance > 0;
11850                         --
11851                         IF curr_fund_source_bal IS NULL THEN
11852                                 --
11853                                 -- This fund/source doesn't exist or is already exhausted,
11854                                 -- so we can't use this credit.  Go on to the next one.
11855                                 --
11856                                 CONTINUE;
11857                         END IF;
11858                         --
11859                         -- Convert the available balances to the currency of the fund
11860                         --
11861                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11862                                 curr_credit_bal.currency_type, deb.currency_type );
11863                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11864                                 curr_credit_bal.currency_type, deb.currency_type );
11865                         --
11866                         -- Determine how much we can attribute to this credit: the minimum
11867                         -- of the debit amount, the fund/source balance, and the
11868                         -- credit balance
11869                         --
11870                         --RAISE NOTICE '   deb bal %', debit_balance;
11871                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11872                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11873                         --
11874                         conv_attr_amount := NULL;
11875                         attr_amount := debit_balance;
11876                         --
11877                         IF attr_amount > conv_alloc_balance THEN
11878                                 attr_amount := conv_alloc_balance;
11879                                 conv_attr_amount := curr_fund_source_bal.balance;
11880                         END IF;
11881                         IF attr_amount > conv_cred_balance THEN
11882                                 attr_amount := conv_cred_balance;
11883                                 conv_attr_amount := curr_credit_bal.balance;
11884                         END IF;
11885                         --
11886                         -- If we're attributing all of one of the balances, then that's how
11887                         -- much we will deduct from the balances, and we already captured
11888                         -- that amount above.  Otherwise we must convert the amount of the
11889                         -- attribution from the currency of the fund back to the currency of
11890                         -- the funding source.
11891                         --
11892                         IF conv_attr_amount IS NULL THEN
11893                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11894                                         deb.currency_type, curr_credit_bal.currency_type );
11895                         END IF;
11896                         --
11897                         -- Insert a row to record the attribution
11898                         --
11899                         attrib_count := attrib_count + 1;
11900                         INSERT INTO acq.debit_attribution (
11901                                 id,
11902                                 fund_debit,
11903                                 debit_amount,
11904                                 funding_source_credit,
11905                                 credit_amount
11906                         ) VALUES (
11907                                 attrib_count,
11908                                 deb.id,
11909                                 attr_amount,
11910                                 curr_credit_bal.credit,
11911                                 conv_attr_amount
11912                         );
11913                         --
11914                         -- Subtract the attributed amount from the various balances
11915                         --
11916                         debit_balance := debit_balance - attr_amount;
11917                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11918                         --
11919                         IF curr_fund_source_bal.balance <= 0 THEN
11920                                 --
11921                                 -- This allocation is exhausted.  Delete it so
11922                                 -- that we don't waste time looking at it again.
11923                                 --
11924                                 DELETE FROM t_fund_source_bal
11925                                 WHERE
11926                                         fund = curr_fund_source_bal.fund
11927                                         AND source = curr_fund_source_bal.source;
11928                         ELSE
11929                                 UPDATE t_fund_source_bal
11930                                 SET balance = balance - conv_attr_amount
11931                                 WHERE
11932                                         fund = curr_fund_source_bal.fund
11933                                         AND source = curr_fund_source_bal.source;
11934                         END IF;
11935                         --
11936                         IF curr_credit_bal.balance <= 0 THEN
11937                                 --
11938                                 -- This funding source credit is exhausted.  Delete it
11939                                 -- so that we don't waste time looking at it again.
11940                                 --
11941                                 --DELETE FROM t_credit
11942                                 --WHERE
11943                                 --      credit = curr_credit_bal.credit;
11944                                 --
11945                                 DELETE FROM t_fund_credit
11946                                 WHERE
11947                                         credit = curr_credit_bal.credit;
11948                         ELSE
11949                                 UPDATE t_credit
11950                                 SET balance = curr_credit_bal.balance
11951                                 WHERE
11952                                         credit = curr_credit_bal.credit;
11953                         END IF;
11954                         --
11955                         -- Are we done with this debit yet?
11956                         --
11957                         IF debit_balance <= 0 THEN
11958                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
11959                         END IF;
11960                 END LOOP;       -- End loop over credits
11961                 --
11962                 IF debit_balance <> 0 THEN
11963                         --
11964                         -- We weren't able to attribute this debit, or at least not
11965                         -- all of it.  Insert a row for the unattributed balance.
11966                         --
11967                         attrib_count := attrib_count + 1;
11968                         INSERT INTO acq.debit_attribution (
11969                                 id,
11970                                 fund_debit,
11971                                 debit_amount,
11972                                 funding_source_credit,
11973                                 credit_amount
11974                         ) VALUES (
11975                                 attrib_count,
11976                                 deb.id,
11977                                 debit_balance,
11978                                 NULL,
11979                                 NULL
11980                         );
11981                 END IF;
11982         END LOOP;   -- End of loop over debits
11983 END;
11984 $$ LANGUAGE 'plpgsql';
11985
11986 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
11987 DECLARE
11988     query TEXT;
11989     output TEXT;
11990 BEGIN
11991     query := $q$
11992         SELECT  regexp_replace(
11993                     oils_xpath_string(
11994                         $q$ || quote_literal($3) || $q$,
11995                         marc,
11996                         ' '
11997                     ),
11998                     $q$ || quote_literal($4) || $q$,
11999                     '',
12000                     'g')
12001           FROM  $q$ || $1 || $q$
12002           WHERE id = $q$ || $2;
12003
12004     EXECUTE query INTO output;
12005
12006     -- RAISE NOTICE 'query: %, output; %', query, output;
12007
12008     RETURN output;
12009 END;
12010 $$ LANGUAGE PLPGSQL IMMUTABLE;
12011
12012 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12013     SELECT extract_marc_field($1,$2,$3,'');
12014 $$ LANGUAGE SQL IMMUTABLE;
12015
12016 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12017 DECLARE
12018     moved_objects INT := 0;
12019     source_cn     asset.call_number%ROWTYPE;
12020     target_cn     asset.call_number%ROWTYPE;
12021     metarec       metabib.metarecord%ROWTYPE;
12022     hold          action.hold_request%ROWTYPE;
12023     ser_rec       serial.record_entry%ROWTYPE;
12024     uri_count     INT := 0;
12025     counter       INT := 0;
12026     uri_datafield TEXT;
12027     uri_text      TEXT := '';
12028 BEGIN
12029
12030     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12031     SELECT  INTO uri_count COUNT(*)
12032       FROM  asset.uri_call_number_map m
12033             JOIN asset.call_number cn ON (m.call_number = cn.id)
12034       WHERE cn.record = source_record;
12035
12036     IF uri_count > 0 THEN
12037
12038         SELECT  COUNT(*) INTO counter
12039           FROM  oils_xpath_table(
12040                     'id',
12041                     'marc',
12042                     'biblio.record_entry',
12043                     '//*[@tag="856"]',
12044                     'id=' || source_record
12045                 ) as t(i int,c text);
12046
12047         FOR i IN 1 .. counter LOOP
12048             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12049                         ' tag="856"' || 
12050                         ' ind1="' || FIRST(ind1) || '"'  || 
12051                         ' ind2="' || FIRST(ind2) || '">' || 
12052                         array_to_string(
12053                             array_accum(
12054                                 '<subfield code="' || subfield || '">' ||
12055                                 regexp_replace(
12056                                     regexp_replace(
12057                                         regexp_replace(data,'&','&amp;','g'),
12058                                         '>', '&gt;', 'g'
12059                                     ),
12060                                     '<', '&lt;', 'g'
12061                                 ) || '</subfield>'
12062                             ), ''
12063                         ) || '</datafield>' INTO uri_datafield
12064               FROM  oils_xpath_table(
12065                         'id',
12066                         'marc',
12067                         'biblio.record_entry',
12068                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12069                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12070                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12071                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12072                         'id=' || source_record
12073                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12074
12075             uri_text := uri_text || uri_datafield;
12076         END LOOP;
12077
12078         IF uri_text <> '' THEN
12079             UPDATE  biblio.record_entry
12080               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12081               WHERE id = target_record;
12082         END IF;
12083
12084     END IF;
12085
12086     -- Find and move metarecords to the target record
12087     SELECT  INTO metarec *
12088       FROM  metabib.metarecord
12089       WHERE master_record = source_record;
12090
12091     IF FOUND THEN
12092         UPDATE  metabib.metarecord
12093           SET   master_record = target_record,
12094             mods = NULL
12095           WHERE id = metarec.id;
12096
12097         moved_objects := moved_objects + 1;
12098     END IF;
12099
12100     -- Find call numbers attached to the source ...
12101     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12102
12103         SELECT  INTO target_cn *
12104           FROM  asset.call_number
12105           WHERE label = source_cn.label
12106             AND owning_lib = source_cn.owning_lib
12107             AND record = target_record;
12108
12109         -- ... and if there's a conflicting one on the target ...
12110         IF FOUND THEN
12111
12112             -- ... move the copies to that, and ...
12113             UPDATE  asset.copy
12114               SET   call_number = target_cn.id
12115               WHERE call_number = source_cn.id;
12116
12117             -- ... move V holds to the move-target call number
12118             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12119
12120                 UPDATE  action.hold_request
12121                   SET   target = target_cn.id
12122                   WHERE id = hold.id;
12123
12124                 moved_objects := moved_objects + 1;
12125             END LOOP;
12126
12127         -- ... if not ...
12128         ELSE
12129             -- ... just move the call number to the target record
12130             UPDATE  asset.call_number
12131               SET   record = target_record
12132               WHERE id = source_cn.id;
12133         END IF;
12134
12135         moved_objects := moved_objects + 1;
12136     END LOOP;
12137
12138     -- Find T holds targeting the source record ...
12139     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12140
12141         -- ... and move them to the target record
12142         UPDATE  action.hold_request
12143           SET   target = target_record
12144           WHERE id = hold.id;
12145
12146         moved_objects := moved_objects + 1;
12147     END LOOP;
12148
12149     -- Find serial records targeting the source record ...
12150     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12151         -- ... and move them to the target record
12152         UPDATE  serial.record_entry
12153           SET   record = target_record
12154           WHERE id = ser_rec.id;
12155
12156         moved_objects := moved_objects + 1;
12157     END LOOP;
12158
12159     -- Finally, "delete" the source record
12160     DELETE FROM biblio.record_entry WHERE id = source_record;
12161
12162     -- That's all, folks!
12163     RETURN moved_objects;
12164 END;
12165 $func$ LANGUAGE plpgsql;
12166
12167 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12168         old_fund   IN INT,
12169         old_amount IN NUMERIC,     -- in currency of old fund
12170         new_fund   IN INT,
12171         new_amount IN NUMERIC,     -- in currency of new fund
12172         user_id    IN INT,
12173         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12174         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12175 ) RETURNS VOID AS $$
12176 /* -------------------------------------------------------------------------------
12177
12178 Function to transfer money from one fund to another.
12179
12180 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12181 negative amount for the old (losing) fund and a positive amount for the new
12182 (gaining) fund.  In some cases there may be more than one such pair of entries
12183 in order to pull the money from different funding sources, or more specifically
12184 from different funding source credits.  For each such pair there is also an
12185 entry in acq.fund_transfer.
12186
12187 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12188 choose a funding source for the transferred money to come from.  This choice
12189 must meet two constraints, so far as possible:
12190
12191 1. The amount transferred from a given funding source must not exceed the
12192 amount allocated to the old fund by the funding source.  To that end we
12193 compare the amount being transferred to the amount allocated.
12194
12195 2. We shouldn't transfer money that has already been spent or encumbered, as
12196 defined by the funding attribution process.  We attribute expenses to the
12197 oldest funding source credits first.  In order to avoid transferring that
12198 attributed money, we reverse the priority, transferring from the newest funding
12199 source credits first.  There can be no guarantee that this approach will
12200 avoid overcommitting a fund, but no other approach can do any better.
12201
12202 In this context the age of a funding source credit is defined by the
12203 deadline_date for credits with deadline_dates, and by the effective_date for
12204 credits without deadline_dates, with the proviso that credits with deadline_dates
12205 are all considered "older" than those without.
12206
12207 ----------
12208
12209 In the signature for this function, there is one last parameter commented out,
12210 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12211 driving the main loop has an OR clause commented out, which references the
12212 funding_source_in parameter.
12213
12214 If these lines are uncommented, this function will allow the user optionally to
12215 restrict a fund transfer to a specified funding source.  If the source
12216 parameter is left NULL, then there will be no such restriction.
12217
12218 ------------------------------------------------------------------------------- */ 
12219 DECLARE
12220         same_currency      BOOLEAN;
12221         currency_ratio     NUMERIC;
12222         old_fund_currency  TEXT;
12223         old_remaining      NUMERIC;  -- in currency of old fund
12224         new_fund_currency  TEXT;
12225         new_fund_active    BOOLEAN;
12226         new_remaining      NUMERIC;  -- in currency of new fund
12227         curr_old_amt       NUMERIC;  -- in currency of old fund
12228         curr_new_amt       NUMERIC;  -- in currency of new fund
12229         source_addition    NUMERIC;  -- in currency of funding source
12230         source_deduction   NUMERIC;  -- in currency of funding source
12231         orig_allocated_amt NUMERIC;  -- in currency of funding source
12232         allocated_amt      NUMERIC;  -- in currency of fund
12233         source             RECORD;
12234 BEGIN
12235         --
12236         -- Sanity checks
12237         --
12238         IF old_fund IS NULL THEN
12239                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12240         END IF;
12241         --
12242         IF old_amount IS NULL THEN
12243                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12244         END IF;
12245         --
12246         -- The new fund and its amount must be both NULL or both not NULL.
12247         --
12248         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12249                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12250         END IF;
12251         --
12252         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12253                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12254         END IF;
12255         --
12256         IF user_id IS NULL THEN
12257                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12258         END IF;
12259         --
12260         -- Initialize the amounts to be transferred, each denominated
12261         -- in the currency of its respective fund.  They will be
12262         -- reduced on each iteration of the loop.
12263         --
12264         old_remaining := old_amount;
12265         new_remaining := new_amount;
12266         --
12267         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12268         --      old_amount, old_fund, new_amount, new_fund;
12269         --
12270         -- Get the currency types of the old and new funds.
12271         --
12272         SELECT
12273                 currency_type
12274         INTO
12275                 old_fund_currency
12276         FROM
12277                 acq.fund
12278         WHERE
12279                 id = old_fund;
12280         --
12281         IF old_fund_currency IS NULL THEN
12282                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12283         END IF;
12284         --
12285         IF new_fund IS NOT NULL THEN
12286                 SELECT
12287                         currency_type,
12288                         active
12289                 INTO
12290                         new_fund_currency,
12291                         new_fund_active
12292                 FROM
12293                         acq.fund
12294                 WHERE
12295                         id = new_fund;
12296                 --
12297                 IF new_fund_currency IS NULL THEN
12298                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12299                 ELSIF NOT new_fund_active THEN
12300                         --
12301                         -- No point in putting money into a fund from whence you can't spend it
12302                         --
12303                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12304                 END IF;
12305                 --
12306                 IF new_amount = old_amount THEN
12307                         same_currency := true;
12308                         currency_ratio := 1;
12309                 ELSE
12310                         --
12311                         -- We'll have to translate currency between funds.  We presume that
12312                         -- the calling code has already applied an appropriate exchange rate,
12313                         -- so we'll apply the same conversion to each sub-transfer.
12314                         --
12315                         same_currency := false;
12316                         currency_ratio := new_amount / old_amount;
12317                 END IF;
12318         END IF;
12319         --
12320         -- Identify the funding source(s) from which we want to transfer the money.
12321         -- The principle is that we want to transfer the newest money first, because
12322         -- we spend the oldest money first.  The priority for spending is defined
12323         -- by a sort of the view acq.ordered_funding_source_credit.
12324         --
12325         FOR source in
12326                 SELECT
12327                         ofsc.id,
12328                         ofsc.funding_source,
12329                         ofsc.amount,
12330                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12331                                 AS converted_amt,
12332                         fs.currency_type
12333                 FROM
12334                         acq.ordered_funding_source_credit AS ofsc,
12335                         acq.funding_source fs
12336                 WHERE
12337                         ofsc.funding_source = fs.id
12338                         and ofsc.funding_source IN
12339                         (
12340                                 SELECT funding_source
12341                                 FROM acq.fund_allocation
12342                                 WHERE fund = old_fund
12343                         )
12344                         -- and
12345                         -- (
12346                         --      ofsc.funding_source = funding_source_in
12347                         --      OR funding_source_in IS NULL
12348                         -- )
12349                 ORDER BY
12350                         ofsc.sort_priority desc,
12351                         ofsc.sort_date desc,
12352                         ofsc.id desc
12353         LOOP
12354                 --
12355                 -- Determine how much money the old fund got from this funding source,
12356                 -- denominated in the currency types of the source and of the fund.
12357                 -- This result may reflect transfers from previous iterations.
12358                 --
12359                 SELECT
12360                         COALESCE( sum( amount ), 0 ),
12361                         COALESCE( sum( amount )
12362                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12363                 INTO
12364                         orig_allocated_amt,     -- in currency of the source
12365                         allocated_amt           -- in currency of the old fund
12366                 FROM
12367                         acq.fund_allocation
12368                 WHERE
12369                         fund = old_fund
12370                         and funding_source = source.funding_source;
12371                 --      
12372                 -- Determine how much to transfer from this credit, in the currency
12373                 -- of the fund.   Begin with the amount remaining to be attributed:
12374                 --
12375                 curr_old_amt := old_remaining;
12376                 --
12377                 -- Can't attribute more than was allocated from the fund:
12378                 --
12379                 IF curr_old_amt > allocated_amt THEN
12380                         curr_old_amt := allocated_amt;
12381                 END IF;
12382                 --
12383                 -- Can't attribute more than the amount of the current credit:
12384                 --
12385                 IF curr_old_amt > source.converted_amt THEN
12386                         curr_old_amt := source.converted_amt;
12387                 END IF;
12388                 --
12389                 curr_old_amt := trunc( curr_old_amt, 2 );
12390                 --
12391                 old_remaining := old_remaining - curr_old_amt;
12392                 --
12393                 -- Determine the amount to be deducted, if any,
12394                 -- from the old allocation.
12395                 --
12396                 IF old_remaining > 0 THEN
12397                         --
12398                         -- In this case we're using the whole allocation, so use that
12399                         -- amount directly instead of applying a currency translation
12400                         -- and thereby inviting round-off errors.
12401                         --
12402                         source_deduction := - orig_allocated_amt;
12403                 ELSE 
12404                         source_deduction := trunc(
12405                                 ( - curr_old_amt ) *
12406                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12407                                 2 );
12408                 END IF;
12409                 --
12410                 IF source_deduction <> 0 THEN
12411                         --
12412                         -- Insert negative allocation for old fund in fund_allocation,
12413                         -- converted into the currency of the funding source
12414                         --
12415                         INSERT INTO acq.fund_allocation (
12416                                 funding_source,
12417                                 fund,
12418                                 amount,
12419                                 allocator,
12420                                 note
12421                         ) VALUES (
12422                                 source.funding_source,
12423                                 old_fund,
12424                                 source_deduction,
12425                                 user_id,
12426                                 'Transfer to fund ' || new_fund
12427                         );
12428                 END IF;
12429                 --
12430                 IF new_fund IS NOT NULL THEN
12431                         --
12432                         -- Determine how much to add to the new fund, in
12433                         -- its currency, and how much remains to be added:
12434                         --
12435                         IF same_currency THEN
12436                                 curr_new_amt := curr_old_amt;
12437                         ELSE
12438                                 IF old_remaining = 0 THEN
12439                                         --
12440                                         -- This is the last iteration, so nothing should be left
12441                                         --
12442                                         curr_new_amt := new_remaining;
12443                                         new_remaining := 0;
12444                                 ELSE
12445                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12446                                         new_remaining := new_remaining - curr_new_amt;
12447                                 END IF;
12448                         END IF;
12449                         --
12450                         -- Determine how much to add, if any,
12451                         -- to the new fund's allocation.
12452                         --
12453                         IF old_remaining > 0 THEN
12454                                 --
12455                                 -- In this case we're using the whole allocation, so use that amount
12456                                 -- amount directly instead of applying a currency translation and
12457                                 -- thereby inviting round-off errors.
12458                                 --
12459                                 source_addition := orig_allocated_amt;
12460                         ELSIF source.currency_type = old_fund_currency THEN
12461                                 --
12462                                 -- In this case we don't need a round trip currency translation,
12463                                 -- thereby inviting round-off errors:
12464                                 --
12465                                 source_addition := curr_old_amt;
12466                         ELSE 
12467                                 source_addition := trunc(
12468                                         curr_new_amt *
12469                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12470                                         2 );
12471                         END IF;
12472                         --
12473                         IF source_addition <> 0 THEN
12474                                 --
12475                                 -- Insert positive allocation for new fund in fund_allocation,
12476                                 -- converted to the currency of the founding source
12477                                 --
12478                                 INSERT INTO acq.fund_allocation (
12479                                         funding_source,
12480                                         fund,
12481                                         amount,
12482                                         allocator,
12483                                         note
12484                                 ) VALUES (
12485                                         source.funding_source,
12486                                         new_fund,
12487                                         source_addition,
12488                                         user_id,
12489                                         'Transfer from fund ' || old_fund
12490                                 );
12491                         END IF;
12492                 END IF;
12493                 --
12494                 IF trunc( curr_old_amt, 2 ) <> 0
12495                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12496                         --
12497                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12498                         --
12499                         INSERT INTO acq.fund_transfer (
12500                                 src_fund,
12501                                 src_amount,
12502                                 dest_fund,
12503                                 dest_amount,
12504                                 transfer_user,
12505                                 note,
12506                                 funding_source_credit
12507                         ) VALUES (
12508                                 old_fund,
12509                                 trunc( curr_old_amt, 2 ),
12510                                 new_fund,
12511                                 trunc( curr_new_amt, 2 ),
12512                                 user_id,
12513                                 xfer_note,
12514                                 source.id
12515                         );
12516                 END IF;
12517                 --
12518                 if old_remaining <= 0 THEN
12519                         EXIT;                   -- Nothing more to be transferred
12520                 END IF;
12521         END LOOP;
12522 END;
12523 $$ LANGUAGE plpgsql;
12524
12525 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12526         old_year INTEGER,
12527         user_id INTEGER,
12528         org_unit_id INTEGER
12529 ) RETURNS VOID AS $$
12530 DECLARE
12531 --
12532 new_id      INT;
12533 old_fund    RECORD;
12534 org_found   BOOLEAN;
12535 --
12536 BEGIN
12537         --
12538         -- Sanity checks
12539         --
12540         IF old_year IS NULL THEN
12541                 RAISE EXCEPTION 'Input year argument is NULL';
12542         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12543                 RAISE EXCEPTION 'Input year is out of range';
12544         END IF;
12545         --
12546         IF user_id IS NULL THEN
12547                 RAISE EXCEPTION 'Input user id argument is NULL';
12548         END IF;
12549         --
12550         IF org_unit_id IS NULL THEN
12551                 RAISE EXCEPTION 'Org unit id argument is NULL';
12552         ELSE
12553                 SELECT TRUE INTO org_found
12554                 FROM actor.org_unit
12555                 WHERE id = org_unit_id;
12556                 --
12557                 IF org_found IS NULL THEN
12558                         RAISE EXCEPTION 'Org unit id is invalid';
12559                 END IF;
12560         END IF;
12561         --
12562         -- Loop over the applicable funds
12563         --
12564         FOR old_fund in SELECT * FROM acq.fund
12565         WHERE
12566                 year = old_year
12567                 AND propagate
12568                 AND org = org_unit_id
12569         LOOP
12570                 BEGIN
12571                         INSERT INTO acq.fund (
12572                                 org,
12573                                 name,
12574                                 year,
12575                                 currency_type,
12576                                 code,
12577                                 rollover,
12578                                 propagate,
12579                                 balance_warning_percent,
12580                                 balance_stop_percent
12581                         ) VALUES (
12582                                 old_fund.org,
12583                                 old_fund.name,
12584                                 old_year + 1,
12585                                 old_fund.currency_type,
12586                                 old_fund.code,
12587                                 old_fund.rollover,
12588                                 true,
12589                                 old_fund.balance_warning_percent,
12590                                 old_fund.balance_stop_percent
12591                         )
12592                         RETURNING id INTO new_id;
12593                 EXCEPTION
12594                         WHEN unique_violation THEN
12595                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12596                                 CONTINUE;
12597                 END;
12598                 --RAISE NOTICE 'Propagating fund % to fund %',
12599                 --      old_fund.code, new_id;
12600         END LOOP;
12601 END;
12602 $$ LANGUAGE plpgsql;
12603
12604 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12605         old_year INTEGER,
12606         user_id INTEGER,
12607         org_unit_id INTEGER
12608 ) RETURNS VOID AS $$
12609 DECLARE
12610 --
12611 new_id      INT;
12612 old_fund    RECORD;
12613 org_found   BOOLEAN;
12614 --
12615 BEGIN
12616         --
12617         -- Sanity checks
12618         --
12619         IF old_year IS NULL THEN
12620                 RAISE EXCEPTION 'Input year argument is NULL';
12621         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12622                 RAISE EXCEPTION 'Input year is out of range';
12623         END IF;
12624         --
12625         IF user_id IS NULL THEN
12626                 RAISE EXCEPTION 'Input user id argument is NULL';
12627         END IF;
12628         --
12629         IF org_unit_id IS NULL THEN
12630                 RAISE EXCEPTION 'Org unit id argument is NULL';
12631         ELSE
12632                 SELECT TRUE INTO org_found
12633                 FROM actor.org_unit
12634                 WHERE id = org_unit_id;
12635                 --
12636                 IF org_found IS NULL THEN
12637                         RAISE EXCEPTION 'Org unit id is invalid';
12638                 END IF;
12639         END IF;
12640         --
12641         -- Loop over the applicable funds
12642         --
12643         FOR old_fund in SELECT * FROM acq.fund
12644         WHERE
12645                 year = old_year
12646                 AND propagate
12647                 AND org in (
12648                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12649                 )
12650         LOOP
12651                 BEGIN
12652                         INSERT INTO acq.fund (
12653                                 org,
12654                                 name,
12655                                 year,
12656                                 currency_type,
12657                                 code,
12658                                 rollover,
12659                                 propagate,
12660                                 balance_warning_percent,
12661                                 balance_stop_percent
12662                         ) VALUES (
12663                                 old_fund.org,
12664                                 old_fund.name,
12665                                 old_year + 1,
12666                                 old_fund.currency_type,
12667                                 old_fund.code,
12668                                 old_fund.rollover,
12669                                 true,
12670                                 old_fund.balance_warning_percent,
12671                                 old_fund.balance_stop_percent
12672                         )
12673                         RETURNING id INTO new_id;
12674                 EXCEPTION
12675                         WHEN unique_violation THEN
12676                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12677                                 CONTINUE;
12678                 END;
12679                 --RAISE NOTICE 'Propagating fund % to fund %',
12680                 --      old_fund.code, new_id;
12681         END LOOP;
12682 END;
12683 $$ LANGUAGE plpgsql;
12684
12685 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12686         old_year INTEGER,
12687         user_id INTEGER,
12688         org_unit_id INTEGER
12689 ) RETURNS VOID AS $$
12690 DECLARE
12691 --
12692 new_fund    INT;
12693 new_year    INT := old_year + 1;
12694 org_found   BOOL;
12695 xfer_amount NUMERIC;
12696 roll_fund   RECORD;
12697 deb         RECORD;
12698 detail      RECORD;
12699 --
12700 BEGIN
12701         --
12702         -- Sanity checks
12703         --
12704         IF old_year IS NULL THEN
12705                 RAISE EXCEPTION 'Input year argument is NULL';
12706     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12707         RAISE EXCEPTION 'Input year is out of range';
12708         END IF;
12709         --
12710         IF user_id IS NULL THEN
12711                 RAISE EXCEPTION 'Input user id argument is NULL';
12712         END IF;
12713         --
12714         IF org_unit_id IS NULL THEN
12715                 RAISE EXCEPTION 'Org unit id argument is NULL';
12716         ELSE
12717                 --
12718                 -- Validate the org unit
12719                 --
12720                 SELECT TRUE
12721                 INTO org_found
12722                 FROM actor.org_unit
12723                 WHERE id = org_unit_id;
12724                 --
12725                 IF org_found IS NULL THEN
12726                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12727                 END IF;
12728         END IF;
12729         --
12730         -- Loop over the propagable funds to identify the details
12731         -- from the old fund plus the id of the new one, if it exists.
12732         --
12733         FOR roll_fund in
12734         SELECT
12735             oldf.id AS old_fund,
12736             oldf.org,
12737             oldf.name,
12738             oldf.currency_type,
12739             oldf.code,
12740                 oldf.rollover,
12741             newf.id AS new_fund_id
12742         FROM
12743         acq.fund AS oldf
12744         LEFT JOIN acq.fund AS newf
12745                 ON ( oldf.code = newf.code )
12746         WHERE
12747                     oldf.org = org_unit_id
12748                 and oldf.year = old_year
12749                 and oldf.propagate
12750         and newf.year = new_year
12751         LOOP
12752                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12753                 --
12754                 IF roll_fund.new_fund_id IS NULL THEN
12755                         --
12756                         -- The old fund hasn't been propagated yet.  Propagate it now.
12757                         --
12758                         INSERT INTO acq.fund (
12759                                 org,
12760                                 name,
12761                                 year,
12762                                 currency_type,
12763                                 code,
12764                                 rollover,
12765                                 propagate,
12766                                 balance_warning_percent,
12767                                 balance_stop_percent
12768                         ) VALUES (
12769                                 roll_fund.org,
12770                                 roll_fund.name,
12771                                 new_year,
12772                                 roll_fund.currency_type,
12773                                 roll_fund.code,
12774                                 true,
12775                                 true,
12776                                 roll_fund.balance_warning_percent,
12777                                 roll_fund.balance_stop_percent
12778                         )
12779                         RETURNING id INTO new_fund;
12780                 ELSE
12781                         new_fund = roll_fund.new_fund_id;
12782                 END IF;
12783                 --
12784                 -- Determine the amount to transfer
12785                 --
12786                 SELECT amount
12787                 INTO xfer_amount
12788                 FROM acq.fund_spent_balance
12789                 WHERE fund = roll_fund.old_fund;
12790                 --
12791                 IF xfer_amount <> 0 THEN
12792                         IF roll_fund.rollover THEN
12793                                 --
12794                                 -- Transfer balance from old fund to new
12795                                 --
12796                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12797                                 --
12798                                 PERFORM acq.transfer_fund(
12799                                         roll_fund.old_fund,
12800                                         xfer_amount,
12801                                         new_fund,
12802                                         xfer_amount,
12803                                         user_id,
12804                                         'Rollover'
12805                                 );
12806                         ELSE
12807                                 --
12808                                 -- Transfer balance from old fund to the void
12809                                 --
12810                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12811                                 --
12812                                 PERFORM acq.transfer_fund(
12813                                         roll_fund.old_fund,
12814                                         xfer_amount,
12815                                         NULL,
12816                                         NULL,
12817                                         user_id,
12818                                         'Rollover'
12819                                 );
12820                         END IF;
12821                 END IF;
12822                 --
12823                 IF roll_fund.rollover THEN
12824                         --
12825                         -- Move any lineitems from the old fund to the new one
12826                         -- where the associated debit is an encumbrance.
12827                         --
12828                         -- Any other tables tying expenditure details to funds should
12829                         -- receive similar treatment.  At this writing there are none.
12830                         --
12831                         UPDATE acq.lineitem_detail
12832                         SET fund = new_fund
12833                         WHERE
12834                         fund = roll_fund.old_fund -- this condition may be redundant
12835                         AND fund_debit in
12836                         (
12837                                 SELECT id
12838                                 FROM acq.fund_debit
12839                                 WHERE
12840                                 fund = roll_fund.old_fund
12841                                 AND encumbrance
12842                         );
12843                         --
12844                         -- Move encumbrance debits from the old fund to the new fund
12845                         --
12846                         UPDATE acq.fund_debit
12847                         SET fund = new_fund
12848                         wHERE
12849                                 fund = roll_fund.old_fund
12850                                 AND encumbrance;
12851                 END IF;
12852                 --
12853                 -- Mark old fund as inactive, now that we've closed it
12854                 --
12855                 UPDATE acq.fund
12856                 SET active = FALSE
12857                 WHERE id = roll_fund.old_fund;
12858         END LOOP;
12859 END;
12860 $$ LANGUAGE plpgsql;
12861
12862 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12863         old_year INTEGER,
12864         user_id INTEGER,
12865         org_unit_id INTEGER
12866 ) RETURNS VOID AS $$
12867 DECLARE
12868 --
12869 new_fund    INT;
12870 new_year    INT := old_year + 1;
12871 org_found   BOOL;
12872 xfer_amount NUMERIC;
12873 roll_fund   RECORD;
12874 deb         RECORD;
12875 detail      RECORD;
12876 --
12877 BEGIN
12878         --
12879         -- Sanity checks
12880         --
12881         IF old_year IS NULL THEN
12882                 RAISE EXCEPTION 'Input year argument is NULL';
12883     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12884         RAISE EXCEPTION 'Input year is out of range';
12885         END IF;
12886         --
12887         IF user_id IS NULL THEN
12888                 RAISE EXCEPTION 'Input user id argument is NULL';
12889         END IF;
12890         --
12891         IF org_unit_id IS NULL THEN
12892                 RAISE EXCEPTION 'Org unit id argument is NULL';
12893         ELSE
12894                 --
12895                 -- Validate the org unit
12896                 --
12897                 SELECT TRUE
12898                 INTO org_found
12899                 FROM actor.org_unit
12900                 WHERE id = org_unit_id;
12901                 --
12902                 IF org_found IS NULL THEN
12903                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12904                 END IF;
12905         END IF;
12906         --
12907         -- Loop over the propagable funds to identify the details
12908         -- from the old fund plus the id of the new one, if it exists.
12909         --
12910         FOR roll_fund in
12911         SELECT
12912             oldf.id AS old_fund,
12913             oldf.org,
12914             oldf.name,
12915             oldf.currency_type,
12916             oldf.code,
12917                 oldf.rollover,
12918             newf.id AS new_fund_id
12919         FROM
12920         acq.fund AS oldf
12921         LEFT JOIN acq.fund AS newf
12922                 ON ( oldf.code = newf.code )
12923         WHERE
12924                     oldf.year = old_year
12925                 AND oldf.propagate
12926         AND newf.year = new_year
12927                 AND oldf.org in (
12928                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12929                 )
12930         LOOP
12931                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12932                 --
12933                 IF roll_fund.new_fund_id IS NULL THEN
12934                         --
12935                         -- The old fund hasn't been propagated yet.  Propagate it now.
12936                         --
12937                         INSERT INTO acq.fund (
12938                                 org,
12939                                 name,
12940                                 year,
12941                                 currency_type,
12942                                 code,
12943                                 rollover,
12944                                 propagate,
12945                                 balance_warning_percent,
12946                                 balance_stop_percent
12947                         ) VALUES (
12948                                 roll_fund.org,
12949                                 roll_fund.name,
12950                                 new_year,
12951                                 roll_fund.currency_type,
12952                                 roll_fund.code,
12953                                 true,
12954                                 true,
12955                                 roll_fund.balance_warning_percent,
12956                                 roll_fund.balance_stop_percent
12957                         )
12958                         RETURNING id INTO new_fund;
12959                 ELSE
12960                         new_fund = roll_fund.new_fund_id;
12961                 END IF;
12962                 --
12963                 -- Determine the amount to transfer
12964                 --
12965                 SELECT amount
12966                 INTO xfer_amount
12967                 FROM acq.fund_spent_balance
12968                 WHERE fund = roll_fund.old_fund;
12969                 --
12970                 IF xfer_amount <> 0 THEN
12971                         IF roll_fund.rollover THEN
12972                                 --
12973                                 -- Transfer balance from old fund to new
12974                                 --
12975                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12976                                 --
12977                                 PERFORM acq.transfer_fund(
12978                                         roll_fund.old_fund,
12979                                         xfer_amount,
12980                                         new_fund,
12981                                         xfer_amount,
12982                                         user_id,
12983                                         'Rollover'
12984                                 );
12985                         ELSE
12986                                 --
12987                                 -- Transfer balance from old fund to the void
12988                                 --
12989                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12990                                 --
12991                                 PERFORM acq.transfer_fund(
12992                                         roll_fund.old_fund,
12993                                         xfer_amount,
12994                                         NULL,
12995                                         NULL,
12996                                         user_id,
12997                                         'Rollover'
12998                                 );
12999                         END IF;
13000                 END IF;
13001                 --
13002                 IF roll_fund.rollover THEN
13003                         --
13004                         -- Move any lineitems from the old fund to the new one
13005                         -- where the associated debit is an encumbrance.
13006                         --
13007                         -- Any other tables tying expenditure details to funds should
13008                         -- receive similar treatment.  At this writing there are none.
13009                         --
13010                         UPDATE acq.lineitem_detail
13011                         SET fund = new_fund
13012                         WHERE
13013                         fund = roll_fund.old_fund -- this condition may be redundant
13014                         AND fund_debit in
13015                         (
13016                                 SELECT id
13017                                 FROM acq.fund_debit
13018                                 WHERE
13019                                 fund = roll_fund.old_fund
13020                                 AND encumbrance
13021                         );
13022                         --
13023                         -- Move encumbrance debits from the old fund to the new fund
13024                         --
13025                         UPDATE acq.fund_debit
13026                         SET fund = new_fund
13027                         wHERE
13028                                 fund = roll_fund.old_fund
13029                                 AND encumbrance;
13030                 END IF;
13031                 --
13032                 -- Mark old fund as inactive, now that we've closed it
13033                 --
13034                 UPDATE acq.fund
13035                 SET active = FALSE
13036                 WHERE id = roll_fund.old_fund;
13037         END LOOP;
13038 END;
13039 $$ LANGUAGE plpgsql;
13040
13041 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13042     SELECT regexp_replace($1, ',', '', 'g');
13043 $$ LANGUAGE SQL STRICT IMMUTABLE;
13044
13045 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13046     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13047 $$ LANGUAGE SQL STRICT IMMUTABLE;
13048
13049 CREATE TABLE acq.distribution_formula_application (
13050     id BIGSERIAL PRIMARY KEY,
13051     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13052     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13053     formula INT NOT NULL
13054         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13055     lineitem INT NOT NULL
13056         REFERENCES acq.lineitem( id )
13057                 ON DELETE CASCADE
13058                 DEFERRABLE INITIALLY DEFERRED
13059 );
13060
13061 CREATE INDEX acqdfa_df_idx
13062     ON acq.distribution_formula_application(formula);
13063 CREATE INDEX acqdfa_li_idx
13064     ON acq.distribution_formula_application(lineitem);
13065 CREATE INDEX acqdfa_creator_idx
13066     ON acq.distribution_formula_application(creator);
13067
13068 CREATE TABLE acq.user_request_type (
13069     id      SERIAL  PRIMARY KEY,
13070     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13071 );
13072
13073 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13074 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13075 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13076 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13077 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13078
13079 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13080
13081 CREATE TABLE acq.cancel_reason (
13082         id            SERIAL            PRIMARY KEY,
13083         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13084                                         DEFERRABLE INITIALLY DEFERRED,
13085         label         TEXT              NOT NULL,
13086         description   TEXT              NOT NULL,
13087         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13088         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13089 );
13090
13091 -- Reserve ids 1-999 for stock reasons
13092 -- Reserve ids 1000-1999 for EDI reasons
13093 -- 2000+ are available for staff to create
13094
13095 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13096
13097 CREATE TABLE acq.user_request (
13098     id                  SERIAL  PRIMARY KEY,
13099     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13100     hold                BOOL    NOT NULL DEFAULT TRUE,
13101
13102     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13103     holdable_formats    TEXT,           -- nullable, for use in hold creation
13104     phone_notify        TEXT,
13105     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13106     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13107     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13108     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13109     need_before         TIMESTAMPTZ,    -- don't create holds after this
13110     max_fee             TEXT,
13111
13112     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13113     isxn                TEXT,
13114     title               TEXT,
13115     volume              TEXT,
13116     author              TEXT,
13117     article_title       TEXT,
13118     article_pages       TEXT,
13119     publisher           TEXT,
13120     location            TEXT,
13121     pubdate             TEXT,
13122     mentioned           TEXT,
13123     other_info          TEXT,
13124         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13125                                              DEFERRABLE INITIALLY DEFERRED
13126 );
13127
13128 CREATE TABLE acq.lineitem_alert_text (
13129         id               SERIAL         PRIMARY KEY,
13130         code             TEXT           NOT NULL,
13131         description      TEXT,
13132         owning_lib       INT            NOT NULL
13133                                         REFERENCES actor.org_unit(id)
13134                                         DEFERRABLE INITIALLY DEFERRED,
13135         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13136 );
13137
13138 ALTER TABLE acq.lineitem_note
13139         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13140                                          DEFERRABLE INITIALLY DEFERRED;
13141
13142 -- add ON DELETE CASCADE clause
13143
13144 ALTER TABLE acq.lineitem_note
13145         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13146
13147 ALTER TABLE acq.lineitem_note
13148         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13149                 ON DELETE CASCADE
13150                 DEFERRABLE INITIALLY DEFERRED;
13151
13152 ALTER TABLE acq.lineitem_note
13153         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13154
13155 CREATE TABLE acq.invoice_method (
13156     code    TEXT    PRIMARY KEY,
13157     name    TEXT    NOT NULL -- i18n-ize
13158 );
13159 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13160 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13161
13162 CREATE TABLE acq.invoice_payment_method (
13163         code      TEXT     PRIMARY KEY,
13164         name      TEXT     NOT NULL
13165 );
13166
13167 CREATE TABLE acq.invoice (
13168     id             SERIAL      PRIMARY KEY,
13169     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13170     provider       INT         NOT NULL REFERENCES acq.provider (id),
13171     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13172     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13173     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13174     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13175     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13176         payment_auth   TEXT,
13177         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13178                                    DEFERRABLE INITIALLY DEFERRED,
13179         note           TEXT,
13180     complete       BOOL        NOT NULL DEFAULT FALSE,
13181     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13182 );
13183
13184 CREATE TABLE acq.invoice_entry (
13185     id              SERIAL      PRIMARY KEY,
13186     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13187     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13188     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13189     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13190     phys_item_count INT, -- and how many did staff count
13191     note            TEXT,
13192     billed_per_item BOOL,
13193     cost_billed     NUMERIC(8,2),
13194     actual_cost     NUMERIC(8,2),
13195         amount_paid     NUMERIC (8,2)
13196 );
13197
13198 CREATE TABLE acq.invoice_item_type (
13199     code    TEXT    PRIMARY KEY,
13200     name    TEXT    NOT NULL, -- i18n-ize
13201         prorate BOOL    NOT NULL DEFAULT FALSE
13202 );
13203
13204 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13205 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13206 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13207 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13208 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13209 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13210
13211 CREATE TABLE acq.po_item (
13212         id              SERIAL      PRIMARY KEY,
13213         purchase_order  INT         REFERENCES acq.purchase_order (id)
13214                                     ON UPDATE CASCADE ON DELETE SET NULL
13215                                     DEFERRABLE INITIALLY DEFERRED,
13216         fund_debit      INT         REFERENCES acq.fund_debit (id)
13217                                     DEFERRABLE INITIALLY DEFERRED,
13218         inv_item_type   TEXT        NOT NULL
13219                                     REFERENCES acq.invoice_item_type (code)
13220                                     DEFERRABLE INITIALLY DEFERRED,
13221         title           TEXT,
13222         author          TEXT,
13223         note            TEXT,
13224         estimated_cost  NUMERIC(8,2),
13225         fund            INT         REFERENCES acq.fund (id)
13226                                     DEFERRABLE INITIALLY DEFERRED,
13227         target          BIGINT
13228 );
13229
13230 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13231     id              SERIAL      PRIMARY KEY,
13232     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13233     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13234     fund_debit      INT         REFERENCES acq.fund_debit (id),
13235     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13236     title           TEXT,
13237     author          TEXT,
13238     note            TEXT,
13239     cost_billed     NUMERIC(8,2),
13240     actual_cost     NUMERIC(8,2),
13241     fund            INT         REFERENCES acq.fund (id)
13242                                 DEFERRABLE INITIALLY DEFERRED,
13243     amount_paid     NUMERIC (8,2),
13244     po_item         INT         REFERENCES acq.po_item (id)
13245                                 DEFERRABLE INITIALLY DEFERRED,
13246     target          BIGINT
13247 );
13248
13249 CREATE TABLE acq.edi_message (
13250     id               SERIAL          PRIMARY KEY,
13251     account          INTEGER         REFERENCES acq.edi_account(id)
13252                                      DEFERRABLE INITIALLY DEFERRED,
13253     remote_file      TEXT,
13254     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13255     translate_time   TIMESTAMPTZ,
13256     process_time     TIMESTAMPTZ,
13257     error_time       TIMESTAMPTZ,
13258     status           TEXT            NOT NULL DEFAULT 'new'
13259                                      CONSTRAINT status_value CHECK
13260                                      ( status IN (
13261                                         'new',          -- needs to be translated
13262                                         'translated',   -- needs to be processed
13263                                         'trans_error',  -- error in translation step
13264                                         'processed',    -- needs to have remote_file deleted
13265                                         'proc_error',   -- error in processing step
13266                                         'delete_error', -- error in deletion
13267                                         'retry',        -- need to retry
13268                                         'complete'      -- done
13269                                      )),
13270     edi              TEXT,
13271     jedi             TEXT,
13272     error            TEXT,
13273     purchase_order   INT             REFERENCES acq.purchase_order
13274                                      DEFERRABLE INITIALLY DEFERRED,
13275     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13276                                      CHECK ( message_type IN (
13277                                         'ORDERS',
13278                                         'ORDRSP',
13279                                         'INVOIC',
13280                                         'OSTENQ',
13281                                         'OSTRPT'
13282                                      ))
13283 );
13284
13285 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13286
13287 ALTER TABLE acq.provider_address
13288         ADD COLUMN fax_phone TEXT;
13289
13290 ALTER TABLE acq.provider_contact_address
13291         ADD COLUMN fax_phone TEXT;
13292
13293 CREATE TABLE acq.provider_note (
13294     id      SERIAL              PRIMARY KEY,
13295     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13296     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13297     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13298     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13299     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13300     value       TEXT            NOT NULL
13301 );
13302 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13303 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13304 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13305
13306 -- For each fund: the total allocation from all sources, in the
13307 -- currency of the fund (or 0 if there are no allocations)
13308
13309 CREATE VIEW acq.all_fund_allocation_total AS
13310 SELECT
13311     f.id AS fund,
13312     COALESCE( SUM( a.amount * acq.exchange_ratio(
13313         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13314     AS amount
13315 FROM
13316     acq.fund f
13317         LEFT JOIN acq.fund_allocation a
13318             ON a.fund = f.id
13319         LEFT JOIN acq.funding_source s
13320             ON a.funding_source = s.id
13321 GROUP BY
13322     f.id;
13323
13324 -- For every fund: the total encumbrances (or 0 if none),
13325 -- in the currency of the fund.
13326
13327 CREATE VIEW acq.all_fund_encumbrance_total AS
13328 SELECT
13329         f.id AS fund,
13330         COALESCE( encumb.amount, 0 ) AS amount
13331 FROM
13332         acq.fund AS f
13333                 LEFT JOIN (
13334                         SELECT
13335                                 fund,
13336                                 sum( amount ) AS amount
13337                         FROM
13338                                 acq.fund_debit
13339                         WHERE
13340                                 encumbrance
13341                         GROUP BY fund
13342                 ) AS encumb
13343                         ON f.id = encumb.fund;
13344
13345 -- For every fund: the total spent (or 0 if none),
13346 -- in the currency of the fund.
13347
13348 CREATE VIEW acq.all_fund_spent_total AS
13349 SELECT
13350     f.id AS fund,
13351     COALESCE( spent.amount, 0 ) AS amount
13352 FROM
13353     acq.fund AS f
13354         LEFT JOIN (
13355             SELECT
13356                 fund,
13357                 sum( amount ) AS amount
13358             FROM
13359                 acq.fund_debit
13360             WHERE
13361                 NOT encumbrance
13362             GROUP BY fund
13363         ) AS spent
13364             ON f.id = spent.fund;
13365
13366 -- For each fund: the amount not yet spent, in the currency
13367 -- of the fund.  May include encumbrances.
13368
13369 CREATE VIEW acq.all_fund_spent_balance AS
13370 SELECT
13371         c.fund,
13372         c.amount - d.amount AS amount
13373 FROM acq.all_fund_allocation_total c
13374     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13375
13376 -- For each fund: the amount neither spent nor encumbered,
13377 -- in the currency of the fund
13378
13379 CREATE VIEW acq.all_fund_combined_balance AS
13380 SELECT
13381      a.fund,
13382      a.amount - COALESCE( c.amount, 0 ) AS amount
13383 FROM
13384      acq.all_fund_allocation_total a
13385         LEFT OUTER JOIN (
13386             SELECT
13387                 fund,
13388                 SUM( amount ) AS amount
13389             FROM
13390                 acq.fund_debit
13391             GROUP BY
13392                 fund
13393         ) AS c USING ( fund );
13394
13395 CREATE OR REPLACE FUNCTION actor.usr_merge( src_usr INT, dest_usr INT, del_addrs BOOLEAN, del_cards BOOLEAN, deactivate_cards BOOLEAN ) RETURNS VOID AS $$
13396 DECLARE
13397         suffix TEXT;
13398         bucket_row RECORD;
13399         picklist_row RECORD;
13400         queue_row RECORD;
13401         folder_row RECORD;
13402 BEGIN
13403
13404     -- do some initial cleanup 
13405     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13406     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13407     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13408
13409     -- actor.*
13410     IF del_cards THEN
13411         DELETE FROM actor.card where usr = src_usr;
13412     ELSE
13413         IF deactivate_cards THEN
13414             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13415         END IF;
13416         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13417     END IF;
13418
13419
13420     IF del_addrs THEN
13421         DELETE FROM actor.usr_address WHERE usr = src_usr;
13422     ELSE
13423         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13424     END IF;
13425
13426     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13427     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13428     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13429     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13430     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13431
13432     -- permission.*
13433     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13434     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13435     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13436     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13437
13438
13439     -- container.*
13440         
13441         -- For each *_bucket table: transfer every bucket belonging to src_usr
13442         -- into the custody of dest_usr.
13443         --
13444         -- In order to avoid colliding with an existing bucket owned by
13445         -- the destination user, append the source user's id (in parenthesese)
13446         -- to the name.  If you still get a collision, add successive
13447         -- spaces to the name and keep trying until you succeed.
13448         --
13449         FOR bucket_row in
13450                 SELECT id, name
13451                 FROM   container.biblio_record_entry_bucket
13452                 WHERE  owner = src_usr
13453         LOOP
13454                 suffix := ' (' || src_usr || ')';
13455                 LOOP
13456                         BEGIN
13457                                 UPDATE  container.biblio_record_entry_bucket
13458                                 SET     owner = dest_usr, name = name || suffix
13459                                 WHERE   id = bucket_row.id;
13460                         EXCEPTION WHEN unique_violation THEN
13461                                 suffix := suffix || ' ';
13462                                 CONTINUE;
13463                         END;
13464                         EXIT;
13465                 END LOOP;
13466         END LOOP;
13467
13468         FOR bucket_row in
13469                 SELECT id, name
13470                 FROM   container.call_number_bucket
13471                 WHERE  owner = src_usr
13472         LOOP
13473                 suffix := ' (' || src_usr || ')';
13474                 LOOP
13475                         BEGIN
13476                                 UPDATE  container.call_number_bucket
13477                                 SET     owner = dest_usr, name = name || suffix
13478                                 WHERE   id = bucket_row.id;
13479                         EXCEPTION WHEN unique_violation THEN
13480                                 suffix := suffix || ' ';
13481                                 CONTINUE;
13482                         END;
13483                         EXIT;
13484                 END LOOP;
13485         END LOOP;
13486
13487         FOR bucket_row in
13488                 SELECT id, name
13489                 FROM   container.copy_bucket
13490                 WHERE  owner = src_usr
13491         LOOP
13492                 suffix := ' (' || src_usr || ')';
13493                 LOOP
13494                         BEGIN
13495                                 UPDATE  container.copy_bucket
13496                                 SET     owner = dest_usr, name = name || suffix
13497                                 WHERE   id = bucket_row.id;
13498                         EXCEPTION WHEN unique_violation THEN
13499                                 suffix := suffix || ' ';
13500                                 CONTINUE;
13501                         END;
13502                         EXIT;
13503                 END LOOP;
13504         END LOOP;
13505
13506         FOR bucket_row in
13507                 SELECT id, name
13508                 FROM   container.user_bucket
13509                 WHERE  owner = src_usr
13510         LOOP
13511                 suffix := ' (' || src_usr || ')';
13512                 LOOP
13513                         BEGIN
13514                                 UPDATE  container.user_bucket
13515                                 SET     owner = dest_usr, name = name || suffix
13516                                 WHERE   id = bucket_row.id;
13517                         EXCEPTION WHEN unique_violation THEN
13518                                 suffix := suffix || ' ';
13519                                 CONTINUE;
13520                         END;
13521                         EXIT;
13522                 END LOOP;
13523         END LOOP;
13524
13525         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13526
13527     -- vandelay.*
13528         -- transfer queues the same way we transfer buckets (see above)
13529         FOR queue_row in
13530                 SELECT id, name
13531                 FROM   vandelay.queue
13532                 WHERE  owner = src_usr
13533         LOOP
13534                 suffix := ' (' || src_usr || ')';
13535                 LOOP
13536                         BEGIN
13537                                 UPDATE  vandelay.queue
13538                                 SET     owner = dest_usr, name = name || suffix
13539                                 WHERE   id = queue_row.id;
13540                         EXCEPTION WHEN unique_violation THEN
13541                                 suffix := suffix || ' ';
13542                                 CONTINUE;
13543                         END;
13544                         EXIT;
13545                 END LOOP;
13546         END LOOP;
13547
13548     -- money.*
13549     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13550     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13551     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13552     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13553     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13554
13555     -- action.*
13556     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13557     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13558     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13559
13560     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13561     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13562     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13563     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13564
13565     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13566     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13567     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13568     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13569     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13570
13571     -- acq.*
13572     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13573         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13574
13575         -- transfer picklists the same way we transfer buckets (see above)
13576         FOR picklist_row in
13577                 SELECT id, name
13578                 FROM   acq.picklist
13579                 WHERE  owner = src_usr
13580         LOOP
13581                 suffix := ' (' || src_usr || ')';
13582                 LOOP
13583                         BEGIN
13584                                 UPDATE  acq.picklist
13585                                 SET     owner = dest_usr, name = name || suffix
13586                                 WHERE   id = picklist_row.id;
13587                         EXCEPTION WHEN unique_violation THEN
13588                                 suffix := suffix || ' ';
13589                                 CONTINUE;
13590                         END;
13591                         EXIT;
13592                 END LOOP;
13593         END LOOP;
13594
13595     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13596     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13597     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13598     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13599     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13600     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13601     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13602     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13603
13604     -- asset.*
13605     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13606     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13607     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13608     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13609     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13610     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13611
13612     -- serial.*
13613     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13614     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13615
13616     -- reporter.*
13617     -- It's not uncommon to define the reporter schema in a replica 
13618     -- DB only, so don't assume these tables exist in the write DB.
13619     BEGIN
13620         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13621     EXCEPTION WHEN undefined_table THEN
13622         -- do nothing
13623     END;
13624     BEGIN
13625         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13626     EXCEPTION WHEN undefined_table THEN
13627         -- do nothing
13628     END;
13629     BEGIN
13630         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13631     EXCEPTION WHEN undefined_table THEN
13632         -- do nothing
13633     END;
13634     BEGIN
13635                 -- transfer folders the same way we transfer buckets (see above)
13636                 FOR folder_row in
13637                         SELECT id, name
13638                         FROM   reporter.template_folder
13639                         WHERE  owner = src_usr
13640                 LOOP
13641                         suffix := ' (' || src_usr || ')';
13642                         LOOP
13643                                 BEGIN
13644                                         UPDATE  reporter.template_folder
13645                                         SET     owner = dest_usr, name = name || suffix
13646                                         WHERE   id = folder_row.id;
13647                                 EXCEPTION WHEN unique_violation THEN
13648                                         suffix := suffix || ' ';
13649                                         CONTINUE;
13650                                 END;
13651                                 EXIT;
13652                         END LOOP;
13653                 END LOOP;
13654     EXCEPTION WHEN undefined_table THEN
13655         -- do nothing
13656     END;
13657     BEGIN
13658                 -- transfer folders the same way we transfer buckets (see above)
13659                 FOR folder_row in
13660                         SELECT id, name
13661                         FROM   reporter.report_folder
13662                         WHERE  owner = src_usr
13663                 LOOP
13664                         suffix := ' (' || src_usr || ')';
13665                         LOOP
13666                                 BEGIN
13667                                         UPDATE  reporter.report_folder
13668                                         SET     owner = dest_usr, name = name || suffix
13669                                         WHERE   id = folder_row.id;
13670                                 EXCEPTION WHEN unique_violation THEN
13671                                         suffix := suffix || ' ';
13672                                         CONTINUE;
13673                                 END;
13674                                 EXIT;
13675                         END LOOP;
13676                 END LOOP;
13677     EXCEPTION WHEN undefined_table THEN
13678         -- do nothing
13679     END;
13680     BEGIN
13681                 -- transfer folders the same way we transfer buckets (see above)
13682                 FOR folder_row in
13683                         SELECT id, name
13684                         FROM   reporter.output_folder
13685                         WHERE  owner = src_usr
13686                 LOOP
13687                         suffix := ' (' || src_usr || ')';
13688                         LOOP
13689                                 BEGIN
13690                                         UPDATE  reporter.output_folder
13691                                         SET     owner = dest_usr, name = name || suffix
13692                                         WHERE   id = folder_row.id;
13693                                 EXCEPTION WHEN unique_violation THEN
13694                                         suffix := suffix || ' ';
13695                                         CONTINUE;
13696                                 END;
13697                                 EXIT;
13698                         END LOOP;
13699                 END LOOP;
13700     EXCEPTION WHEN undefined_table THEN
13701         -- do nothing
13702     END;
13703
13704     -- Finally, delete the source user
13705     DELETE FROM actor.usr WHERE id = src_usr;
13706
13707 END;
13708 $$ LANGUAGE plpgsql;
13709
13710 -- The "add" trigger functions should protect against existing NULLed values, just in case
13711 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13712 BEGIN
13713     IF NOT NEW.voided THEN
13714         UPDATE  money.materialized_billable_xact_summary
13715           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13716             last_billing_ts = NEW.billing_ts,
13717             last_billing_note = NEW.note,
13718             last_billing_type = NEW.billing_type,
13719             balance_owed = balance_owed + NEW.amount
13720           WHERE id = NEW.xact;
13721     END IF;
13722
13723     RETURN NEW;
13724 END;
13725 $$ LANGUAGE PLPGSQL;
13726
13727 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13728 BEGIN
13729     IF NOT NEW.voided THEN
13730         UPDATE  money.materialized_billable_xact_summary
13731           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13732             last_payment_ts = NEW.payment_ts,
13733             last_payment_note = NEW.note,
13734             last_payment_type = TG_ARGV[0],
13735             balance_owed = balance_owed - NEW.amount
13736           WHERE id = NEW.xact;
13737     END IF;
13738
13739     RETURN NEW;
13740 END;
13741 $$ LANGUAGE PLPGSQL;
13742
13743 -- Refresh the mat view with the corrected underlying view
13744 TRUNCATE money.materialized_billable_xact_summary;
13745 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13746
13747 -- Now redefine the view as a window onto the materialized view
13748 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13749     SELECT * FROM money.materialized_billable_xact_summary;
13750
13751 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13752     user_id    IN INTEGER,
13753     perm_code  IN TEXT
13754 )
13755 RETURNS SETOF INTEGER AS $$
13756 --
13757 -- Return a set of all the org units for which a given user has a given
13758 -- permission, granted directly (not through inheritance from a parent
13759 -- org unit).
13760 --
13761 -- The permissions apply to a minimum depth of the org unit hierarchy,
13762 -- for the org unit(s) to which the user is assigned.  (They also apply
13763 -- to the subordinates of those org units, but we don't report the
13764 -- subordinates here.)
13765 --
13766 -- For purposes of this function, the permission.usr_work_ou_map table
13767 -- defines which users belong to which org units.  I.e. we ignore the
13768 -- home_ou column of actor.usr.
13769 --
13770 -- The result set may contain duplicates, which should be eliminated
13771 -- by a DISTINCT clause.
13772 --
13773 DECLARE
13774     b_super       BOOLEAN;
13775     n_perm        INTEGER;
13776     n_min_depth   INTEGER;
13777     n_work_ou     INTEGER;
13778     n_curr_ou     INTEGER;
13779     n_depth       INTEGER;
13780     n_curr_depth  INTEGER;
13781 BEGIN
13782     --
13783     -- Check for superuser
13784     --
13785     SELECT INTO b_super
13786         super_user
13787     FROM
13788         actor.usr
13789     WHERE
13790         id = user_id;
13791     --
13792     IF NOT FOUND THEN
13793         return;             -- No user?  No permissions.
13794     ELSIF b_super THEN
13795         --
13796         -- Super user has all permissions everywhere
13797         --
13798         FOR n_work_ou IN
13799             SELECT
13800                 id
13801             FROM
13802                 actor.org_unit
13803             WHERE
13804                 parent_ou IS NULL
13805         LOOP
13806             RETURN NEXT n_work_ou;
13807         END LOOP;
13808         RETURN;
13809     END IF;
13810     --
13811     -- Translate the permission name
13812     -- to a numeric permission id
13813     --
13814     SELECT INTO n_perm
13815         id
13816     FROM
13817         permission.perm_list
13818     WHERE
13819         code = perm_code;
13820     --
13821     IF NOT FOUND THEN
13822         RETURN;               -- No such permission
13823     END IF;
13824     --
13825     -- Find the highest-level org unit (i.e. the minimum depth)
13826     -- to which the permission is applied for this user
13827     --
13828     -- This query is modified from the one in permission.usr_perms().
13829     --
13830     SELECT INTO n_min_depth
13831         min( depth )
13832     FROM    (
13833         SELECT depth
13834           FROM permission.usr_perm_map upm
13835          WHERE upm.usr = user_id
13836            AND (upm.perm = n_perm OR upm.perm = -1)
13837                     UNION
13838         SELECT  gpm.depth
13839           FROM  permission.grp_perm_map gpm
13840           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13841             AND gpm.grp IN (
13842                SELECT   (permission.grp_ancestors(
13843                     (SELECT profile FROM actor.usr WHERE id = user_id)
13844                 )).id
13845             )
13846                     UNION
13847         SELECT  p.depth
13848           FROM  permission.grp_perm_map p
13849           WHERE (p.perm = n_perm OR p.perm = -1)
13850             AND p.grp IN (
13851                 SELECT (permission.grp_ancestors(m.grp)).id
13852                 FROM   permission.usr_grp_map m
13853                 WHERE  m.usr = user_id
13854             )
13855     ) AS x;
13856     --
13857     IF NOT FOUND THEN
13858         RETURN;                -- No such permission for this user
13859     END IF;
13860     --
13861     -- Identify the org units to which the user is assigned.  Note that
13862     -- we pay no attention to the home_ou column in actor.usr.
13863     --
13864     FOR n_work_ou IN
13865         SELECT
13866             work_ou
13867         FROM
13868             permission.usr_work_ou_map
13869         WHERE
13870             usr = user_id
13871     LOOP            -- For each org unit to which the user is assigned
13872         --
13873         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13874         -- We take it on faith that this depth agrees with the actual hierarchy
13875         -- defined in actor.org_unit.
13876         --
13877         SELECT INTO n_depth
13878             type.depth
13879         FROM
13880             actor.org_unit_type type
13881                 INNER JOIN actor.org_unit ou
13882                     ON ( ou.ou_type = type.id )
13883         WHERE
13884             ou.id = n_work_ou;
13885         --
13886         IF NOT FOUND THEN
13887             CONTINUE;        -- Maybe raise exception?
13888         END IF;
13889         --
13890         -- Compare the depth of the work org unit to the
13891         -- minimum depth, and branch accordingly
13892         --
13893         IF n_depth = n_min_depth THEN
13894             --
13895             -- The org unit is at the right depth, so return it.
13896             --
13897             RETURN NEXT n_work_ou;
13898         ELSIF n_depth > n_min_depth THEN
13899             --
13900             -- Traverse the org unit tree toward the root,
13901             -- until you reach the minimum depth determined above
13902             --
13903             n_curr_depth := n_depth;
13904             n_curr_ou := n_work_ou;
13905             WHILE n_curr_depth > n_min_depth LOOP
13906                 SELECT INTO n_curr_ou
13907                     parent_ou
13908                 FROM
13909                     actor.org_unit
13910                 WHERE
13911                     id = n_curr_ou;
13912                 --
13913                 IF FOUND THEN
13914                     n_curr_depth := n_curr_depth - 1;
13915                 ELSE
13916                     --
13917                     -- This can happen only if the hierarchy defined in
13918                     -- actor.org_unit is corrupted, or out of sync with
13919                     -- the depths defined in actor.org_unit_type.
13920                     -- Maybe we should raise an exception here, instead
13921                     -- of silently ignoring the problem.
13922                     --
13923                     n_curr_ou = NULL;
13924                     EXIT;
13925                 END IF;
13926             END LOOP;
13927             --
13928             IF n_curr_ou IS NOT NULL THEN
13929                 RETURN NEXT n_curr_ou;
13930             END IF;
13931         ELSE
13932             --
13933             -- The permission applies only at a depth greater than the work org unit.
13934             -- Use connectby() to find all dependent org units at the specified depth.
13935             --
13936             FOR n_curr_ou IN
13937                 SELECT ou::INTEGER
13938                 FROM connectby(
13939                         'actor.org_unit',         -- table name
13940                         'id',                     -- key column
13941                         'parent_ou',              -- recursive foreign key
13942                         n_work_ou::TEXT,          -- id of starting point
13943                         (n_min_depth - n_depth)   -- max depth to search, relative
13944                     )                             --   to starting point
13945                     AS t(
13946                         ou text,            -- dependent org unit
13947                         parent_ou text,     -- (ignore)
13948                         level int           -- depth relative to starting point
13949                     )
13950                 WHERE
13951                     level = n_min_depth - n_depth
13952             LOOP
13953                 RETURN NEXT n_curr_ou;
13954             END LOOP;
13955         END IF;
13956         --
13957     END LOOP;
13958     --
13959     RETURN;
13960     --
13961 END;
13962 $$ LANGUAGE 'plpgsql';
13963
13964 ALTER TABLE acq.purchase_order
13965         ADD COLUMN cancel_reason INT
13966                 REFERENCES acq.cancel_reason( id )
13967             DEFERRABLE INITIALLY DEFERRED,
13968         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
13969
13970 -- Build the history table and lifecycle view
13971 -- for acq.purchase_order
13972
13973 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
13974
13975 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
13976
13977 ALTER TABLE acq.lineitem
13978         ADD COLUMN cancel_reason INT
13979                 REFERENCES acq.cancel_reason( id )
13980             DEFERRABLE INITIALLY DEFERRED,
13981         ADD COLUMN estimated_unit_price NUMERIC,
13982         ADD COLUMN claim_policy INT
13983                 REFERENCES acq.claim_policy
13984                 DEFERRABLE INITIALLY DEFERRED,
13985         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
13986
13987 -- Build the history table and lifecycle view
13988 -- for acq.lineitem
13989
13990 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
13991 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
13992
13993 ALTER TABLE acq.lineitem_detail
13994         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
13995                                             DEFERRABLE INITIALLY DEFERRED;
13996
13997 ALTER TABLE acq.lineitem_detail
13998         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
13999
14000 ALTER TABLE acq.lineitem_detail
14001         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14002                 ON DELETE CASCADE
14003                 DEFERRABLE INITIALLY DEFERRED;
14004
14005 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
14006
14007 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14008         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14009
14010 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14011         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14012
14013 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
14014
14015     use MARC::Record;
14016     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14017     use strict;
14018
14019     my $target_xml = shift;
14020     my $source_xml = shift;
14021     my $field_spec = shift;
14022     my $force_add = shift || 0;
14023
14024     my $target_r = MARC::Record->new_from_xml( $target_xml );
14025     my $source_r = MARC::Record->new_from_xml( $source_xml );
14026
14027     return $target_xml unless ($target_r && $source_r);
14028
14029     my @field_list = split(',', $field_spec);
14030
14031     my %fields;
14032     for my $f (@field_list) {
14033         $f =~ s/^\s*//; $f =~ s/\s*$//;
14034         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14035             my $field = $1;
14036             $field =~ s/\s+//;
14037             my $sf = $2;
14038             $sf =~ s/\s+//;
14039             my $match = $3;
14040             $match =~ s/^\s*//; $match =~ s/\s*$//;
14041             $fields{$field} = { sf => [ split('', $sf) ] };
14042             if ($match) {
14043                 my ($msf,$mre) = split('~', $match);
14044                 if (length($msf) > 0 and length($mre) > 0) {
14045                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14046                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14047                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14048                 }
14049             }
14050         }
14051     }
14052
14053     for my $f ( keys %fields) {
14054         if ( @{$fields{$f}{sf}} ) {
14055             for my $from_field ($source_r->field( $f )) {
14056                 my @tos = $target_r->field( $f );
14057                 if (!@tos) {
14058                     next if (exists($fields{$f}{match}) and !$force_add);
14059                     my @new_fields = map { $_->clone } $source_r->field( $f );
14060                     $target_r->insert_fields_ordered( @new_fields );
14061                 } else {
14062                     for my $to_field (@tos) {
14063                         if (exists($fields{$f}{match})) {
14064                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14065                         }
14066                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14067                         $to_field->add_subfields( @new_sf );
14068                     }
14069                 }
14070             }
14071         } else {
14072             my @new_fields = map { $_->clone } $source_r->field( $f );
14073             $target_r->insert_fields_ordered( @new_fields );
14074         }
14075     }
14076
14077     $target_xml = $target_r->as_xml_record;
14078     $target_xml =~ s/^<\?.+?\?>$//mo;
14079     $target_xml =~ s/\n//sgo;
14080     $target_xml =~ s/>\s+</></sgo;
14081
14082     return $target_xml;
14083
14084 $_$ LANGUAGE PLPERLU;
14085
14086 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14087     SELECT vandelay.add_field( $1, $2, $3, 0 );
14088 $_$ LANGUAGE SQL;
14089
14090 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14091
14092     use MARC::Record;
14093     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14094     use strict;
14095
14096     my $xml = shift;
14097     my $r = MARC::Record->new_from_xml( $xml );
14098
14099     return $xml unless ($r);
14100
14101     my $field_spec = shift;
14102     my @field_list = split(',', $field_spec);
14103
14104     my %fields;
14105     for my $f (@field_list) {
14106         $f =~ s/^\s*//; $f =~ s/\s*$//;
14107         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14108             my $field = $1;
14109             $field =~ s/\s+//;
14110             my $sf = $2;
14111             $sf =~ s/\s+//;
14112             my $match = $3;
14113             $match =~ s/^\s*//; $match =~ s/\s*$//;
14114             $fields{$field} = { sf => [ split('', $sf) ] };
14115             if ($match) {
14116                 my ($msf,$mre) = split('~', $match);
14117                 if (length($msf) > 0 and length($mre) > 0) {
14118                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14119                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14120                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14121                 }
14122             }
14123         }
14124     }
14125
14126     for my $f ( keys %fields) {
14127         for my $to_field ($r->field( $f )) {
14128             if (exists($fields{$f}{match})) {
14129                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14130             }
14131
14132             if ( @{$fields{$f}{sf}} ) {
14133                 $to_field->delete_subfield(code => $fields{$f}{sf});
14134             } else {
14135                 $r->delete_field( $to_field );
14136             }
14137         }
14138     }
14139
14140     $xml = $r->as_xml_record;
14141     $xml =~ s/^<\?.+?\?>$//mo;
14142     $xml =~ s/\n//sgo;
14143     $xml =~ s/>\s+</></sgo;
14144
14145     return $xml;
14146
14147 $_$ LANGUAGE PLPERLU;
14148
14149 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14150 DECLARE
14151     xml_output TEXT;
14152     parsed_target TEXT;
14153     curr_field TEXT;
14154 BEGIN
14155
14156     parsed_target := vandelay.strip_field( target_xml, ''); -- this dance normalizes the format of the xml for the IF below
14157
14158     FOR curr_field IN SELECT UNNEST( STRING_TO_ARRAY(field, ',') ) LOOP -- naive split, but it's the same we use in the perl
14159
14160         xml_output := vandelay.strip_field( parsed_target, curr_field);
14161
14162         IF xml_output <> parsed_target  AND curr_field ~ E'~' THEN
14163             -- we removed something, and there was a regexp restriction in the curr_field definition, so proceed
14164             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 1 );
14165         ELSIF curr_field !~ E'~' THEN
14166             -- No regexp restriction, add the curr_field
14167             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 0 );
14168         END IF;
14169
14170         parsed_target := xml_output; -- in prep for any following loop iterations
14171
14172     END LOOP;
14173
14174     RETURN xml_output;
14175 END;
14176 $_$ LANGUAGE PLPGSQL;
14177
14178 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14179     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14180 $_$ LANGUAGE SQL;
14181
14182 CREATE VIEW action.unfulfilled_hold_max_loop AS
14183         SELECT  hold,
14184                 max(count) AS max
14185         FROM    action.unfulfilled_hold_loops
14186         GROUP BY 1;
14187
14188 ALTER TABLE acq.lineitem_attr
14189         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14190
14191 ALTER TABLE acq.lineitem_attr
14192         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14193                 ON DELETE CASCADE
14194                 DEFERRABLE INITIALLY DEFERRED;
14195
14196 ALTER TABLE acq.po_note
14197         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14198
14199 CREATE TABLE vandelay.merge_profile (
14200     id              BIGSERIAL   PRIMARY KEY,
14201     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14202     name            TEXT        NOT NULL,
14203     add_spec        TEXT,
14204     replace_spec    TEXT,
14205     strip_spec      TEXT,
14206     preserve_spec   TEXT,
14207     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14208     CONSTRAINT add_replace_strip_or_preserve CHECK ((preserve_spec IS NOT NULL OR replace_spec IS NOT NULL) OR (preserve_spec IS NULL AND replace_spec IS NULL))
14209 );
14210
14211 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14212 DECLARE
14213     attr        RECORD;
14214     attr_def    RECORD;
14215     eg_rec      RECORD;
14216     id_value    TEXT;
14217     exact_id    BIGINT;
14218 BEGIN
14219
14220     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14221
14222     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14223
14224     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14225         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14226
14227         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14228             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14229             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14230             IF exact_id IS NOT NULL THEN
14231                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14232             END IF;
14233         END IF;
14234     END IF;
14235
14236     IF exact_id IS NULL THEN
14237         FOR attr IN SELECT a.* FROM vandelay.queued_bib_record_attr a JOIN vandelay.bib_attr_definition d ON (d.id = a.field) WHERE record = NEW.id AND d.ident IS TRUE LOOP
14238
14239             -- All numbers? check for an id match
14240             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14241                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14242                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14243                 END LOOP;
14244             END IF;
14245
14246             -- Looks like an ISBN? check for an isbn match
14247             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14248                 FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE LOWER('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP
14249                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14250                     IF FOUND THEN
14251                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14252                     END IF;
14253                 END LOOP;
14254
14255                 -- subcheck for isbn-as-tcn
14256                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14257                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14258                 END LOOP;
14259             END IF;
14260
14261             -- check for an OCLC tcn_value match
14262             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14263                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14264                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14265                 END LOOP;
14266             END IF;
14267
14268             -- check for a direct tcn_value match
14269             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14270                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14271             END LOOP;
14272
14273             -- check for a direct item barcode match
14274             FOR eg_rec IN
14275                     SELECT  DISTINCT b.*
14276                       FROM  biblio.record_entry b
14277                             JOIN asset.call_number cn ON (cn.record = b.id)
14278                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14279                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14280             LOOP
14281                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14282             END LOOP;
14283
14284         END LOOP;
14285     END IF;
14286
14287     RETURN NULL;
14288 END;
14289 $func$ LANGUAGE PLPGSQL;
14290
14291 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_xml TEXT, source_xml TEXT, add_rule TEXT, replace_preserve_rule TEXT, strip_rule TEXT ) RETURNS TEXT AS $_$
14292     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14293 $_$ LANGUAGE SQL;
14294
14295 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14296 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14297 DECLARE
14298     output              vandelay.compile_profile%ROWTYPE;
14299     profile             vandelay.merge_profile%ROWTYPE;
14300     profile_tmpl        TEXT;
14301     profile_tmpl_owner  TEXT;
14302     add_rule            TEXT := '';
14303     strip_rule          TEXT := '';
14304     replace_rule        TEXT := '';
14305     preserve_rule       TEXT := '';
14306
14307 BEGIN
14308
14309     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14310     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14311
14312     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14313         SELECT  p.* INTO profile
14314           FROM  vandelay.merge_profile p
14315                 JOIN actor.org_unit u ON (u.id = p.owner)
14316           WHERE p.name = profile_tmpl
14317                 AND u.shortname = profile_tmpl_owner;
14318
14319         IF profile.id IS NOT NULL THEN
14320             add_rule := COALESCE(profile.add_spec,'');
14321             strip_rule := COALESCE(profile.strip_spec,'');
14322             replace_rule := COALESCE(profile.replace_spec,'');
14323             preserve_rule := COALESCE(profile.preserve_spec,'');
14324         END IF;
14325     END IF;
14326
14327     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14328     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14329     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14330     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14331
14332     output.add_rule := BTRIM(add_rule,',');
14333     output.replace_rule := BTRIM(replace_rule,',');
14334     output.strip_rule := BTRIM(strip_rule,',');
14335     output.preserve_rule := BTRIM(preserve_rule,',');
14336
14337     RETURN output;
14338 END;
14339 $_$ LANGUAGE PLPGSQL;
14340
14341 -- Template-based marc munging functions
14342 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14343 DECLARE
14344     merge_profile   vandelay.merge_profile%ROWTYPE;
14345     dyn_profile     vandelay.compile_profile%ROWTYPE;
14346     editor_string   TEXT;
14347     editor_id       INT;
14348     source_marc     TEXT;
14349     target_marc     TEXT;
14350     eg_marc         TEXT;
14351     replace_rule    TEXT;
14352     match_count     INT;
14353 BEGIN
14354
14355     SELECT  b.marc INTO eg_marc
14356       FROM  biblio.record_entry b
14357       WHERE b.id = eg_id
14358       LIMIT 1;
14359
14360     IF eg_marc IS NULL OR v_marc IS NULL THEN
14361         -- RAISE NOTICE 'no marc for template or bib record';
14362         RETURN FALSE;
14363     END IF;
14364
14365     dyn_profile := vandelay.compile_profile( v_marc );
14366
14367     IF merge_profile_id IS NOT NULL THEN
14368         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14369         IF FOUND THEN
14370             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14371             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14372             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14373             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14374         END IF;
14375     END IF;
14376
14377     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14378         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14379         RETURN FALSE;
14380     END IF;
14381
14382     IF dyn_profile.replace_rule <> '' THEN
14383         source_marc = v_marc;
14384         target_marc = eg_marc;
14385         replace_rule = dyn_profile.replace_rule;
14386     ELSE
14387         source_marc = eg_marc;
14388         target_marc = v_marc;
14389         replace_rule = dyn_profile.preserve_rule;
14390     END IF;
14391
14392     UPDATE  biblio.record_entry
14393       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14394       WHERE id = eg_id;
14395
14396     IF NOT FOUND THEN
14397         -- RAISE NOTICE 'update of biblio.record_entry failed';
14398         RETURN FALSE;
14399     END IF;
14400
14401     RETURN TRUE;
14402
14403 END;
14404 $$ LANGUAGE PLPGSQL;
14405
14406 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14407     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14408 $$ LANGUAGE SQL;
14409
14410 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14411 DECLARE
14412     merge_profile   vandelay.merge_profile%ROWTYPE;
14413     dyn_profile     vandelay.compile_profile%ROWTYPE;
14414     editor_string   TEXT;
14415     editor_id       INT;
14416     source_marc     TEXT;
14417     target_marc     TEXT;
14418     eg_marc         TEXT;
14419     v_marc          TEXT;
14420     replace_rule    TEXT;
14421     match_count     INT;
14422 BEGIN
14423
14424     SELECT  q.marc INTO v_marc
14425       FROM  vandelay.queued_record q
14426             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14427       LIMIT 1;
14428
14429     IF v_marc IS NULL THEN
14430         -- RAISE NOTICE 'no marc for vandelay or bib record';
14431         RETURN FALSE;
14432     END IF;
14433
14434     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14435         UPDATE  vandelay.queued_bib_record
14436           SET   imported_as = eg_id,
14437                 import_time = NOW()
14438           WHERE id = import_id;
14439
14440         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14441
14442         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14443             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14444
14445             IF editor_id IS NULL THEN
14446                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14447             END IF;
14448
14449             IF editor_id IS NOT NULL THEN
14450                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14451             END IF;
14452         END IF;
14453
14454         RETURN TRUE;
14455     END IF;
14456
14457     -- RAISE NOTICE 'update of biblio.record_entry failed';
14458
14459     RETURN FALSE;
14460
14461 END;
14462 $$ LANGUAGE PLPGSQL;
14463
14464 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14465 DECLARE
14466     eg_id           BIGINT;
14467     match_count     INT;
14468     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14469 BEGIN
14470
14471     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14472
14473     IF FOUND THEN
14474         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14475         RETURN FALSE;
14476     END IF;
14477
14478     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14479
14480     IF match_count <> 1 THEN
14481         -- RAISE NOTICE 'not an exact match';
14482         RETURN FALSE;
14483     END IF;
14484
14485     SELECT  d.* INTO match_attr
14486       FROM  vandelay.bib_attr_definition d
14487             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14488             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14489       WHERE m.queued_record = import_id;
14490
14491     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14492         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14493         RETURN FALSE;
14494     END IF;
14495
14496     SELECT  m.eg_record INTO eg_id
14497       FROM  vandelay.bib_match m
14498       WHERE m.queued_record = import_id
14499       LIMIT 1;
14500
14501     IF eg_id IS NULL THEN
14502         RETURN FALSE;
14503     END IF;
14504
14505     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14506 END;
14507 $$ LANGUAGE PLPGSQL;
14508
14509 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14510 DECLARE
14511     queued_record   vandelay.queued_bib_record%ROWTYPE;
14512 BEGIN
14513
14514     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14515
14516         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14517             RETURN NEXT queued_record.id;
14518         END IF;
14519
14520     END LOOP;
14521
14522     RETURN;
14523
14524 END;
14525 $$ LANGUAGE PLPGSQL;
14526
14527 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14528     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14529 $$ LANGUAGE SQL;
14530
14531 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14532 DECLARE
14533     merge_profile   vandelay.merge_profile%ROWTYPE;
14534     dyn_profile     vandelay.compile_profile%ROWTYPE;
14535     source_marc     TEXT;
14536     target_marc     TEXT;
14537     eg_marc         TEXT;
14538     v_marc          TEXT;
14539     replace_rule    TEXT;
14540     match_count     INT;
14541 BEGIN
14542
14543     SELECT  b.marc INTO eg_marc
14544       FROM  authority.record_entry b
14545             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14546       LIMIT 1;
14547
14548     SELECT  q.marc INTO v_marc
14549       FROM  vandelay.queued_record q
14550             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14551       LIMIT 1;
14552
14553     IF eg_marc IS NULL OR v_marc IS NULL THEN
14554         -- RAISE NOTICE 'no marc for vandelay or authority record';
14555         RETURN FALSE;
14556     END IF;
14557
14558     dyn_profile := vandelay.compile_profile( v_marc );
14559
14560     IF merge_profile_id IS NOT NULL THEN
14561         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14562         IF FOUND THEN
14563             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14564             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14565             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14566             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14567         END IF;
14568     END IF;
14569
14570     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14571         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14572         RETURN FALSE;
14573     END IF;
14574
14575     IF dyn_profile.replace_rule <> '' THEN
14576         source_marc = v_marc;
14577         target_marc = eg_marc;
14578         replace_rule = dyn_profile.replace_rule;
14579     ELSE
14580         source_marc = eg_marc;
14581         target_marc = v_marc;
14582         replace_rule = dyn_profile.preserve_rule;
14583     END IF;
14584
14585     UPDATE  authority.record_entry
14586       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14587       WHERE id = eg_id;
14588
14589     IF FOUND THEN
14590         UPDATE  vandelay.queued_authority_record
14591           SET   imported_as = eg_id,
14592                 import_time = NOW()
14593           WHERE id = import_id;
14594         RETURN TRUE;
14595     END IF;
14596
14597     -- RAISE NOTICE 'update of authority.record_entry failed';
14598
14599     RETURN FALSE;
14600
14601 END;
14602 $$ LANGUAGE PLPGSQL;
14603
14604 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14605 DECLARE
14606     eg_id           BIGINT;
14607     match_count     INT;
14608 BEGIN
14609     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14610
14611     IF match_count <> 1 THEN
14612         -- RAISE NOTICE 'not an exact match';
14613         RETURN FALSE;
14614     END IF;
14615
14616     SELECT  m.eg_record INTO eg_id
14617       FROM  vandelay.authority_match m
14618       WHERE m.queued_record = import_id
14619       LIMIT 1;
14620
14621     IF eg_id IS NULL THEN
14622         RETURN FALSE;
14623     END IF;
14624
14625     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14626 END;
14627 $$ LANGUAGE PLPGSQL;
14628
14629 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14630 DECLARE
14631     queued_record   vandelay.queued_authority_record%ROWTYPE;
14632 BEGIN
14633
14634     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14635
14636         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14637             RETURN NEXT queued_record.id;
14638         END IF;
14639
14640     END LOOP;
14641
14642     RETURN;
14643
14644 END;
14645 $$ LANGUAGE PLPGSQL;
14646
14647 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14648     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14649 $$ LANGUAGE SQL;
14650
14651 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14652 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14653 DECLARE
14654     eg_tcn          TEXT;
14655     eg_tcn_source   TEXT;
14656     output          vandelay.tcn_data%ROWTYPE;
14657 BEGIN
14658
14659     -- 001/003
14660     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14661     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14662
14663         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14664         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14665             eg_tcn_source := 'System Local';
14666         END IF;
14667
14668         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14669
14670         IF NOT FOUND THEN
14671             output.used := FALSE;
14672         ELSE
14673             output.used := TRUE;
14674         END IF;
14675
14676         output.tcn := eg_tcn;
14677         output.tcn_source := eg_tcn_source;
14678         RETURN NEXT output;
14679
14680     END IF;
14681
14682     -- 901 ab
14683     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14684     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14685
14686         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14687         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14688             eg_tcn_source := 'System Local';
14689         END IF;
14690
14691         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14692
14693         IF NOT FOUND THEN
14694             output.used := FALSE;
14695         ELSE
14696             output.used := TRUE;
14697         END IF;
14698
14699         output.tcn := eg_tcn;
14700         output.tcn_source := eg_tcn_source;
14701         RETURN NEXT output;
14702
14703     END IF;
14704
14705     -- 039 ab
14706     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14707     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14708
14709         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14710         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14711             eg_tcn_source := 'System Local';
14712         END IF;
14713
14714         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14715
14716         IF NOT FOUND THEN
14717             output.used := FALSE;
14718         ELSE
14719             output.used := TRUE;
14720         END IF;
14721
14722         output.tcn := eg_tcn;
14723         output.tcn_source := eg_tcn_source;
14724         RETURN NEXT output;
14725
14726     END IF;
14727
14728     -- 020 a
14729     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14730     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14731
14732         eg_tcn_source := 'ISBN';
14733
14734         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14735
14736         IF NOT FOUND THEN
14737             output.used := FALSE;
14738         ELSE
14739             output.used := TRUE;
14740         END IF;
14741
14742         output.tcn := eg_tcn;
14743         output.tcn_source := eg_tcn_source;
14744         RETURN NEXT output;
14745
14746     END IF;
14747
14748     -- 022 a
14749     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14750     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14751
14752         eg_tcn_source := 'ISSN';
14753
14754         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14755
14756         IF NOT FOUND THEN
14757             output.used := FALSE;
14758         ELSE
14759             output.used := TRUE;
14760         END IF;
14761
14762         output.tcn := eg_tcn;
14763         output.tcn_source := eg_tcn_source;
14764         RETURN NEXT output;
14765
14766     END IF;
14767
14768     -- 010 a
14769     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14770     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14771
14772         eg_tcn_source := 'LCCN';
14773
14774         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14775
14776         IF NOT FOUND THEN
14777             output.used := FALSE;
14778         ELSE
14779             output.used := TRUE;
14780         END IF;
14781
14782         output.tcn := eg_tcn;
14783         output.tcn_source := eg_tcn_source;
14784         RETURN NEXT output;
14785
14786     END IF;
14787
14788     -- 035 a
14789     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14790     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14791
14792         eg_tcn_source := 'System Legacy';
14793
14794         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14795
14796         IF NOT FOUND THEN
14797             output.used := FALSE;
14798         ELSE
14799             output.used := TRUE;
14800         END IF;
14801
14802         output.tcn := eg_tcn;
14803         output.tcn_source := eg_tcn_source;
14804         RETURN NEXT output;
14805
14806     END IF;
14807
14808     RETURN;
14809 END;
14810 $_$ LANGUAGE PLPGSQL;
14811
14812 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14813
14814 CREATE OR REPLACE RULE protect_bib_rec_delete AS ON DELETE TO biblio.record_entry DO INSTEAD (UPDATE biblio.record_entry SET deleted = TRUE WHERE OLD.id = biblio.record_entry.id; DELETE FROM metabib.metarecord_source_map WHERE source = OLD.id);
14815
14816 -- remove invalid data ... there was no fkey before, boo
14817 DELETE FROM metabib.series_field_entry WHERE source NOT IN (SELECT id FROM biblio.record_entry);
14818 DELETE FROM metabib.series_field_entry WHERE field NOT IN (SELECT id FROM config.metabib_field);
14819
14820 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14821 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14822 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14823 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14824 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14825
14826 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14827 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14828 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14829 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14830
14831 ALTER TABLE metabib.series_field_entry
14832         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14833                 REFERENCES biblio.record_entry (id)
14834                 ON DELETE CASCADE
14835                 DEFERRABLE INITIALLY DEFERRED;
14836
14837 ALTER TABLE metabib.series_field_entry
14838         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14839                 REFERENCES config.metabib_field (id)
14840                 ON DELETE CASCADE
14841                 DEFERRABLE INITIALLY DEFERRED;
14842
14843 CREATE TABLE acq.claim_policy_action (
14844         id              SERIAL       PRIMARY KEY,
14845         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14846                                  ON DELETE CASCADE
14847                                      DEFERRABLE INITIALLY DEFERRED,
14848         action_interval INTERVAL     NOT NULL,
14849         action          INT          NOT NULL REFERENCES acq.claim_event_type
14850                                      DEFERRABLE INITIALLY DEFERRED,
14851         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14852 );
14853
14854 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14855 DECLARE
14856     value       TEXT; 
14857     atype       TEXT; 
14858     prov        INT;
14859     pos         INT;
14860     adef        RECORD;
14861     xpath_string    TEXT;
14862 BEGIN
14863     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14864     
14865         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14866       
14867         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14868             IF (atype = 'lineitem_provider_attr_definition') THEN
14869                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14870                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14871             END IF;
14872             
14873             IF (atype = 'lineitem_provider_attr_definition') THEN
14874                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14875             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14876                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14877             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14878                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14879             END IF;
14880       
14881             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14882
14883             IF (adef.code = 'title' OR adef.code = 'author') THEN
14884                 -- title and author should not be split
14885                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14886                 -- string-join in the xpath and remove this special case
14887                 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14888                 IF (value IS NOT NULL AND value <> '') THEN
14889                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14890                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14891                 END IF;
14892             ELSE
14893                 pos := 1;
14894
14895                 LOOP
14896                     SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14897       
14898                     IF (value IS NOT NULL AND value <> '') THEN
14899                         INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14900                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14901                     ELSE
14902                         EXIT;
14903                     END IF;
14904
14905                     pos := pos + 1;
14906                 END LOOP;
14907             END IF;
14908
14909         END IF;
14910
14911     END LOOP;
14912
14913     RETURN NULL;
14914 END;
14915 $function$ LANGUAGE PLPGSQL;
14916
14917 UPDATE config.metabib_field SET label = name;
14918 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14919
14920 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14921          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14922
14923 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14924
14925 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14926
14927 CREATE TABLE config.metabib_search_alias (
14928     alias       TEXT    PRIMARY KEY,
14929     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14930     field       INT     REFERENCES config.metabib_field (id)
14931 );
14932
14933 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14934 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14935 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14936 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14937 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14938 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14939 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14940 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14941
14942 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14943 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14944 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
14945 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
14946 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
14947 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
14948 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
14949 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
14950 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
14951 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
14952 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
14953 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
14954 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
14955
14956 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
14957 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
14958 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
14959 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
14960 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
14961 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
14962 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
14963 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
14964
14965 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
14966 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
14967 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
14968 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
14969 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
14970
14971 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
14972 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
14973 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
14974
14975 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
14976 UPDATE config.metabib_field SET xpath=$$//mods32:mods/mods32:name[@type='corporate' and mods32:role/mods32:roleTerm[text()='creator']]$$, facet_field=TRUE, facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 7;
14977 UPDATE config.metabib_field SET xpath=$$//mods32:mods/mods32:name[@type='personal' and mods32:role/mods32:roleTerm[text()='creator']]$$, facet_field=TRUE, facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 8;
14978 UPDATE config.metabib_field SET xpath=$$//mods32:mods/mods32:name[@type='conference' and mods32:role/mods32:roleTerm[text()='creator']]$$, facet_field=TRUE, facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 9;
14979 UPDATE config.metabib_field SET xpath=$$//mods32:mods/mods32:name[@type='personal' and not(mods32:role)]$$, facet_field=TRUE, facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 10;
14980
14981 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
14982 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
14983 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
14984 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
14985
14986 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
14987 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
14988 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
14989 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
14990 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
14991 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
14992 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
14993 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
14994 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
14995 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
14996 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
14997 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
14998 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
14999 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
15000
15001 CREATE TABLE asset.opac_visible_copies (
15002   id        BIGINT primary key, -- copy id
15003   record    BIGINT,
15004   circ_lib  INTEGER
15005 );
15006 COMMENT ON TABLE asset.opac_visible_copies IS $$
15007 Materialized view of copies that are visible in the OPAC, used by
15008 search.query_parser_fts() to speed up OPAC visibility checks on large
15009 databases.  Contents are maintained by a set of triggers.
15010 $$;
15011 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
15012
15013 CREATE OR REPLACE FUNCTION search.query_parser_fts (
15014
15015     param_search_ou INT,
15016     param_depth     INT,
15017     param_query     TEXT,
15018     param_statuses  INT[],
15019     param_locations INT[],
15020     param_offset    INT,
15021     param_check     INT,
15022     param_limit     INT,
15023     metarecord      BOOL,
15024     staff           BOOL
15025  
15026 ) RETURNS SETOF search.search_result AS $func$
15027 DECLARE
15028
15029     current_res         search.search_result%ROWTYPE;
15030     search_org_list     INT[];
15031
15032     check_limit         INT;
15033     core_limit          INT;
15034     core_offset         INT;
15035     tmp_int             INT;
15036
15037     core_result         RECORD;
15038     core_cursor         REFCURSOR;
15039     core_rel_query      TEXT;
15040
15041     total_count         INT := 0;
15042     check_count         INT := 0;
15043     deleted_count       INT := 0;
15044     visible_count       INT := 0;
15045     excluded_count      INT := 0;
15046
15047 BEGIN
15048
15049     check_limit := COALESCE( param_check, 1000 );
15050     core_limit  := COALESCE( param_limit, 25000 );
15051     core_offset := COALESCE( param_offset, 0 );
15052
15053     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15054
15055     IF param_search_ou > 0 THEN
15056         IF param_depth IS NOT NULL THEN
15057             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15058         ELSE
15059             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15060         END IF;
15061     ELSIF param_search_ou < 0 THEN
15062         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15063     ELSIF param_search_ou = 0 THEN
15064         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15065     END IF;
15066
15067     OPEN core_cursor FOR EXECUTE param_query;
15068
15069     LOOP
15070
15071         FETCH core_cursor INTO core_result;
15072         EXIT WHEN NOT FOUND;
15073         EXIT WHEN total_count >= core_limit;
15074
15075         total_count := total_count + 1;
15076
15077         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15078
15079         check_count := check_count + 1;
15080
15081         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15082         IF NOT FOUND THEN
15083             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15084             deleted_count := deleted_count + 1;
15085             CONTINUE;
15086         END IF;
15087
15088         PERFORM 1
15089           FROM  biblio.record_entry b
15090                 JOIN config.bib_source s ON (b.source = s.id)
15091           WHERE s.transcendant
15092                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15093
15094         IF FOUND THEN
15095             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15096             visible_count := visible_count + 1;
15097
15098             current_res.id = core_result.id;
15099             current_res.rel = core_result.rel;
15100
15101             tmp_int := 1;
15102             IF metarecord THEN
15103                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15104             END IF;
15105
15106             IF tmp_int = 1 THEN
15107                 current_res.record = core_result.records[1];
15108             ELSE
15109                 current_res.record = NULL;
15110             END IF;
15111
15112             RETURN NEXT current_res;
15113
15114             CONTINUE;
15115         END IF;
15116
15117         PERFORM 1
15118           FROM  asset.call_number cn
15119                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15120                 JOIN asset.uri uri ON (map.uri = uri.id)
15121           WHERE NOT cn.deleted
15122                 AND cn.label = '##URI##'
15123                 AND uri.active
15124                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15125                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15126                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15127           LIMIT 1;
15128
15129         IF FOUND THEN
15130             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15131             visible_count := visible_count + 1;
15132
15133             current_res.id = core_result.id;
15134             current_res.rel = core_result.rel;
15135
15136             tmp_int := 1;
15137             IF metarecord THEN
15138                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15139             END IF;
15140
15141             IF tmp_int = 1 THEN
15142                 current_res.record = core_result.records[1];
15143             ELSE
15144                 current_res.record = NULL;
15145             END IF;
15146
15147             RETURN NEXT current_res;
15148
15149             CONTINUE;
15150         END IF;
15151
15152         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15153
15154             PERFORM 1
15155               FROM  asset.call_number cn
15156                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15157               WHERE NOT cn.deleted
15158                     AND NOT cp.deleted
15159                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15160                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15161                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15162               LIMIT 1;
15163
15164             IF NOT FOUND THEN
15165                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15166                 excluded_count := excluded_count + 1;
15167                 CONTINUE;
15168             END IF;
15169
15170         END IF;
15171
15172         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15173
15174             PERFORM 1
15175               FROM  asset.call_number cn
15176                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15177               WHERE NOT cn.deleted
15178                     AND NOT cp.deleted
15179                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15180                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15181                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15182               LIMIT 1;
15183
15184             IF NOT FOUND THEN
15185                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15186                 excluded_count := excluded_count + 1;
15187                 CONTINUE;
15188             END IF;
15189
15190         END IF;
15191
15192         IF staff IS NULL OR NOT staff THEN
15193
15194             PERFORM 1
15195               FROM  asset.opac_visible_copies
15196               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15197                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15198               LIMIT 1;
15199
15200             IF NOT FOUND THEN
15201                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15202                 excluded_count := excluded_count + 1;
15203                 CONTINUE;
15204             END IF;
15205
15206         ELSE
15207
15208             PERFORM 1
15209               FROM  asset.call_number cn
15210                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15211                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15212               WHERE NOT cn.deleted
15213                     AND NOT cp.deleted
15214                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15215                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15216               LIMIT 1;
15217
15218             IF NOT FOUND THEN
15219
15220                 PERFORM 1
15221                   FROM  asset.call_number cn
15222                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15223                   LIMIT 1;
15224
15225                 IF FOUND THEN
15226                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15227                     excluded_count := excluded_count + 1;
15228                     CONTINUE;
15229                 END IF;
15230
15231             END IF;
15232
15233         END IF;
15234
15235         visible_count := visible_count + 1;
15236
15237         current_res.id = core_result.id;
15238         current_res.rel = core_result.rel;
15239
15240         tmp_int := 1;
15241         IF metarecord THEN
15242             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15243         END IF;
15244
15245         IF tmp_int = 1 THEN
15246             current_res.record = core_result.records[1];
15247         ELSE
15248             current_res.record = NULL;
15249         END IF;
15250
15251         RETURN NEXT current_res;
15252
15253         IF visible_count % 1000 = 0 THEN
15254             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15255         END IF;
15256
15257     END LOOP;
15258
15259     current_res.id = NULL;
15260     current_res.rel = NULL;
15261     current_res.record = NULL;
15262     current_res.total = total_count;
15263     current_res.checked = check_count;
15264     current_res.deleted = deleted_count;
15265     current_res.visible = visible_count;
15266     current_res.excluded = excluded_count;
15267
15268     CLOSE core_cursor;
15269
15270     RETURN NEXT current_res;
15271
15272 END;
15273 $func$ LANGUAGE PLPGSQL;
15274
15275 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15276 ALTER TABLE biblio.record_entry
15277          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15278          REFERENCES actor.org_unit (id)
15279          DEFERRABLE INITIALLY DEFERRED;
15280
15281 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15282
15283 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15284 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15285
15286 DROP VIEW auditor.biblio_record_entry_lifecycle;
15287
15288 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15289
15290 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15291         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15292 $$ LANGUAGE SQL STRICT IMMUTABLE;
15293
15294 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15295     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15296 $$ LANGUAGE SQL STRICT IMMUTABLE;
15297
15298 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15299     return lc(shift);
15300 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15301
15302 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15303     return uc(shift);
15304 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15305
15306 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15307     use Unicode::Normalize;
15308
15309     my $x = NFD(shift);
15310     $x =~ s/\pM+//go;
15311     return $x;
15312
15313 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15314
15315 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15316     use Unicode::Normalize;
15317
15318     my $x = NFC(shift);
15319     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15320     return $x;
15321
15322 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15323
15324 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15325 DECLARE
15326     setting RECORD;
15327     cur_org INT;
15328 BEGIN
15329     cur_org := org_id;
15330     LOOP
15331         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15332         IF FOUND THEN
15333             RETURN NEXT setting;
15334         END IF;
15335         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15336         EXIT WHEN cur_org IS NULL;
15337     END LOOP;
15338     RETURN;
15339 END;
15340 $$ LANGUAGE plpgsql STABLE;
15341
15342 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15343 DECLARE
15344     counter INT;
15345     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15346 BEGIN
15347
15348     SELECT  COUNT(*) INTO counter
15349       FROM  oils_xpath_table(
15350                 'id',
15351                 'marc',
15352                 'acq.lineitem',
15353                 '//*[@tag="' || tag || '"]',
15354                 'id=' || lineitem
15355             ) as t(i int,c text);
15356
15357     FOR i IN 1 .. counter LOOP
15358         FOR lida IN
15359             SELECT  *
15360               FROM  (   SELECT  id,i,t,v
15361                           FROM  oils_xpath_table(
15362                                     'id',
15363                                     'marc',
15364                                     'acq.lineitem',
15365                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15366                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15367                                     'id=' || lineitem
15368                                 ) as t(id int,t text,v text)
15369                     )x
15370         LOOP
15371             RETURN NEXT lida;
15372         END LOOP;
15373     END LOOP;
15374
15375     RETURN;
15376 END;
15377 $$ LANGUAGE PLPGSQL;
15378
15379 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15380 DECLARE
15381     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15382     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15383     result      config.i18n_core%ROWTYPE;
15384     fallback    TEXT;
15385     keyfield    TEXT := keyclass || '.' || keycol;
15386 BEGIN
15387
15388     -- Try the full locale
15389     SELECT  * INTO result
15390       FROM  config.i18n_core
15391       WHERE fq_field = keyfield
15392             AND identity_value = keyvalue
15393             AND translation = locale;
15394
15395     -- Try just the language
15396     IF NOT FOUND THEN
15397         SELECT  * INTO result
15398           FROM  config.i18n_core
15399           WHERE fq_field = keyfield
15400                 AND identity_value = keyvalue
15401                 AND translation = language;
15402     END IF;
15403
15404     -- Fall back to the string we passed in in the first place
15405     IF NOT FOUND THEN
15406     EXECUTE
15407             'SELECT ' ||
15408                 keycol ||
15409             ' FROM ' || keytable ||
15410             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15411                 INTO fallback;
15412         RETURN fallback;
15413     END IF;
15414
15415     RETURN result.string;
15416 END;
15417 $func$ LANGUAGE PLPGSQL STABLE;
15418
15419 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15420
15421 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15422
15423 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15424
15425 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15426     3, 1, 'delivered_but_lost',
15427     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15428
15429 CREATE TABLE config.global_flag (
15430     label   TEXT    NOT NULL
15431 ) INHERITS (config.internal_flag);
15432 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15433
15434 INSERT INTO config.global_flag (name, label, enabled)
15435     VALUES (
15436         'cat.bib.use_id_for_tcn',
15437         oils_i18n_gettext(
15438             'cat.bib.use_id_for_tcn',
15439             'Cat: Use Internal ID for TCN Value',
15440             'cgf', 
15441             'label'
15442         ),
15443         TRUE
15444     );
15445
15446 -- resolves performance issue noted by EG Indiana
15447
15448 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15449
15450 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15451
15452 -- 1.6 included stock indexes from 1 through 15, but didn't increase the
15453 -- sequence value to leave room for additional stock indexes in subsequent
15454 -- releases (hello!), so custom added indexes will conflict with these.
15455
15456 -- The following function changes the ID of an existing custom index
15457 -- (and any references to that index) to the target ID; if no target ID
15458 -- is supplied, then it adds 100 to the source ID. So this could break if a site
15459 -- has custom indexes at both 16 and 116, for example - but if that's the
15460 -- case anywhere, I'm throwing my hands up in surrender:
15461
15462 CREATE OR REPLACE FUNCTION config.modify_metabib_field(source INT, target INT) RETURNS INT AS $func$
15463 DECLARE
15464     f_class TEXT;
15465     check_id INT;
15466     target_id INT;
15467 BEGIN
15468     SELECT field_class INTO f_class FROM config.metabib_field WHERE id = source;
15469     IF NOT FOUND THEN
15470         RETURN 0;
15471     END IF;
15472     IF target IS NULL THEN
15473         target_id = source + 100;
15474     ELSE
15475         target_id = target;
15476     END IF;
15477     SELECT id FROM config.metabib_field INTO check_id WHERE id = target_id;
15478     IF FOUND THEN
15479         RAISE NOTICE 'Cannot bump config.metabib_field.id from % to %; the target ID already exists.', source, target_id;
15480         RETURN 0;
15481     END IF;
15482     UPDATE config.metabib_field SET id = target_id WHERE id = source;
15483     EXECUTE ' UPDATE metabib.' || f_class || '_field_entry SET field = ' || target_id || ' WHERE field = ' || source;
15484     UPDATE config.metabib_field_index_norm_map SET field = target_id WHERE field = source;
15485     UPDATE search.relevance_adjustment SET field = target_id WHERE field = source;
15486     RETURN 1;
15487 END;
15488 $func$ LANGUAGE PLPGSQL;
15489
15490 -- To avoid sequential scans against the large metabib.*_field_entry tables
15491 CREATE INDEX metabib_author_field_entry_field ON metabib.author_field_entry(field);
15492 CREATE INDEX metabib_keyword_field_entry_field ON metabib.keyword_field_entry(field);
15493 CREATE INDEX metabib_series_field_entry_field ON metabib.series_field_entry(field);
15494 CREATE INDEX metabib_subject_field_entry_field ON metabib.subject_field_entry(field);
15495 CREATE INDEX metabib_title_field_entry_field ON metabib.title_field_entry(field);
15496
15497 -- Now update those custom indexes
15498 SELECT config.modify_metabib_field(id, NULL)
15499     FROM config.metabib_field
15500     WHERE id > 15 AND id < 100 AND field_class || name <> 'subjectcomplete';
15501
15502 -- Ensure "subject|complete" is id = 16, if it exists
15503 SELECT config.modify_metabib_field(id, 16)
15504     FROM config.metabib_field
15505     WHERE id <> 16 AND field_class || name = 'subjectcomplete';
15506
15507 -- And bump the config.metabib_field sequence to a minimum of 100 to avoid problems in the future
15508 SELECT setval('config.metabib_field_id_seq', GREATEST(100, (SELECT MAX(id) + 1 FROM config.metabib_field)));
15509
15510 -- And drop the temporary indexes that we just created
15511 DROP INDEX metabib.metabib_author_field_entry_field;
15512 DROP INDEX metabib.metabib_keyword_field_entry_field;
15513 DROP INDEX metabib.metabib_series_field_entry_field;
15514 DROP INDEX metabib.metabib_subject_field_entry_field;
15515 DROP INDEX metabib.metabib_title_field_entry_field;
15516
15517 -- Now we can go ahead and insert the additional stock indexes
15518
15519 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath )
15520     SELECT  16, 'subject', 'complete', oils_i18n_gettext(16, 'All Subjects', 'cmf', 'label'), 'mods32', $$//mods32:mods/mods32:subject//text()$$
15521       WHERE NOT EXISTS (select id from config.metabib_field where field_class = 'subject' and name = 'complete'); -- in case it's already there
15522
15523 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15524     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15525 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15526     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15527 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15528     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15529 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15530     (20, 'identifier', 'upc', oils_i18n_gettext(20, 'UPC', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="024" and ind1="1"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15531 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15532     (21, 'identifier', 'ismn', oils_i18n_gettext(21, 'ISMN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="024" and ind1="2"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15533 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15534     (22, 'identifier', 'ean', oils_i18n_gettext(22, 'EAN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="024" and ind1="3"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15535 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15536     (23, 'identifier', 'isrc', oils_i18n_gettext(23, 'ISRC', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="024" and ind1="0"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15537 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15538     (24, 'identifier', 'sici', oils_i18n_gettext(24, 'SICI', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="024" and ind1="4"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15539 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15540     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15541
15542 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15543  
15544
15545 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15546
15547 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
15548 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15549 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15550 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15551 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15552 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15553 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15554
15555 CREATE TABLE metabib.identifier_field_entry (
15556         id              BIGSERIAL       PRIMARY KEY,
15557         source          BIGINT          NOT NULL,
15558         field           INT             NOT NULL,
15559         value           TEXT            NOT NULL,
15560         index_vector    tsvector        NOT NULL
15561 );
15562 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15563         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15564         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15565
15566 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15567 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15568     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15569 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15570
15571 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15572     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15573 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15574     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15575
15576 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15577     use Business::ISBN;
15578     use strict;
15579     use warnings;
15580
15581     # For each ISBN found in a single string containing a set of ISBNs:
15582     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15583     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15584
15585     my $input = shift;
15586     my $output = '';
15587
15588     foreach my $word (split(/\s/, $input)) {
15589         my $isbn = Business::ISBN->new($word);
15590
15591         # First check the checksum; if it is not valid, fix it and add the original
15592         # bad-checksum ISBN to the output
15593         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15594             $output .= $isbn->isbn() . " ";
15595             $isbn->fix_checksum();
15596         }
15597
15598         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15599         # and add the normalized original ISBN to the output
15600         if ($isbn && $isbn->is_valid()) {
15601             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15602             $output .= $isbn->isbn . " ";
15603
15604             # If we successfully converted the ISBN to its counterpart, add the
15605             # converted ISBN to the output as well
15606             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15607         }
15608     }
15609     return $output if $output;
15610
15611     # If there were no valid ISBNs, just return the raw input
15612     return $input;
15613 $func$ LANGUAGE PLPERLU;
15614
15615 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15616 /*
15617  * Copyright (C) 2010 Merrimack Valley Library Consortium
15618  * Jason Stephenson <jstephenson@mvlc.org>
15619  * Copyright (C) 2010 Laurentian University
15620  * Dan Scott <dscott@laurentian.ca>
15621  *
15622  * The translate_isbn1013 function takes an input ISBN and returns the
15623  * following in a single space-delimited string if the input ISBN is valid:
15624  *   - The normalized input ISBN (hyphens stripped)
15625  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15626  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15627  */
15628 $$;
15629
15630 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15631 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15632 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15633 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15634 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15635 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15636
15637 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15638         'ISBN 10/13 conversion',
15639         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15640         'translate_isbn1013',
15641         0
15642 );
15643
15644 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15645         'Replace',
15646         'Replace all occurences of first parameter in the string with the second parameter.',
15647         'replace',
15648         2
15649 );
15650
15651 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15652     SELECT  m.id, i.id, 1
15653       FROM  config.metabib_field m,
15654             config.index_normalizer i
15655       WHERE i.func IN ('first_word')
15656             AND m.id IN (18);
15657
15658 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15659     SELECT  m.id, i.id, 2
15660       FROM  config.metabib_field m,
15661             config.index_normalizer i
15662       WHERE i.func IN ('translate_isbn1013')
15663             AND m.id IN (18);
15664
15665 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15666     SELECT  m.id, i.id, $$['-','']$$
15667       FROM  config.metabib_field m,
15668             config.index_normalizer i
15669       WHERE i.func IN ('replace')
15670             AND m.id IN (19);
15671
15672 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15673     SELECT  m.id, i.id, $$[' ','']$$
15674       FROM  config.metabib_field m,
15675             config.index_normalizer i
15676       WHERE i.func IN ('replace')
15677             AND m.id IN (19);
15678
15679 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15680
15681 UPDATE  config.metabib_field_index_norm_map
15682   SET   params = REPLACE(params,E'\'','"')
15683   WHERE params IS NOT NULL AND params <> '';
15684
15685 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15686
15687 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15688
15689 ALTER TABLE config.circ_modifier
15690         ADD COLUMN avg_wait_time INTERVAL;
15691
15692 --CREATE TABLE actor.usr_password_reset (
15693 --  id SERIAL PRIMARY KEY,
15694 --  uuid TEXT NOT NULL, 
15695 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15696 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15697 --  has_been_reset BOOL NOT NULL DEFAULT false
15698 --);
15699 --COMMENT ON TABLE actor.usr_password_reset IS $$
15700 --/*
15701 -- * Copyright (C) 2010 Laurentian University
15702 -- * Dan Scott <dscott@laurentian.ca>
15703 -- *
15704 -- * Self-serve password reset requests
15705 -- *
15706 -- * ****
15707 -- *
15708 -- * This program is free software; you can redistribute it and/or
15709 -- * modify it under the terms of the GNU General Public License
15710 -- * as published by the Free Software Foundation; either version 2
15711 -- * of the License, or (at your option) any later version.
15712 -- *
15713 -- * This program is distributed in the hope that it will be useful,
15714 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15715 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15716 -- * GNU General Public License for more details.
15717 -- */
15718 --$$;
15719 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15720 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15721 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15722 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15723
15724 -- Use the identifier search class tsconfig
15725 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15726 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15727     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15728     FOR EACH ROW
15729     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15730
15731 INSERT INTO config.global_flag (name,label,enabled)
15732     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15733 INSERT INTO config.global_flag (name,label,enabled)
15734     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15735
15736 -- turn a JSON scalar into an SQL TEXT value
15737 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15738     use JSON::XS;                    
15739     my $json = shift();
15740     my $txt;
15741     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15742     return undef if ($@);
15743     return $txt
15744 $f$ LANGUAGE PLPERLU;
15745
15746 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15747 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15748 DECLARE
15749     c               action.circulation%ROWTYPE;
15750     view_age        INTERVAL;
15751     usr_view_age    actor.usr_setting%ROWTYPE;
15752     usr_view_start  actor.usr_setting%ROWTYPE;
15753 BEGIN
15754     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15755     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15756
15757     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15758         -- User opted in and supplied a retention age
15759         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15760             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15761         ELSE
15762             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15763         END IF;
15764     ELSIF usr_view_start.value IS NOT NULL THEN
15765         -- User opted in
15766         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15767     ELSE
15768         -- User did not opt in
15769         RETURN;
15770     END IF;
15771
15772     FOR c IN
15773         SELECT  *
15774           FROM  action.circulation
15775           WHERE usr = usr_id
15776                 AND parent_circ IS NULL
15777                 AND xact_start > NOW() - view_age
15778           ORDER BY xact_start
15779     LOOP
15780         RETURN NEXT c;
15781     END LOOP;
15782
15783     RETURN;
15784 END;
15785 $func$ LANGUAGE PLPGSQL;
15786
15787 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15788 DECLARE
15789     usr_keep_age    actor.usr_setting%ROWTYPE;
15790     usr_keep_start  actor.usr_setting%ROWTYPE;
15791     org_keep_age    INTERVAL;
15792     org_keep_count  INT;
15793
15794     keep_age        INTERVAL;
15795
15796     target_acp      RECORD;
15797     circ_chain_head action.circulation%ROWTYPE;
15798     circ_chain_tail action.circulation%ROWTYPE;
15799
15800     purge_position  INT;
15801     count_purged    INT;
15802 BEGIN
15803
15804     count_purged := 0;
15805
15806     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15807
15808     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15809     IF org_keep_count IS NULL THEN
15810         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15811     END IF;
15812
15813     -- First, find copies with more than keep_count non-renewal circs
15814     FOR target_acp IN
15815         SELECT  target_copy,
15816                 COUNT(*) AS total_real_circs
15817           FROM  action.circulation
15818           WHERE parent_circ IS NULL
15819                 AND xact_finish IS NOT NULL
15820           GROUP BY target_copy
15821           HAVING COUNT(*) > org_keep_count
15822     LOOP
15823         purge_position := 0;
15824         -- And, for those, select circs that are finished and older than keep_age
15825         FOR circ_chain_head IN
15826             SELECT  *
15827               FROM  action.circulation
15828               WHERE target_copy = target_acp.target_copy
15829                     AND parent_circ IS NULL
15830               ORDER BY xact_start
15831         LOOP
15832
15833             -- Stop once we've purged enough circs to hit org_keep_count
15834             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15835
15836             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15837             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15838
15839             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15840             usr_keep_age.value := NULL;
15841             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15842
15843             usr_keep_start.value := NULL;
15844             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15845
15846             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15847                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15848                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15849                 ELSE
15850                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15851                 END IF;
15852             ELSIF usr_keep_start.value IS NOT NULL THEN
15853                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15854             ELSE
15855                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15856             END IF;
15857
15858             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15859
15860             -- We've passed the purging tests, purge the circ chain starting at the end
15861             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15862             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15863                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15864                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15865             END LOOP;
15866
15867             count_purged := count_purged + 1;
15868             purge_position := purge_position + 1;
15869
15870         END LOOP;
15871     END LOOP;
15872 END;
15873 $func$ LANGUAGE PLPGSQL;
15874
15875 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15876 DECLARE
15877     h               action.hold_request%ROWTYPE;
15878     view_age        INTERVAL;
15879     view_count      INT;
15880     usr_view_count  actor.usr_setting%ROWTYPE;
15881     usr_view_age    actor.usr_setting%ROWTYPE;
15882     usr_view_start  actor.usr_setting%ROWTYPE;
15883 BEGIN
15884     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15885     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15886     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15887
15888     FOR h IN
15889         SELECT  *
15890           FROM  action.hold_request
15891           WHERE usr = usr_id
15892                 AND fulfillment_time IS NULL
15893                 AND cancel_time IS NULL
15894           ORDER BY request_time DESC
15895     LOOP
15896         RETURN NEXT h;
15897     END LOOP;
15898
15899     IF usr_view_start.value IS NULL THEN
15900         RETURN;
15901     END IF;
15902
15903     IF usr_view_age.value IS NOT NULL THEN
15904         -- User opted in and supplied a retention age
15905         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15906             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15907         ELSE
15908             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15909         END IF;
15910     ELSE
15911         -- User opted in
15912         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15913     END IF;
15914
15915     IF usr_view_count.value IS NOT NULL THEN
15916         view_count := oils_json_to_text(usr_view_count.value)::INT;
15917     ELSE
15918         view_count := 1000;
15919     END IF;
15920
15921     -- show some fulfilled/canceled holds
15922     FOR h IN
15923         SELECT  *
15924           FROM  action.hold_request
15925           WHERE usr = usr_id
15926                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15927                 AND request_time > NOW() - view_age
15928           ORDER BY request_time DESC
15929           LIMIT view_count
15930     LOOP
15931         RETURN NEXT h;
15932     END LOOP;
15933
15934     RETURN;
15935 END;
15936 $func$ LANGUAGE PLPGSQL;
15937
15938 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15939
15940 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15941
15942 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15943
15944 DROP TABLE IF EXISTS serial.issuance CASCADE;
15945
15946 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15947
15948 DROP TABLE IF EXISTS serial.subscription CASCADE;
15949
15950 CREATE TABLE asset.copy_template (
15951         id             SERIAL   PRIMARY KEY,
15952         owning_lib     INT      NOT NULL
15953                                 REFERENCES actor.org_unit (id)
15954                                 DEFERRABLE INITIALLY DEFERRED,
15955         creator        BIGINT   NOT NULL
15956                                 REFERENCES actor.usr (id)
15957                                 DEFERRABLE INITIALLY DEFERRED,
15958         editor         BIGINT   NOT NULL
15959                                 REFERENCES actor.usr (id)
15960                                 DEFERRABLE INITIALLY DEFERRED,
15961         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15962         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15963         name           TEXT     NOT NULL,
15964         -- columns above this point are attributes of the template itself
15965         -- columns after this point are attributes of the copy this template modifies/creates
15966         circ_lib       INT      REFERENCES actor.org_unit (id)
15967                                 DEFERRABLE INITIALLY DEFERRED,
15968         status         INT      REFERENCES config.copy_status (id)
15969                                 DEFERRABLE INITIALLY DEFERRED,
15970         location       INT      REFERENCES asset.copy_location (id)
15971                                 DEFERRABLE INITIALLY DEFERRED,
15972         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15973                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15974         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15975                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15976         age_protect    INT,
15977         circulate      BOOL,
15978         deposit        BOOL,
15979         ref            BOOL,
15980         holdable       BOOL,
15981         deposit_amount NUMERIC(6,2),
15982         price          NUMERIC(8,2),
15983         circ_modifier  TEXT,
15984         circ_as_type   TEXT,
15985         alert_message  TEXT,
15986         opac_visible   BOOL,
15987         floating       BOOL,
15988         mint_condition BOOL
15989 );
15990
15991 CREATE TABLE serial.subscription (
15992         id                     SERIAL       PRIMARY KEY,
15993         owning_lib             INT          NOT NULL DEFAULT 1
15994                                             REFERENCES actor.org_unit (id)
15995                                             ON DELETE SET NULL
15996                                             DEFERRABLE INITIALLY DEFERRED,
15997         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15998         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15999         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
16000                                             ON DELETE SET NULL
16001                                             DEFERRABLE INITIALLY DEFERRED,
16002         expected_date_offset   INTERVAL
16003         -- acquisitions/business-side tables link to here
16004 );
16005 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
16006 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
16007
16008 --at least one distribution per org_unit holding issues
16009 CREATE TABLE serial.distribution (
16010         id                    SERIAL  PRIMARY KEY,
16011         record_entry          BIGINT  REFERENCES serial.record_entry (id)
16012                                       ON DELETE SET NULL
16013                                       DEFERRABLE INITIALLY DEFERRED,
16014         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
16015                                           summary_method IS NULL
16016                                           OR summary_method IN ( 'add_to_sre',
16017                                           'merge_with_sre', 'use_sre_only',
16018                                           'use_sdist_only')),
16019         subscription          INT     NOT NULL
16020                                       REFERENCES serial.subscription (id)
16021                                                                   ON DELETE CASCADE
16022                                                                   DEFERRABLE INITIALLY DEFERRED,
16023         holding_lib           INT     NOT NULL
16024                                       REFERENCES actor.org_unit (id)
16025                                                                   DEFERRABLE INITIALLY DEFERRED,
16026         label                 TEXT    NOT NULL,
16027         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
16028                                       DEFERRABLE INITIALLY DEFERRED,
16029         receive_unit_template INT     REFERENCES asset.copy_template (id)
16030                                       DEFERRABLE INITIALLY DEFERRED,
16031         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
16032                                       DEFERRABLE INITIALLY DEFERRED,
16033         bind_unit_template    INT     REFERENCES asset.copy_template (id)
16034                                       DEFERRABLE INITIALLY DEFERRED,
16035         unit_label_prefix     TEXT,
16036         unit_label_suffix     TEXT
16037 );
16038 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
16039 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
16040
16041 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
16042
16043 CREATE TABLE serial.stream (
16044         id              SERIAL  PRIMARY KEY,
16045         distribution    INT     NOT NULL
16046                                 REFERENCES serial.distribution (id)
16047                                 ON DELETE CASCADE
16048                                 DEFERRABLE INITIALLY DEFERRED,
16049         routing_label   TEXT
16050 );
16051 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
16052
16053 CREATE UNIQUE INDEX label_once_per_dist
16054         ON serial.stream (distribution, routing_label)
16055         WHERE routing_label IS NOT NULL;
16056
16057 CREATE TABLE serial.routing_list_user (
16058         id             SERIAL       PRIMARY KEY,
16059         stream         INT          NOT NULL
16060                                     REFERENCES serial.stream
16061                                     ON DELETE CASCADE
16062                                     DEFERRABLE INITIALLY DEFERRED,
16063         pos            INT          NOT NULL DEFAULT 1,
16064         reader         INT          REFERENCES actor.usr
16065                                     ON DELETE CASCADE
16066                                     DEFERRABLE INITIALLY DEFERRED,
16067         department     TEXT,
16068         note           TEXT,
16069         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
16070         CONSTRAINT reader_or_dept CHECK
16071         (
16072             -- Recipient is a person or a department, but not both
16073                 (reader IS NOT NULL AND department IS NULL) OR
16074                 (reader IS NULL AND department IS NOT NULL)
16075         )
16076 );
16077 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
16078 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
16079
16080 CREATE TABLE serial.caption_and_pattern (
16081         id           SERIAL       PRIMARY KEY,
16082         subscription INT          NOT NULL REFERENCES serial.subscription (id)
16083                                   ON DELETE CASCADE
16084                                   DEFERRABLE INITIALLY DEFERRED,
16085         type         TEXT         NOT NULL
16086                                   CONSTRAINT cap_type CHECK ( type in
16087                                   ( 'basic', 'supplement', 'index' )),
16088         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
16089         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
16090         end_date     TIMESTAMP WITH TIME ZONE,
16091         active       BOOL         NOT NULL DEFAULT FALSE,
16092         pattern_code TEXT         NOT NULL,       -- must contain JSON
16093         enum_1       TEXT,
16094         enum_2       TEXT,
16095         enum_3       TEXT,
16096         enum_4       TEXT,
16097         enum_5       TEXT,
16098         enum_6       TEXT,
16099         chron_1      TEXT,
16100         chron_2      TEXT,
16101         chron_3      TEXT,
16102         chron_4      TEXT,
16103         chron_5      TEXT
16104 );
16105 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
16106
16107 CREATE TABLE serial.issuance (
16108         id              SERIAL    PRIMARY KEY,
16109         creator         INT       NOT NULL
16110                                   REFERENCES actor.usr (id)
16111                                                           DEFERRABLE INITIALLY DEFERRED,
16112         editor          INT       NOT NULL
16113                                   REFERENCES actor.usr (id)
16114                                   DEFERRABLE INITIALLY DEFERRED,
16115         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16116         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16117         subscription    INT       NOT NULL
16118                                   REFERENCES serial.subscription (id)
16119                                   ON DELETE CASCADE
16120                                   DEFERRABLE INITIALLY DEFERRED,
16121         label           TEXT,
16122         date_published  TIMESTAMP WITH TIME ZONE,
16123         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16124                               DEFERRABLE INITIALLY DEFERRED,
16125         holding_code    TEXT,
16126         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16127                                   (
16128                                       holding_type IS NULL
16129                                       OR holding_type IN ('basic','supplement','index')
16130                                   ),
16131         holding_link_id INT
16132         -- TODO: add columns for separate enumeration/chronology values
16133 );
16134 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16135 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16136 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16137
16138 CREATE TABLE serial.unit (
16139         label           TEXT,
16140         label_sort_key  TEXT,
16141         contents        TEXT    NOT NULL
16142 ) INHERITS (asset.copy);
16143 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16144 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16145 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16146 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16147 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16148
16149 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16150
16151 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16152
16153 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16154
16155 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16156
16157 CREATE TABLE serial.item (
16158         id              SERIAL  PRIMARY KEY,
16159         creator         INT     NOT NULL
16160                                 REFERENCES actor.usr (id)
16161                                 DEFERRABLE INITIALLY DEFERRED,
16162         editor          INT     NOT NULL
16163                                 REFERENCES actor.usr (id)
16164                                 DEFERRABLE INITIALLY DEFERRED,
16165         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16166         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16167         issuance        INT     NOT NULL
16168                                 REFERENCES serial.issuance (id)
16169                                 ON DELETE CASCADE
16170                                 DEFERRABLE INITIALLY DEFERRED,
16171         stream          INT     NOT NULL
16172                                 REFERENCES serial.stream (id)
16173                                 ON DELETE CASCADE
16174                                 DEFERRABLE INITIALLY DEFERRED,
16175         unit            INT     REFERENCES serial.unit (id)
16176                                 ON DELETE SET NULL
16177                                 DEFERRABLE INITIALLY DEFERRED,
16178         uri             INT     REFERENCES asset.uri (id)
16179                                 ON DELETE SET NULL
16180                                 DEFERRABLE INITIALLY DEFERRED,
16181         date_expected   TIMESTAMP WITH TIME ZONE,
16182         date_received   TIMESTAMP WITH TIME ZONE,
16183         status          TEXT    CONSTRAINT valid_status CHECK (
16184                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16185                                'Expected', 'Not Held', 'Not Published', 'Received'))
16186                             DEFAULT 'Expected',
16187         shadowed        BOOL    NOT NULL DEFAULT FALSE
16188 );
16189 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16190 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16191 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16192 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16193 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16194 CREATE INDEX serial_item_status_idx ON serial.item (status);
16195
16196 CREATE TABLE serial.item_note (
16197         id          SERIAL  PRIMARY KEY,
16198         item        INT     NOT NULL
16199                             REFERENCES serial.item (id)
16200                             ON DELETE CASCADE
16201                             DEFERRABLE INITIALLY DEFERRED,
16202         creator     INT     NOT NULL
16203                             REFERENCES actor.usr (id)
16204                             DEFERRABLE INITIALLY DEFERRED,
16205         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16206         pub         BOOL    NOT NULL    DEFAULT FALSE,
16207         title       TEXT    NOT NULL,
16208         value       TEXT    NOT NULL
16209 );
16210 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16211
16212 CREATE TABLE serial.basic_summary (
16213         id                  SERIAL  PRIMARY KEY,
16214         distribution        INT     NOT NULL
16215                                     REFERENCES serial.distribution (id)
16216                                     ON DELETE CASCADE
16217                                     DEFERRABLE INITIALLY DEFERRED,
16218         generated_coverage  TEXT    NOT NULL,
16219         textual_holdings    TEXT,
16220         show_generated      BOOL    NOT NULL DEFAULT TRUE
16221 );
16222 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16223
16224 CREATE TABLE serial.supplement_summary (
16225         id                  SERIAL  PRIMARY KEY,
16226         distribution        INT     NOT NULL
16227                                     REFERENCES serial.distribution (id)
16228                                     ON DELETE CASCADE
16229                                     DEFERRABLE INITIALLY DEFERRED,
16230         generated_coverage  TEXT    NOT NULL,
16231         textual_holdings    TEXT,
16232         show_generated      BOOL    NOT NULL DEFAULT TRUE
16233 );
16234 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16235
16236 CREATE TABLE serial.index_summary (
16237         id                  SERIAL  PRIMARY KEY,
16238         distribution        INT     NOT NULL
16239                                     REFERENCES serial.distribution (id)
16240                                     ON DELETE CASCADE
16241                                     DEFERRABLE INITIALLY DEFERRED,
16242         generated_coverage  TEXT    NOT NULL,
16243         textual_holdings    TEXT,
16244         show_generated      BOOL    NOT NULL DEFAULT TRUE
16245 );
16246 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16247
16248 -- DELETE FROM action_trigger.environment WHERE event_def IN (29,30); DELETE FROM action_trigger.event where event_def IN (29,30); DELETE FROM action_trigger.event_definition WHERE id IN (29,30); DELETE FROM action_trigger.hook WHERE key IN ('money.format.payment_receipt.email','money.format.payment_receipt.print'); DELETE FROM config.upgrade_log WHERE version = '0289'; -- from testing, this sql will remove these events, etc.
16249
16250 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16251 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16252
16253 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16254 CREATE UNIQUE INDEX asset_call_number_label_once_per_lib ON asset.call_number (record, owning_lib, label) WHERE deleted = FALSE OR deleted IS FALSE;
16255
16256 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16257 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16258
16259 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16260 RETURNS INTEGER AS $$
16261 BEGIN
16262         RETURN EXTRACT( EPOCH FROM interval_val );
16263 END;
16264 $$ LANGUAGE plpgsql;
16265
16266 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16267 RETURNS INTEGER AS $$
16268 BEGIN
16269         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16270 END;
16271 $$ LANGUAGE plpgsql;
16272
16273 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16274     'temp',
16275     oils_i18n_gettext(
16276         'temp',
16277         'Temporary bucket which gets deleted after use.',
16278         'cbrebt',
16279         'label'
16280     )
16281 );
16282
16283 -- DELETE FROM action_trigger.environment WHERE event_def IN (31,32); DELETE FROM action_trigger.event where event_def IN (31,32); DELETE FROM action_trigger.event_definition WHERE id IN (31,32); DELETE FROM action_trigger.hook WHERE key IN ('biblio.format.record_entry.email','biblio.format.record_entry.print'); DELETE FROM action_trigger.cleanup WHERE module = 'DeleteTempBiblioBucket'; DELETE FROM container.biblio_record_entry_bucket_item WHERE bucket IN (SELECT id FROM container.biblio_record_entry_bucket WHERE btype = 'temp'); DELETE FROM container.biblio_record_entry_bucket WHERE btype = 'temp'; DELETE FROM container.biblio_record_entry_bucket_type WHERE code = 'temp'; DELETE FROM config.upgrade_log WHERE version = '0294'; -- from testing, this sql will remove these events, etc.
16284
16285 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16286 BEGIN
16287
16288     IF xml_is_well_formed(NEW.marc) THEN
16289         RETURN NEW;
16290     ELSE
16291         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16292     END IF;
16293     
16294 END;
16295 $func$ LANGUAGE PLPGSQL;
16296
16297 CREATE TRIGGER a_marcxml_is_well_formed BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.check_marcxml_well_formed();
16298
16299 CREATE TRIGGER a_marcxml_is_well_formed BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.check_marcxml_well_formed();
16300
16301 ALTER TABLE serial.record_entry
16302         ALTER COLUMN marc DROP NOT NULL;
16303
16304 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16305 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16306 <xsl:stylesheet
16307     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16308     xmlns:marc="http://www.loc.gov/MARC21/slim"
16309     version="1.0">
16310 <!--
16311 Copyright (C) 2010  Equinox Software, Inc.
16312 Galen Charlton <gmc@esilibrary.cOM.
16313
16314 This program is free software; you can redistribute it and/or
16315 modify it under the terms of the GNU General Public License
16316 as published by the Free Software Foundation; either version 2
16317 of the License, or (at your option) any later version.
16318
16319 This program is distributed in the hope that it will be useful,
16320 but WITHOUT ANY WARRANTY; without even the implied warranty of
16321 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16322 GNU General Public License for more details.
16323
16324 marc21_expand_880.xsl - stylesheet used during indexing to
16325                         map alternative graphical representations
16326                         of MARC fields stored in 880 fields
16327                         to the corresponding tag name and value.
16328
16329 For example, if a MARC record for a Chinese book has
16330
16331 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16332 880.00 $6 245-01/$1 $a八十三年短篇小說選
16333
16334 this stylesheet will transform it to the equivalent of
16335
16336 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16337 245.00 $6 245-01/$1 $a八十三年短篇小說選
16338
16339 -->
16340     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16341
16342     <xsl:template match="@*|node()">
16343         <xsl:copy>
16344             <xsl:apply-templates select="@*|node()"/>
16345         </xsl:copy>
16346     </xsl:template>
16347
16348     <xsl:template match="//marc:datafield[@tag='880']">
16349         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16350             <marc:datafield>
16351                 <xsl:attribute name="tag">
16352                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16353                 </xsl:attribute>
16354                 <xsl:attribute name="ind1">
16355                     <xsl:value-of select="@ind1" />
16356                 </xsl:attribute>
16357                 <xsl:attribute name="ind2">
16358                     <xsl:value-of select="@ind2" />
16359                 </xsl:attribute>
16360                 <xsl:apply-templates />
16361             </marc:datafield>
16362         </xsl:if>
16363     </xsl:template>
16364     
16365 </xsl:stylesheet>$$);
16366
16367 -- fix broken prefix and namespace URI for the
16368 -- mods32 transform found in some databases
16369 -- that started out at version 1.2 or earlier
16370 UPDATE config.xml_transform
16371 SET namespace_uri = 'http://www.loc.gov/mods/v3'
16372 WHERE name = 'mods32'
16373 AND namespace_uri = 'http://www.loc.gov/mods/'
16374 AND xslt LIKE '%xmlns="http://www.loc.gov/mods/v3"%';
16375
16376 UPDATE config.xml_transform
16377 SET prefix = 'mods32'
16378 WHERE name = 'mods32'
16379 AND prefix = 'mods'
16380 AND EXISTS (SELECT xpath FROM config.metabib_field WHERE xpath ~ 'mods32:');
16381
16382 -- Splitting the ingest trigger up into little bits
16383
16384 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16385     flag INTEGER PRIMARY KEY
16386 ) ON COMMIT DROP;
16387 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16388
16389 -- cause failure if either of the tables we want to drop have rows
16390 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16391 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16392
16393 DROP TABLE IF EXISTS asset.copy_transparency_map;
16394 DROP TABLE IF EXISTS asset.copy_transparency;
16395
16396 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16397
16398 -- We won't necessarily use all of these, but they are here for completeness.
16399 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16400 -- Values are the EDI code value + 1000
16401
16402 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16403 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16404 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16405 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16406 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16407 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16408 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16409 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16410 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16411 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16412 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16413 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16414 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16415 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16416 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16417 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16418 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16419 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16420 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16421 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16422 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16423 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16424 ('t',( 22+1000), 1, 'Call-off delivery', 'A request for delivery of a particular quantity of goods to be delivered on a particular date (or within a particular period).'),
16425 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16426 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16427 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16428 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16429 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16430 ('t',( 28+1000), 1, 'Verified documents for debit',    'Upon receipt and verification of documents we shall authorize you to debit our account with you when due.'),
16431 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16432 ('t',( 30+1000), 1, 'Authenticated advice for authorization', 'On receipt of your authenticated advice we shall authorize you to debit our account with you when due.'),
16433 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16434 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16435 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16436 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16437 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16438 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16439 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16440 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16441 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16442 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16443 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16444 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16445 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16446 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16447 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16448 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16449 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16450 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16451 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16452 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16453 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16454 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16455 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16456 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16457 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16458 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16459 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16460 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16461 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16462 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16463 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16464 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16465 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16466 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16467 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16468 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16469 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16470 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16471 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16472 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16473 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16474 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16475 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16476 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16477 ('t',( 75+1000), 1, 'Processed by primary, forwarded to additional payer(s)',   'This request has been processed by the primary payer and sent to additional payer(s).'),
16478 ('t',( 76+1000), 1, 'Processed by secondary, forwarded to additional payer(s)', 'This request has been processed by the secondary payer and sent to additional payer(s).'),
16479 ('t',( 77+1000), 1, 'Processed by tertiary, forwarded to additional payer(s)',  'This request has been processed by the tertiary payer and sent to additional payer(s).'),
16480 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16481 ('t',( 79+1000), 1, 'Not our claim, forwarded to another payer(s)', 'A request does not belong to this payer but has been forwarded to another payer(s).'),
16482 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16483 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16484 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16485 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16486 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16487 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16488 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16489 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16490 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16491 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16492 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16493 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16494 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16495 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16496 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16497 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16498 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16499 ('t',( 97+1000), 1, 'Single credit item of a group', 'Notification that the credit item is a single credit item of a group of credit items.'),
16500 ('t',( 98+1000), 1, 'Single debit item of a group',  'Notification that the debit item is a single debit item of a group of debit items.'),
16501 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16502 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16503 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16504 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16505 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16506 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16507 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16508 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16509 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16510 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16511 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16512 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16513 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16514 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16515 ('t',(113+1000), 1, 'Resale and claim', 'The identified items have been sold by the distributor to the end customer, and compensation for the loss of inventory value is claimed.'),
16516 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16517 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16518
16519 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16520     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16521  
16522 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16523  
16524 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16525         'Remove Parenthesized Substring',
16526         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16527         'remove_paren_substring',
16528         0
16529 );
16530
16531 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16532         'Trim Surrounding Space',
16533         'Trim leading and trailing spaces from extracted text.',
16534         'btrim',
16535         0
16536 );
16537
16538 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16539     SELECT  m.id,
16540             i.id,
16541             -2
16542       FROM  config.metabib_field m,
16543             config.index_normalizer i
16544       WHERE i.func IN ('remove_paren_substring')
16545             AND m.id IN (26);
16546
16547 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16548     SELECT  m.id,
16549             i.id,
16550             -1
16551       FROM  config.metabib_field m,
16552             config.index_normalizer i
16553       WHERE i.func IN ('btrim')
16554             AND m.id IN (26);
16555
16556 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16557 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16558 DECLARE
16559     dyn_profile     vandelay.compile_profile%ROWTYPE;
16560     replace_rule    TEXT;
16561     tmp_marc        TEXT;
16562     trgt_marc        TEXT;
16563     tmpl_marc        TEXT;
16564     match_count     INT;
16565 BEGIN
16566
16567     IF target_marc IS NULL OR template_marc IS NULL THEN
16568         -- RAISE NOTICE 'no marc for target or template record';
16569         RETURN NULL;
16570     END IF;
16571
16572     dyn_profile := vandelay.compile_profile( template_marc );
16573
16574     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16575         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16576         RETURN NULL;
16577     END IF;
16578
16579     IF dyn_profile.replace_rule <> '' THEN
16580         trgt_marc = target_marc;
16581         tmpl_marc = template_marc;
16582         replace_rule = dyn_profile.replace_rule;
16583     ELSE
16584         tmp_marc = target_marc;
16585         trgt_marc = template_marc;
16586         tmpl_marc = tmp_marc;
16587         replace_rule = dyn_profile.preserve_rule;
16588     END IF;
16589
16590     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16591
16592 END;
16593 $$ LANGUAGE PLPGSQL;
16594
16595 -- Function to generate an ephemeral overlay template from an authority record
16596 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16597
16598     use MARC::Record;
16599     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16600
16601     my $xml = shift;
16602     my $r = MARC::Record->new_from_xml( $xml );
16603
16604     return undef unless ($r);
16605
16606     my $id = shift() || $r->subfield( '901' => 'c' );
16607     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16608     return undef unless ($id); # We need an ID!
16609
16610     my $tmpl = MARC::Record->new();
16611     $tmpl->encoding( 'UTF-8' );
16612
16613     my @rule_fields;
16614     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16615
16616         my $tag = $field->tag;
16617         my $i1 = $field->indicator(1);
16618         my $i2 = $field->indicator(2);
16619         my $sf = join '', map { $_->[0] } $field->subfields;
16620         my @data = map { @$_ } $field->subfields;
16621
16622         my @replace_them;
16623
16624         # Map the authority field to bib fields it can control.
16625         if ($tag >= 100 and $tag <= 111) {       # names
16626             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16627         } elsif ($tag eq '130') {                # uniform title
16628             @replace_them = qw/130 240 440 730 830/;
16629         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16630             @replace_them = ($tag + 500);
16631         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16632             @replace_them = qw/100 400 600 700 800 110 410 610 710 810 111 411 611 711 811 130 240 440 730 830 650 651 655/;
16633         } else {
16634             next;
16635         }
16636
16637         # Dummy up the bib-side data
16638         $tmpl->append_fields(
16639             map {
16640                 MARC::Field->new( $_, $i1, $i2, @data )
16641             } @replace_them
16642         );
16643
16644         # Construct some 'replace' rules
16645         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16646     }
16647
16648     # Insert the replace rules into the template
16649     $tmpl->append_fields(
16650         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16651     );
16652
16653     $xml = $tmpl->as_xml_record;
16654     $xml =~ s/^<\?.+?\?>$//mo;
16655     $xml =~ s/\n//sgo;
16656     $xml =~ s/>\s+</></sgo;
16657
16658     return $xml;
16659
16660 $func$ LANGUAGE PLPERLU;
16661
16662 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16663     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16664 $func$ LANGUAGE SQL;
16665
16666 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16667     SELECT authority.generate_overlay_template( $1, NULL );
16668 $func$ LANGUAGE SQL;
16669
16670 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16671 DELETE FROM config.metabib_field WHERE id = 26;
16672
16673 -- Making this a global_flag (UI accessible) instead of an internal_flag
16674 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16675     VALUES (
16676         'ingest.disable_authority_linking',
16677         oils_i18n_gettext(
16678             'ingest.disable_authority_linking',
16679             'Authority Automation: Disable bib-authority link tracking',
16680             'cgf', 
16681             'label'
16682         )
16683     );
16684 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16685 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16686
16687 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16688     VALUES (
16689         'ingest.disable_authority_auto_update',
16690         oils_i18n_gettext(
16691             'ingest.disable_authority_auto_update',
16692             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16693             'cgf', 
16694             'label'
16695         )
16696     );
16697
16698 -- Enable automated ingest of authority records; just insert the row into
16699 -- authority.record_entry and authority.full_rec will automatically be populated
16700
16701 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16702     UPDATE  biblio.record_entry
16703       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16704       WHERE id = $2;
16705     SELECT $1;
16706 $func$ LANGUAGE SQL;
16707
16708 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16709     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16710 $func$ LANGUAGE SQL;
16711
16712 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16713
16714 use MARC::Record;
16715 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16716
16717 my $xml = shift;
16718 my $r = MARC::Record->new_from_xml( $xml );
16719
16720 return_next( { tag => 'LDR', value => $r->leader } );
16721
16722 for my $f ( $r->fields ) {
16723     if ($f->is_control_field) {
16724         return_next({ tag => $f->tag, value => $f->data });
16725     } else {
16726         for my $s ($f->subfields) {
16727             return_next({
16728                 tag      => $f->tag,
16729                 ind1     => $f->indicator(1),
16730                 ind2     => $f->indicator(2),
16731                 subfield => $s->[0],
16732                 value    => $s->[1]
16733             });
16734
16735         }
16736     }
16737 }
16738
16739 return undef;
16740
16741 $func$ LANGUAGE PLPERLU;
16742
16743 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16744 DECLARE
16745     auth    authority.record_entry%ROWTYPE;
16746     output    authority.full_rec%ROWTYPE;
16747     field    RECORD;
16748 BEGIN
16749     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16750
16751     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16752         output.record := rid;
16753         output.ind1 := field.ind1;
16754         output.ind2 := field.ind2;
16755         output.tag := field.tag;
16756         output.subfield := field.subfield;
16757         IF field.subfield IS NOT NULL THEN
16758             output.value := naco_normalize(field.value, field.subfield);
16759         ELSE
16760             output.value := field.value;
16761         END IF;
16762
16763         CONTINUE WHEN output.value IS NULL;
16764
16765         RETURN NEXT output;
16766     END LOOP;
16767 END;
16768 $func$ LANGUAGE PLPGSQL;
16769
16770 -- authority.rec_descriptor appears to be unused currently
16771 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16772 BEGIN
16773     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16774 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16775 --        SELECT  auth_id, ;
16776
16777     RETURN;
16778 END;
16779 $func$ LANGUAGE PLPGSQL;
16780
16781 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16782 BEGIN
16783     DELETE FROM authority.full_rec WHERE record = auth_id;
16784     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16785         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16786
16787     RETURN;
16788 END;
16789 $func$ LANGUAGE PLPGSQL;
16790
16791 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16792 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16793 BEGIN
16794
16795     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16796         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16797         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16798           -- Should remove matching $0 from controlled fields at the same time?
16799         RETURN NEW; -- and we're done
16800     END IF;
16801
16802     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16803         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16804
16805         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16806             RETURN NEW;
16807         END IF;
16808     END IF;
16809
16810     -- Flatten and insert the afr data
16811     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16812     IF NOT FOUND THEN
16813         PERFORM authority.reingest_authority_full_rec(NEW.id);
16814 -- authority.rec_descriptor is not currently used
16815 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16816 --        IF NOT FOUND THEN
16817 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16818 --        END IF;
16819     END IF;
16820
16821     RETURN NEW;
16822 END;
16823 $func$ LANGUAGE PLPGSQL;
16824
16825 CREATE TRIGGER aaa_auth_ingest_or_delete AFTER INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE authority.indexing_ingest_or_delete ();
16826
16827 -- Some records manage to get XML namespace declarations into each element,
16828 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16829 -- This broke the old maintain_901(), so we'll make the regex more robust
16830
16831 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16832 BEGIN
16833     -- Remove any existing 901 fields before we insert the authoritative one
16834     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16835     IF TG_TABLE_SCHEMA = 'biblio' THEN
16836         NEW.marc := REGEXP_REPLACE(
16837             NEW.marc,
16838             E'(</(?:[^:]*?:)?record>)',
16839             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16840                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16841                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16842                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16843                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16844                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16845                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16846              E'</datafield>\\1'
16847         );
16848     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16849         NEW.marc := REGEXP_REPLACE(
16850             NEW.marc,
16851             E'(</(?:[^:]*?:)?record>)',
16852             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16853                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16854                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16855              E'</datafield>\\1'
16856         );
16857     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16858         NEW.marc := REGEXP_REPLACE(
16859             NEW.marc,
16860             E'(</(?:[^:]*?:)?record>)',
16861             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16862                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16863                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16864                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16865                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16866              E'</datafield>\\1'
16867         );
16868     ELSE
16869         NEW.marc := REGEXP_REPLACE(
16870             NEW.marc,
16871             E'(</(?:[^:]*?:)?record>)',
16872             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16873                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16874                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16875              E'</datafield>\\1'
16876         );
16877     END IF;
16878
16879     RETURN NEW;
16880 END;
16881 $func$ LANGUAGE PLPGSQL;
16882
16883 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16884 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16885 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16886  
16887 -- In booking, elbow room defines:
16888 --  a) how far in the future you must make a reservation on a given item if
16889 --      that item will have to transit somewhere to fulfill the reservation.
16890 --  b) how soon a reservation must be starting for the reserved item to
16891 --      be op-captured by the checkin interface.
16892
16893 -- We don't want to clobber any default_elbow room at any level:
16894
16895 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16896 DECLARE
16897     existing    actor.org_unit_setting%ROWTYPE;
16898 BEGIN
16899     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16900     IF NOT FOUND THEN
16901         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16902             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16903             'circ.booking_reservation.default_elbow_room',
16904             '"1 day"'
16905         );
16906         RETURN 1;
16907     END IF;
16908     RETURN 0;
16909 END;
16910 $$ LANGUAGE plpgsql;
16911
16912 SELECT pg_temp.default_elbow();
16913
16914 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16915
16916 -- returns the distinct set of target copy IDs from a user's visible circulation history
16917 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16918     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16919 $$ LANGUAGE SQL;
16920
16921 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16922 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16923 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16924 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16925
16926 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16927
16928 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16929 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16930
16931 INSERT INTO config.global_flag (name, label, enabled)
16932     VALUES (
16933         'cat.maintain_control_numbers',
16934         oils_i18n_gettext(
16935             'cat.maintain_control_numbers',
16936             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16937             'cgf', 
16938             'label'
16939         ),
16940         TRUE
16941     );
16942
16943 INSERT INTO config.global_flag (name, label, enabled)
16944     VALUES (
16945         'circ.holds.empty_issuance_ok',
16946         oils_i18n_gettext(
16947             'circ.holds.empty_issuance_ok',
16948             'Holds: Allow holds on empty issuances',
16949             'cgf',
16950             'label'
16951         ),
16952         TRUE
16953     );
16954
16955 INSERT INTO config.global_flag (name, label, enabled)
16956     VALUES (
16957         'circ.holds.usr_not_requestor',
16958         oils_i18n_gettext(
16959             'circ.holds.usr_not_requestor',
16960             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16961             'cgf',
16962             'label'
16963         ),
16964         TRUE
16965     );
16966
16967 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16968 use strict;
16969 use MARC::Record;
16970 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16971 use Encode;
16972 use Unicode::Normalize;
16973
16974 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16975 my $schema = $_TD->{table_schema};
16976 my $rec_id = $_TD->{new}{id};
16977
16978 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16979 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16980 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16981     return;
16982 }
16983
16984 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16985 my $ou_cni = 'EVRGRN';
16986
16987 my $owner;
16988 if ($schema eq 'serial') {
16989     $owner = $_TD->{new}{owning_lib};
16990 } else {
16991     # are.owner and bre.owner can be null, so fall back to the consortial setting
16992     $owner = $_TD->{new}{owner} || 1;
16993 }
16994
16995 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16996 if ($ous_rv->{processed}) {
16997     $ou_cni = $ous_rv->{rows}[0]->{value};
16998     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16999 } else {
17000     # Fall back to the shortname of the OU if there was no OU setting
17001     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
17002     if ($ous_rv->{processed}) {
17003         $ou_cni = $ous_rv->{rows}[0]->{shortname};
17004     }
17005 }
17006
17007 my ($create, $munge) = (0, 0);
17008
17009 my @scns = $record->field('035');
17010
17011 foreach my $id_field ('001', '003') {
17012     my $spec_value;
17013     my @controls = $record->field($id_field);
17014
17015     if ($id_field eq '001') {
17016         $spec_value = $rec_id;
17017     } else {
17018         $spec_value = $ou_cni;
17019     }
17020
17021     # Create the 001/003 if none exist
17022     if (scalar(@controls) == 1) {
17023         # Only one field; check to see if we need to munge it
17024         unless (grep $_->data() eq $spec_value, @controls) {
17025             $munge = 1;
17026         }
17027     } else {
17028         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
17029         foreach my $control (@controls) {
17030             unless ($control->data() eq $spec_value) {
17031                 $record->delete_field($control);
17032             }
17033         }
17034         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
17035         $create = 1;
17036     }
17037 }
17038
17039 # Now, if we need to munge the 001, we will first push the existing 001/003
17040 # into the 035; but if the record did not have one (and one only) 001 and 003
17041 # to begin with, skip this process
17042 if ($munge and not $create) {
17043     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
17044
17045     # Do not create duplicate 035 fields
17046     unless (grep $_->subfield('a') eq $scn, @scns) {
17047         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
17048     }
17049 }
17050
17051 # Set the 001/003 and update the MARC
17052 if ($create or $munge) {
17053     $record->field('001')->data($rec_id);
17054     $record->field('003')->data($ou_cni);
17055
17056     my $xml = $record->as_xml_record();
17057     $xml =~ s/\n//sgo;
17058     $xml =~ s/^<\?xml.+\?\s*>//go;
17059     $xml =~ s/>\s+</></go;
17060     $xml =~ s/\p{Cc}//go;
17061
17062     # Embed a version of OpenILS::Application::AppUtils->entityize()
17063     # to avoid having to set PERL5LIB for PostgreSQL as well
17064
17065     # If we are going to convert non-ASCII characters to XML entities,
17066     # we had better be dealing with a UTF8 string to begin with
17067     $xml = decode_utf8($xml);
17068
17069     $xml = NFC($xml);
17070
17071     # Convert raw ampersands to entities
17072     $xml =~ s/&(?!\S+;)/&amp;/gso;
17073
17074     # Convert Unicode characters to entities
17075     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
17076
17077     $xml =~ s/[\x00-\x1f]//go;
17078     $_TD->{new}{marc} = $xml;
17079
17080     return "MODIFY";
17081 }
17082
17083 return;
17084 $func$ LANGUAGE PLPERLU;
17085
17086 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17087 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17088 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17089
17090 INSERT INTO metabib.facet_entry (source, field, value)
17091     SELECT source, field, value FROM (
17092         SELECT * FROM metabib.author_field_entry
17093             UNION ALL
17094         SELECT * FROM metabib.keyword_field_entry
17095             UNION ALL
17096         SELECT * FROM metabib.identifier_field_entry
17097             UNION ALL
17098         SELECT * FROM metabib.title_field_entry
17099             UNION ALL
17100         SELECT * FROM metabib.subject_field_entry
17101             UNION ALL
17102         SELECT * FROM metabib.series_field_entry
17103         )x
17104     WHERE x.index_vector = '';
17105         
17106 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
17107 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
17108 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
17109 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
17110 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
17111 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
17112
17113 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
17114 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
17115 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
17116
17117 -- copy OPAC visibility materialized view
17118 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
17119
17120     TRUNCATE TABLE asset.opac_visible_copies;
17121
17122     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17123     SELECT  cp.id, cp.circ_lib, cn.record
17124     FROM  asset.copy cp
17125         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17126         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17127         JOIN asset.copy_location cl ON (cp.location = cl.id)
17128         JOIN config.copy_status cs ON (cp.status = cs.id)
17129         JOIN biblio.record_entry b ON (cn.record = b.id)
17130     WHERE NOT cp.deleted
17131         AND NOT cn.deleted
17132         AND NOT b.deleted
17133         AND cs.opac_visible
17134         AND cl.opac_visible
17135         AND cp.opac_visible
17136         AND a.opac_visible;
17137
17138 $$ LANGUAGE SQL;
17139 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17140 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17141 $$;
17142
17143 -- and actually populate the table
17144 SELECT asset.refresh_opac_visible_copies_mat_view();
17145
17146 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17147 DECLARE
17148     add_query       TEXT;
17149     remove_query    TEXT;
17150     do_add          BOOLEAN := false;
17151     do_remove       BOOLEAN := false;
17152 BEGIN
17153     add_query := $$
17154             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17155                 SELECT  cp.id, cp.circ_lib, cn.record
17156                   FROM  asset.copy cp
17157                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17158                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17159                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17160                         JOIN config.copy_status cs ON (cp.status = cs.id)
17161                         JOIN biblio.record_entry b ON (cn.record = b.id)
17162                   WHERE NOT cp.deleted
17163                         AND NOT cn.deleted
17164                         AND NOT b.deleted
17165                         AND cs.opac_visible
17166                         AND cl.opac_visible
17167                         AND cp.opac_visible
17168                         AND a.opac_visible
17169     $$;
17170  
17171     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17172
17173     IF TG_OP = 'INSERT' THEN
17174
17175         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17176             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17177             EXECUTE add_query;
17178         END IF;
17179
17180         RETURN NEW;
17181
17182     END IF;
17183
17184     -- handle items first, since with circulation activity
17185     -- their statuses change frequently
17186     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17187
17188         IF OLD.location    <> NEW.location OR
17189            OLD.call_number <> NEW.call_number OR
17190            OLD.status      <> NEW.status OR
17191            OLD.circ_lib    <> NEW.circ_lib THEN
17192             -- any of these could change visibility, but
17193             -- we'll save some queries and not try to calculate
17194             -- the change directly
17195             do_remove := true;
17196             do_add := true;
17197         ELSE
17198
17199             IF OLD.deleted <> NEW.deleted THEN
17200                 IF NEW.deleted THEN
17201                     do_remove := true;
17202                 ELSE
17203                     do_add := true;
17204                 END IF;
17205             END IF;
17206
17207             IF OLD.opac_visible <> NEW.opac_visible THEN
17208                 IF OLD.opac_visible THEN
17209                     do_remove := true;
17210                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17211                                         -- is also marked opac_visible
17212                     do_add := true;
17213                 END IF;
17214             END IF;
17215
17216         END IF;
17217
17218         IF do_remove THEN
17219             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17220         END IF;
17221         IF do_add THEN
17222             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17223             EXECUTE add_query;
17224         END IF;
17225
17226         RETURN NEW;
17227
17228     END IF;
17229
17230     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17231  
17232         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17233
17234             RETURN NEW;
17235  
17236         ELSIF NEW.deleted THEN -- remove rows
17237  
17238             IF TG_TABLE_NAME = 'call_number' THEN
17239                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17240             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17241                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17242             END IF;
17243  
17244             RETURN NEW;
17245  
17246         ELSIF OLD.deleted THEN -- add rows
17247  
17248             IF TG_TABLE_NAME IN ('copy','unit') THEN
17249                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17250             ELSIF TG_TABLE_NAME = 'call_number' THEN
17251                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17252             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17253                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17254             END IF;
17255  
17256             EXECUTE add_query;
17257             RETURN NEW;
17258  
17259         END IF;
17260  
17261     END IF;
17262
17263     IF TG_TABLE_NAME = 'call_number' THEN
17264
17265         IF OLD.record <> NEW.record THEN
17266             -- call number is linked to different bib
17267             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17268             EXECUTE remove_query;
17269             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17270             EXECUTE add_query;
17271         END IF;
17272
17273         RETURN NEW;
17274
17275     END IF;
17276
17277     IF TG_TABLE_NAME IN ('record_entry') THEN
17278         RETURN NEW; -- don't have 'opac_visible'
17279     END IF;
17280
17281     -- actor.org_unit, asset.copy_location, asset.copy_status
17282     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17283
17284         RETURN NEW;
17285
17286     ELSIF NEW.opac_visible THEN -- add rows
17287
17288         IF TG_TABLE_NAME = 'org_unit' THEN
17289             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17290         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17291             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17292         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17293             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17294         END IF;
17295  
17296         EXECUTE add_query;
17297  
17298     ELSE -- delete rows
17299
17300         IF TG_TABLE_NAME = 'org_unit' THEN
17301             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17302         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17303             remove_query := remove_query || 'location = ' || NEW.id || ');';
17304         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17305             remove_query := remove_query || 'status = ' || NEW.id || ');';
17306         END IF;
17307  
17308         EXECUTE remove_query;
17309  
17310     END IF;
17311  
17312     RETURN NEW;
17313 END;
17314 $func$ LANGUAGE PLPGSQL;
17315 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17316 Trigger function to update the copy OPAC visiblity cache.
17317 $$;
17318 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17319 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17320 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17321 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy_location FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17322 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17323 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON config.copy_status FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17324 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON actor.org_unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17325
17326 -- must create this rule explicitly; it is not inherited from asset.copy
17327 CREATE RULE protect_serial_unit_delete AS ON DELETE TO serial.unit DO INSTEAD UPDATE serial.unit SET deleted = TRUE WHERE OLD.id = serial.unit.id;
17328
17329 CREATE 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);
17330
17331 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17332 DECLARE
17333     moved_objects INT := 0;
17334     bib_id        INT := 0;
17335     bib_rec       biblio.record_entry%ROWTYPE;
17336     auth_link     authority.bib_linking%ROWTYPE;
17337     ingest_same   boolean;
17338 BEGIN
17339
17340     -- Defining our terms:
17341     -- "target record" = the record that will survive the merge
17342     -- "source record" = the record that is sacrifing its existence and being
17343     --   replaced by the target record
17344
17345     -- 1. Update all bib records with the ID from target_record in their $0
17346     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre
17347       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17348       WHERE abl.authority = source_record LOOP
17349
17350         UPDATE biblio.record_entry
17351           SET marc = REGEXP_REPLACE(marc,
17352             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17353             E'\\1' || target_record || '<', 'g')
17354           WHERE id = bib_rec.id;
17355
17356           moved_objects := moved_objects + 1;
17357     END LOOP;
17358
17359     -- 2. Grab the current value of reingest on same MARC flag
17360     SELECT enabled INTO ingest_same
17361       FROM config.internal_flag
17362       WHERE name = 'ingest.reingest.force_on_same_marc'
17363     ;
17364
17365     -- 3. Temporarily set reingest on same to TRUE
17366     UPDATE config.internal_flag
17367       SET enabled = TRUE
17368       WHERE name = 'ingest.reingest.force_on_same_marc'
17369     ;
17370
17371     -- 4. Make a harmless update to target_record to trigger auto-update
17372     --    in linked bibliographic records
17373     UPDATE authority.record_entry
17374       SET deleted = FALSE
17375       WHERE id = target_record;
17376
17377     -- 5. "Delete" source_record
17378     DELETE FROM authority.record_entry
17379       WHERE id = source_record;
17380
17381     -- 6. Set "reingest on same MARC" flag back to initial value
17382     UPDATE config.internal_flag
17383       SET enabled = ingest_same
17384       WHERE name = 'ingest.reingest.force_on_same_marc'
17385     ;
17386
17387     RETURN moved_objects;
17388 END;
17389 $func$ LANGUAGE plpgsql;
17390
17391 -- serial.record_entry already had an owner column spelled "owning_lib"
17392 -- Adjust the table and affected functions accordingly
17393
17394 ALTER TABLE serial.record_entry DROP COLUMN owner;
17395
17396 CREATE TABLE actor.usr_saved_search (
17397     id              SERIAL          PRIMARY KEY,
17398         owner           INT             NOT NULL REFERENCES actor.usr (id)
17399                                         ON DELETE CASCADE
17400                                         DEFERRABLE INITIALLY DEFERRED,
17401         name            TEXT            NOT NULL,
17402         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17403         query_text      TEXT            NOT NULL,
17404         query_type      TEXT            NOT NULL
17405                                         CONSTRAINT valid_query_text CHECK (
17406                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17407                                         -- we may add other types someday
17408         target          TEXT            NOT NULL
17409                                         CONSTRAINT valid_target CHECK (
17410                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17411         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17412 );
17413
17414 -- Apply Dan Wells' changes to the serial schema, from the
17415 -- seials-integration branch
17416
17417 CREATE TABLE serial.subscription_note (
17418         id           SERIAL PRIMARY KEY,
17419         subscription INT    NOT NULL
17420                             REFERENCES serial.subscription (id)
17421                             ON DELETE CASCADE
17422                             DEFERRABLE INITIALLY DEFERRED,
17423         creator      INT    NOT NULL
17424                             REFERENCES actor.usr (id)
17425                             DEFERRABLE INITIALLY DEFERRED,
17426         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17427         pub          BOOL   NOT NULL DEFAULT FALSE,
17428         title        TEXT   NOT NULL,
17429         value        TEXT   NOT NULL
17430 );
17431 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17432
17433 CREATE TABLE serial.distribution_note (
17434         id           SERIAL PRIMARY KEY,
17435         distribution INT    NOT NULL
17436                             REFERENCES serial.distribution (id)
17437                             ON DELETE CASCADE
17438                             DEFERRABLE INITIALLY DEFERRED,
17439         creator      INT    NOT NULL
17440                             REFERENCES actor.usr (id)
17441                             DEFERRABLE INITIALLY DEFERRED,
17442         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17443         pub          BOOL   NOT NULL DEFAULT FALSE,
17444         title        TEXT   NOT NULL,
17445         value        TEXT   NOT NULL
17446 );
17447 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17448
17449 ------- Begin surgery on serial.unit
17450
17451 ALTER TABLE serial.unit
17452         DROP COLUMN label;
17453
17454 ALTER TABLE serial.unit
17455         RENAME COLUMN label_sort_key TO sort_key;
17456
17457 ALTER TABLE serial.unit
17458         RENAME COLUMN contents TO detailed_contents;
17459
17460 ALTER TABLE serial.unit
17461         ADD COLUMN summary_contents TEXT;
17462
17463 UPDATE serial.unit
17464 SET summary_contents = detailed_contents;
17465
17466 ALTER TABLE serial.unit
17467         ALTER column summary_contents SET NOT NULL;
17468
17469 ------- End surgery on serial.unit
17470
17471 -- DELETE FROM config.upgrade_log WHERE version = 'temp'; DELETE FROM action_trigger.event WHERE event_def IN (33,34); DELETE FROM action_trigger.environment WHERE event_def IN (33,34); DELETE FROM action_trigger.event_definition WHERE id IN (33,34); DELETE FROM action_trigger.hook WHERE key IN ( 'circ.format.missing_pieces.slip.print', 'circ.format.missing_pieces.letter.print' );
17472
17473 -- Now rebuild the constraints dropped via cascade.
17474 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17475 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17476 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17477
17478 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17479
17480 DELETE FROM config.metabib_field_index_norm_map
17481     WHERE norm IN (
17482         SELECT id 
17483             FROM config.index_normalizer
17484             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17485     )
17486     AND field = 18
17487 ;
17488
17489 -- We won't necessarily use all of these, but they are here for completeness.
17490 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17491 -- Values are the EDI code value + 1200
17492
17493 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17494 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17495 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17496 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17497 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17498 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17499 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17500 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17501 (1, 't', 1208, 'Inventory quantity at supplier''s subject to inspection by', 'customer Quantity of goods which the customer requires the supplier to have in inventory and which may be inspected by the customer if desired.'),
17502 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17503 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17504 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17505 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17506 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17507 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17508 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17509 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17510 (1, 't', 1217, 'Quantity on hand', 'The total quantity of a product on hand at a location. This includes as well units awaiting return to manufacturer, units unavailable due to inspection procedures and undamaged stock available for despatch, resale or use.'),
17511 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17512 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17513 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17514 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17515 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17516 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17517 (1, 't', 1224, 'Inventory quantity at supplier''s not subject to inspection', 'by customer Quantity of goods which the customer requires the supplier to have in inventory but which will not be checked by the customer.'),
17518 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17519 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17520 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17521 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17522 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17523 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17524 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17525 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17526 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17527 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17528 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17529 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17530 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17531 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17532 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17533 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17534 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17535 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17536 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17537 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17538 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17539 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17540 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17541 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17542 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17543 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17544 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17545 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17546 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17547 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17548 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17549 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17550 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17551 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17552 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17553 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17554 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17555 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17556 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17557 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17558 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17559 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17560 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17561 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17562 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17563 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17564 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17565 (1, 't', 1272, 'Cumulative quantity received end of prior year', 'Cumulative quantity of all deliveries of the product received by the buyer till end of prior year.'),
17566 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17567 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17568 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17569 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17570 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17571 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17572 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17573 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17574 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17575 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17576 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17577 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17578 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17579 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17580 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17581 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17582 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17583 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17584 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17585 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17586 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17587 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17588 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17589 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17590 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17591 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17592 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17593 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17594 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17595 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17596 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17597 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17598 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17599 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17600 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17601 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17602 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17603 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17604 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17605 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17606 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17607 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17608 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17609 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17610 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17611 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17612 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17613 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17614 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17615 (1, 't', 1322, 'Short-landed goods', 'If quantity of goods actually landed is less than the quantity which appears in the documentation. This quantity is the difference between these quantities.'),
17616 (1, 't', 1323, 'Surplus goods', 'If quantity of goods actually landed is more than the quantity which appears in the documentation. This quantity is the difference between these quantities.'),
17617 (1, 'f', 1324, 'Damaged goods', 'Quantity of goods which have deteriorated in transport such that they cannot be used for the purpose for which they were originally intended.'),
17618 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17619 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17620 (1, 't', 1327, 'Report difference', 'The quantity concerning the same transaction differs between two documents/messages and the source of this difference is a typing error.'),
17621 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17622 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17623 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17624 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17625 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17626 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17627 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17628 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17629 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17630 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17631 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17632 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17633 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17634 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17635 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17636 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17637 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17638 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17639 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17640 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17641 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17642 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17643 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17644 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17645 (1, 't', 1352, 'Consignment stock', 'Quantity of goods with an external customer which is still the property of the supplier. Payment for these goods is only made to the supplier when the ownership has been transferred between the trading partners.'),
17646 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17647 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17648 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17649 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17650 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17651 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17652 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17653 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17654 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17655 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17656 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17657 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17658 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17659 (1, 't', 1366, 'All time buy',             'The total quantity of the referenced covering all future needs. Further orders of the referenced item are not expected.'),
17660 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17661 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17662 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17663 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17664 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17665 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17666 (1, 't', 1373, 'Previously amortised quantity', 'The cumulative quantity of the referenced item which had a cost for tooling amortisation included in the item price.'),
17667 (1, 't', 1374, 'Total amortisation quantity', 'The total quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17668 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17669 (1, 't', 1376, 'Concurrent item output of tooling', 'The number of related items which can be produced simultaneously with a single piece of the referenced tooling.'),
17670 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17671 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17672 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17673 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17674 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17675 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17676 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17677 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17678 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17679 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17680 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17681 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17682 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17683 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17684 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17685 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17686 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17687 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17688 (1, 'f', 1395, 'Received, not accepted, to be returned',  'Quantity which has been received but not accepted at a given location and which will consequently be returned to the relevant party.'),
17689 (1, 'f', 1396, 'Received, not accepted, to be destroyed', 'Quantity which has been received but not accepted at a given location and which will consequently be destroyed.'),
17690 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17691 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17692 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17693 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17694 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17695 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17696 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17697 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17698 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17699 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17700 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17701 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17702 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17703 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17704 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17705 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17706 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17707 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17708 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17709 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17710 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17711 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17712 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17713 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17714 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17715 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17716 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17717 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17718 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17719 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17720 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17721 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17722 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17723 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17724 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17725 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17726 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17727 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17728 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17729 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17730 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17731 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17732 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17733 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17734 (1, 't', 1442, 'Number of months', 'The number of months.'),
17735 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17736 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17737 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17738 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17739 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17740 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17741 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17742 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17743 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17744 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17745 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17746 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17747 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17748 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17749 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17750 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17751 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17752 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17753 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17754 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17755 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17756 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17757 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17758 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17759 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17760 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17761 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17762 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17763 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17764 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17765 (1, 't', 1473, 'Agents', 'The number of agents.'),
17766 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17767 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17768 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17769 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17770 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17771 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17772 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17773 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17774 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17775 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17776 (1, 't', 1484, 'Departments', 'The number of departments.'),
17777 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17778 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17779 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17780 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17781 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17782 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17783 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17784 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17785 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17786 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17787 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17788 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17789 (1, 't', 1497, 'Executives', 'The number of executives.'),
17790 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17791 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17792 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17793 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17794 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17795 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17796 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17797 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17798 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17799 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17800 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17801 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17802 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17803 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17804 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17805 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17806 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17807 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17808 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17809 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17810 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17811 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17812 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17813 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17814 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17815 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17816 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17817 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17818 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17819 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17820 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17821 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17822 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17823 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17824 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17825 (1, 't', 1533, 'Seats',        'The number of seats.'),
17826 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17827 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17828 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17829 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17830 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17831 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17832 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17833 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17834 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17835 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17836 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17837 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17838 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17839 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17840 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17841 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17842 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17843 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17844 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17845 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17846 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17847 (1, 't', 1555, 'Total number of foreign subsidiaries not included in', 'financial statement The total number of foreign subsidiaries not included in the financial statement.'),
17848 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17849 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17850 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17851 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17852 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17853 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17854 (1, 't', 1562, 'Total number of domestic subsidiaries not included in', 'financial statement The total number of domestic subsidiaries not included in the financial statement.'),
17855 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17856 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17857 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17858 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17859 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17860 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17861 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17862 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17863 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17864 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17865 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17866 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17867 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17868 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17869 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17870 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17871 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17872 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17873 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17874 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17875 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17876 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17877 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17878 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17879 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17880 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17881 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17882 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17883 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17884 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17885 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17886 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17887 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17888 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17889 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17890 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17891 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17892 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17893 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17894 (1, 't', 1602, 'Patients',         'Number of patients.'),
17895 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17896 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17897 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17898 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17899 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17900 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17901 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17902 (1, 't', 1610, 'Operators',        'Number of operators.'),
17903 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17904 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17905 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17906 (1, 't', 1614, 'Machines',         'Number of machines.'),
17907 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17908 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17909 (1, 't', 1617, 'Directors',        'Number of directors.'),
17910 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17911 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17912 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17913 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17914 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17915 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17916 (1, 't', 1624, 'Beds', 'Number of beds.'),
17917 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17918 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17919 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17920 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17921 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17922 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17923 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17924 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17925 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17926 (1, 't', 1634, 'Detrimental legal filings against directors', 'The number of legal filings that are of a detrimental nature that have been filed against the directors.'),
17927 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17928 (1, 't', 1636, 'Professor', 'The number of professors.'),
17929 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17930 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17931 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17932 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17933 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17934 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17935 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17936 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17937 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17938 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17939 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17940 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17941 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17942 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17943 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17944 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17945 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17946 (1, 't', 1654, 'Quantity ordered but not yet allocated from stock', 'A quantity of products which has been ordered but which has not yet been allocated from stock.'),
17947 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17948 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17949 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17950 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17951 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17952 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17953 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17954 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17955 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17956 (1, 't', 1664, 'Tariff Quantity', 'Quantity of the goods in the unit as required by Customs for duty/tax/fee assessment. These quantities may also be used for other fiscal or statistical purposes.'),
17957 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17958 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17959 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17960 (1, 't', 1668, 'Goods blocked for transshipment process', 'Goods are physically present, but can not be ordered because they are scheduled for a transshipment process.'),
17961 (1, 't', 1669, 'Goods blocked for cross docking process', 'Goods are physically present, but can not be ordered because they are scheduled for a cross docking process.'),
17962 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17963 (1, 't', 1671, 'Number of packages for a set', 'Number of packages used to pack the individual items in a grouping of merchandise that is sold together as a single trade item.'),
17964 (1, 't', 1672, 'Number of items in a set', 'The number of individual items in a grouping of merchandise that is sold together as a single trade item.'),
17965 (1, 't', 1673, 'Order sizing factor', 'A trade item specification other than gross, net weight, or volume for a trade item or a transaction, used for order sizing and pricing purposes.'),
17966 (1, 't', 1674, 'Number of different next lower level trade items', 'Value indicates the number of differrent next lower level trade items contained in a complex trade item.'),
17967 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17968 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17969 (1, 't', 1677, 'Free quantity of next lower level trade item', 'The numeric quantity of free items in a combination pack. The unit of measure used for the free quantity of the next lower level must be the same as the unit of measure of the Net Content of the Child Trade Item.'),
17970 (1, 't', 1678, 'Marine Diesel Oil bunkers on board, on arrival',     'Number of Marine Diesel Oil (MDO) bunkers on board when the vessel arrives in the port.'),
17971 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17972 (1, 't', 1680, 'Intermediate Fuel Oil bunkers on board, on arrival', 'Number of Intermediate Fuel Oil (IFO) bunkers on board when the vessel arrives in the port.'),
17973 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17974 (1, 't', 1682, 'Bunker C bunkers on board, on arrival',              'Number of Bunker C, or Number 6 fuel oil bunkers on board when the vessel arrives in the port.'),
17975 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17976 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17977 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17978 (1, 't', 1686, 'Quantity to be decremented (LPCO)', 'Quantity to be decremented from the allowable quantity on a License, Permit, Certificate, or Other document (LPCO).'),
17979 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17980 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17981 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17982 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17983 ;
17984 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17985
17986 CREATE TABLE acq.serial_claim (
17987     id     SERIAL           PRIMARY KEY,
17988     type   INT              NOT NULL REFERENCES acq.claim_type
17989                                      DEFERRABLE INITIALLY DEFERRED,
17990     item    BIGINT          NOT NULL REFERENCES serial.item
17991                                      DEFERRABLE INITIALLY DEFERRED
17992 );
17993
17994 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17995
17996 CREATE TABLE acq.serial_claim_event (
17997     id             BIGSERIAL        PRIMARY KEY,
17998     type           INT              NOT NULL REFERENCES acq.claim_event_type
17999                                              DEFERRABLE INITIALLY DEFERRED,
18000     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
18001                                              DEFERRABLE INITIALLY DEFERRED,
18002     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
18003     creator        INT              NOT NULL REFERENCES actor.usr
18004                                              DEFERRABLE INITIALLY DEFERRED,
18005     note           TEXT
18006 );
18007
18008 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
18009
18010 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
18011
18012 -- now what about the auditor.*_lifecycle views??
18013
18014 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
18015     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
18016 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
18017     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
18018 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
18019 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
18020
18021 CREATE TABLE asset.call_number_class (
18022     id             bigserial     PRIMARY KEY,
18023     name           TEXT          NOT NULL,
18024     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
18025     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18026 );
18027
18028 COMMENT ON TABLE asset.call_number_class IS $$
18029 Defines the call number normalization database functions in the "normalizer"
18030 column and the tag/subfield combinations to use to lookup the call number in
18031 the "field" column for a given classification scheme. Tag/subfield combinations
18032 are delimited by commas.
18033 $$;
18034
18035 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
18036     ('Generic', 'asset.label_normalizer_generic'),
18037     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
18038     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
18039 ;
18040
18041 -- Generic fields
18042 UPDATE asset.call_number_class
18043     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18044     WHERE id = 1
18045 ;
18046
18047 -- Dewey fields
18048 UPDATE asset.call_number_class
18049     SET field = '080ab,082ab'
18050     WHERE id = 2
18051 ;
18052
18053 -- LC fields
18054 UPDATE asset.call_number_class
18055     SET field = '050ab,055ab'
18056     WHERE id = 3
18057 ;
18058  
18059 ALTER TABLE asset.call_number
18060         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
18061                 REFERENCES asset.call_number_class(id)
18062                 DEFERRABLE INITIALLY DEFERRED;
18063
18064 ALTER TABLE asset.call_number
18065         ADD COLUMN label_sortkey TEXT;
18066
18067 CREATE INDEX asset_call_number_label_sortkey
18068         ON asset.call_number(oils_text_as_bytea(label_sortkey));
18069
18070 ALTER TABLE auditor.asset_call_number_history
18071         ADD COLUMN label_class BIGINT;
18072
18073 ALTER TABLE auditor.asset_call_number_history
18074         ADD COLUMN label_sortkey TEXT;
18075
18076 -- Pick up the new columns in dependent views
18077
18078 DROP VIEW auditor.asset_call_number_lifecycle;
18079
18080 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18081
18082 DROP VIEW auditor.asset_call_number_lifecycle;
18083
18084 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18085
18086 DROP VIEW IF EXISTS stats.fleshed_call_number;
18087
18088 CREATE VIEW stats.fleshed_call_number AS
18089         SELECT  cn.*,
18090             CAST(cn.create_date AS DATE) AS create_date_day,
18091         CAST(cn.edit_date AS DATE) AS edit_date_day,
18092         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
18093         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
18094             rd.item_lang,
18095                 rd.item_type,
18096                 rd.item_form
18097         FROM    asset.call_number cn
18098                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
18099
18100 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
18101 DECLARE
18102     sortkey        TEXT := '';
18103 BEGIN
18104     sortkey := NEW.label_sortkey;
18105
18106     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
18107        quote_literal( NEW.label ) || ')'
18108        FROM asset.call_number_class acnc
18109        WHERE acnc.id = NEW.label_class
18110        INTO sortkey;
18111
18112     NEW.label_sortkey = sortkey;
18113
18114     RETURN NEW;
18115 END;
18116 $func$ LANGUAGE PLPGSQL;
18117
18118 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
18119     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
18120     # thus could probably be considered a derived work, although nothing was
18121     # directly copied - but to err on the safe side of providing attribution:
18122     # Copyright (C) 2007 LibLime
18123     # Licensed under the GPL v2 or later
18124
18125     use strict;
18126     use warnings;
18127
18128     # Converts the callnumber to uppercase
18129     # Strips spaces from start and end of the call number
18130     # Converts anything other than letters, digits, and periods into underscores
18131     # Collapses multiple underscores into a single underscore
18132     my $callnum = uc(shift);
18133     $callnum =~ s/^\s//g;
18134     $callnum =~ s/\s$//g;
18135     $callnum =~ s/[^A-Z0-9_.]/_/g;
18136     $callnum =~ s/_{2,}/_/g;
18137
18138     return $callnum;
18139 $func$ LANGUAGE PLPERLU;
18140
18141 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
18142     # Derived from the Koha C4::ClassSortRoutine::Dewey module
18143     # Copyright (C) 2007 LibLime
18144     # Licensed under the GPL v2 or later
18145
18146     use strict;
18147     use warnings;
18148
18149     my $init = uc(shift);
18150     $init =~ s/^\s+//;
18151     $init =~ s/\s+$//;
18152     $init =~ s!/!!g;
18153     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18154     my @tokens = split /\.|\s+/, $init;
18155     my $digit_group_count = 0;
18156     for (my $i = 0; $i <= $#tokens; $i++) {
18157         if ($tokens[$i] =~ /^\d+$/) {
18158             $digit_group_count++;
18159             if (2 == $digit_group_count) {
18160                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18161                 $tokens[$i] =~ tr/ /0/;
18162             }
18163         }
18164     }
18165     my $key = join("_", @tokens);
18166     $key =~ s/[^\p{IsAlnum}_]//g;
18167
18168     return $key;
18169
18170 $func$ LANGUAGE PLPERLU;
18171
18172 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18173     use strict;
18174     use warnings;
18175
18176     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18177     # The author hopes to upload it to CPAN some day, which would make our lives easier
18178     use Library::CallNumber::LC;
18179
18180     my $callnum = Library::CallNumber::LC->new(shift);
18181     return $callnum->normalize();
18182
18183 $func$ LANGUAGE PLPERLU;
18184
18185 CREATE OR REPLACE FUNCTION asset.opac_ou_record_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18186 DECLARE
18187     ans RECORD;
18188     trans INT;
18189 BEGIN
18190     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18191
18192     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
18193         RETURN QUERY
18194         SELECT  ans.depth,
18195                 ans.id,
18196                 COUNT( av.id ),
18197                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18198                 COUNT( av.id ),
18199                 trans
18200           FROM
18201                 actor.org_unit_descendants(ans.id) d
18202                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18203                 JOIN asset.copy cp ON (cp.id = av.id)
18204           GROUP BY 1,2,6;
18205
18206         IF NOT FOUND THEN
18207             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18208         END IF;
18209
18210     END LOOP;
18211
18212     RETURN;
18213 END;
18214 $f$ LANGUAGE PLPGSQL;
18215
18216 CREATE OR REPLACE FUNCTION asset.opac_lasso_record_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18217 DECLARE
18218     ans RECORD;
18219     trans INT;
18220 BEGIN
18221     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18222
18223     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18224         RETURN QUERY
18225         SELECT  -1,
18226                 ans.id,
18227                 COUNT( av.id ),
18228                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18229                 COUNT( av.id ),
18230                 trans
18231           FROM
18232                 actor.org_unit_descendants(ans.id) d
18233                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18234                 JOIN asset.copy cp ON (cp.id = av.id)
18235           GROUP BY 1,2,6;
18236
18237         IF NOT FOUND THEN
18238             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18239         END IF;
18240
18241     END LOOP;
18242
18243     RETURN;
18244 END;
18245 $f$ LANGUAGE PLPGSQL;
18246
18247 CREATE OR REPLACE FUNCTION asset.staff_ou_record_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18248 DECLARE
18249     ans RECORD;
18250     trans INT;
18251 BEGIN
18252     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18253
18254     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
18255         RETURN QUERY
18256         SELECT  ans.depth,
18257                 ans.id,
18258                 COUNT( cp.id ),
18259                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18260                 COUNT( cp.id ),
18261                 trans
18262           FROM
18263                 actor.org_unit_descendants(ans.id) d
18264                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18265                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18266           GROUP BY 1,2,6;
18267
18268         IF NOT FOUND THEN
18269             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18270         END IF;
18271
18272     END LOOP;
18273
18274     RETURN;
18275 END;
18276 $f$ LANGUAGE PLPGSQL;
18277
18278 CREATE OR REPLACE FUNCTION asset.staff_lasso_record_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18279 DECLARE
18280     ans RECORD;
18281     trans INT;
18282 BEGIN
18283     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18284
18285     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18286         RETURN QUERY
18287         SELECT  -1,
18288                 ans.id,
18289                 COUNT( cp.id ),
18290                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18291                 COUNT( cp.id ),
18292                 trans
18293           FROM
18294                 actor.org_unit_descendants(ans.id) d
18295                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18296                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18297           GROUP BY 1,2,6;
18298
18299         IF NOT FOUND THEN
18300             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18301         END IF;
18302
18303     END LOOP;
18304
18305     RETURN;
18306 END;
18307 $f$ LANGUAGE PLPGSQL;
18308
18309 CREATE OR REPLACE FUNCTION asset.record_copy_count ( place INT, record BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18310 BEGIN
18311     IF staff IS TRUE THEN
18312         IF place > 0 THEN
18313             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18314         ELSE
18315             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18316         END IF;
18317     ELSE
18318         IF place > 0 THEN
18319             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18320         ELSE
18321             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18322         END IF;
18323     END IF;
18324
18325     RETURN;
18326 END;
18327 $f$ LANGUAGE PLPGSQL;
18328
18329 CREATE OR REPLACE FUNCTION asset.opac_ou_metarecord_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18330 DECLARE
18331     ans RECORD;
18332     trans INT;
18333 BEGIN
18334     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18335
18336     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
18337         RETURN QUERY
18338         SELECT  ans.depth,
18339                 ans.id,
18340                 COUNT( av.id ),
18341                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18342                 COUNT( av.id ),
18343                 trans
18344           FROM
18345                 actor.org_unit_descendants(ans.id) d
18346                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18347                 JOIN asset.copy cp ON (cp.id = av.id)
18348                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18349           GROUP BY 1,2,6;
18350
18351         IF NOT FOUND THEN
18352             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18353         END IF;
18354
18355     END LOOP;
18356
18357     RETURN;
18358 END;
18359 $f$ LANGUAGE PLPGSQL;
18360
18361 CREATE OR REPLACE FUNCTION asset.opac_lasso_metarecord_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18362 DECLARE
18363     ans RECORD;
18364     trans INT;
18365 BEGIN
18366     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18367
18368     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18369         RETURN QUERY
18370         SELECT  -1,
18371                 ans.id,
18372                 COUNT( av.id ),
18373                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18374                 COUNT( av.id ),
18375                 trans
18376           FROM
18377                 actor.org_unit_descendants(ans.id) d
18378                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18379                 JOIN asset.copy cp ON (cp.id = av.id)
18380                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18381           GROUP BY 1,2,6;
18382
18383         IF NOT FOUND THEN
18384             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18385         END IF;
18386
18387     END LOOP;
18388
18389     RETURN;
18390 END;
18391 $f$ LANGUAGE PLPGSQL;
18392
18393 CREATE OR REPLACE FUNCTION asset.staff_ou_metarecord_copy_count (org INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18394 DECLARE
18395     ans RECORD;
18396     trans INT;
18397 BEGIN
18398     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18399
18400     FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
18401         RETURN QUERY
18402         SELECT  ans.depth,
18403                 ans.id,
18404                 COUNT( cp.id ),
18405                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18406                 COUNT( cp.id ),
18407                 trans
18408           FROM
18409                 actor.org_unit_descendants(ans.id) d
18410                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18411                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18412                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18413           GROUP BY 1,2,6;
18414
18415         IF NOT FOUND THEN
18416             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18417         END IF;
18418
18419     END LOOP;
18420
18421     RETURN;
18422 END;
18423 $f$ LANGUAGE PLPGSQL;
18424
18425 CREATE OR REPLACE FUNCTION asset.staff_lasso_metarecord_copy_count (i_lasso INT, record BIGINT) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18426 DECLARE
18427     ans RECORD;
18428     trans INT;
18429 BEGIN
18430     SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = record;
18431
18432     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18433         RETURN QUERY
18434         SELECT  -1,
18435                 ans.id,
18436                 COUNT( cp.id ),
18437                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18438                 COUNT( cp.id ),
18439                 trans
18440           FROM
18441                 actor.org_unit_descendants(ans.id) d
18442                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18443                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18444                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18445           GROUP BY 1,2,6;
18446
18447         IF NOT FOUND THEN
18448             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18449         END IF;
18450
18451     END LOOP;
18452
18453     RETURN;
18454 END;
18455 $f$ LANGUAGE PLPGSQL;
18456
18457 CREATE OR REPLACE FUNCTION asset.metarecord_copy_count ( place INT, record BIGINT, staff BOOL) RETURNS TABLE (depth INT, org_unit INT, visible BIGINT, available BIGINT, unshadow BIGINT, transcendant INT) AS $f$
18458 BEGIN
18459     IF staff IS TRUE THEN
18460         IF place > 0 THEN
18461             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18462         ELSE
18463             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18464         END IF;
18465     ELSE
18466         IF place > 0 THEN
18467             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18468         ELSE
18469             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18470         END IF;
18471     END IF;
18472
18473     RETURN;
18474 END;
18475 $f$ LANGUAGE PLPGSQL;
18476
18477 -- No transaction is required
18478
18479 -- Triggers on the vandelay.queued_*_record tables delete entries from
18480 -- the associated vandelay.queued_*_record_attr tables based on the record's
18481 -- ID; create an index on that column to avoid sequential scans for each
18482 -- queued record that is deleted
18483 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18484 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18485
18486 -- Avoid sequential scans for queue retrieval operations by providing an
18487 -- index on the queue column
18488 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18489 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18490
18491 -- Start picking up call number label prefixes and suffixes
18492 -- from asset.copy_location
18493 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18494 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18495
18496 DROP VIEW auditor.asset_copy_lifecycle;
18497
18498 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18499
18500 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18501
18502 -- Let's not break existing reports
18503 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18504 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18505
18506 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18507 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18508 SELECT  r.id,
18509     r.fingerprint,
18510     r.quality,
18511     r.tcn_source,
18512     r.tcn_value,
18513     FIRST(title.value) AS title,
18514     FIRST(author.value) AS author,
18515     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18516     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18517     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18518     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18519   FROM  biblio.record_entry r
18520     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18521     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18522     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18523     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18524     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18525     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18526   GROUP BY 1,2,3,4,5;
18527
18528 -- Correct the ISSN array definition for reporter.simple_record
18529
18530 CREATE OR REPLACE VIEW reporter.simple_record AS
18531 SELECT  r.id,
18532         s.metarecord,
18533         r.fingerprint,
18534         r.quality,
18535         r.tcn_source,
18536         r.tcn_value,
18537         title.value AS title,
18538         uniform_title.value AS uniform_title,
18539         author.value AS author,
18540         publisher.value AS publisher,
18541         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18542         series_title.value AS series_title,
18543         series_statement.value AS series_statement,
18544         summary.value AS summary,
18545         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18546         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18547         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18548         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18549         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18550         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18551         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18552         ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
18553   FROM  biblio.record_entry r
18554         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18555         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18556         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18557         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18558         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18559         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18560         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18561         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18562         LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
18563         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18564         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18565   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18566
18567 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18568     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
18569 $$ LANGUAGE SQL;
18570
18571 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
18572
18573     DELETE FROM reporter.materialized_simple_record;
18574
18575     INSERT INTO reporter.materialized_simple_record
18576         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
18577         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
18578
18579     CREATE TRIGGER bbb_simple_rec_trigger
18580         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
18581         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
18582
18583 $$ LANGUAGE SQL;
18584
18585 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18586 BEGIN
18587     IF TG_OP = 'DELETE' THEN
18588         PERFORM reporter.simple_rec_delete(NEW.id);
18589     ELSE
18590         PERFORM reporter.simple_rec_update(NEW.id);
18591     END IF;
18592
18593     RETURN NEW;
18594 END;
18595 $func$ LANGUAGE PLPGSQL;
18596
18597 CREATE TRIGGER bbb_simple_rec_trigger AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger ();
18598
18599 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18600
18601 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18602
18603 UPDATE config.org_unit_setting_type
18604     SET view_perm = (SELECT id FROM permission.perm_list
18605         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18606     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18607
18608 UPDATE config.org_unit_setting_type
18609     SET update_perm = (SELECT id FROM permission.perm_list
18610         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18611     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18612
18613 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18614     VALUES (
18615         'opac.fully_compressed_serial_holdings',
18616         'OPAC: Use fully compressed serial holdings',
18617         'Show fully compressed serial holdings for all libraries at and below
18618         the current context unit',
18619         'bool'
18620     );
18621
18622 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18623     use strict;
18624     use warnings;
18625
18626     use utf8;
18627     use MARC::Record;
18628     use MARC::File::XML (BinaryEncoding => 'UTF8');
18629     use MARC::Charset;
18630     use UUID::Tiny ':std';
18631
18632     MARC::Charset->assume_unicode(1);
18633
18634     my $xml = shift() or return undef;
18635
18636     my $r;
18637
18638     # Prevent errors in XML parsing from blowing out ungracefully
18639     eval {
18640         $r = MARC::Record->new_from_xml( $xml );
18641         1;
18642     } or do {
18643        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18644     };
18645
18646     if (!$r) {
18647        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18648     }
18649
18650     # From http://www.loc.gov/standards/sourcelist/subject.html
18651     my $thes_code_map = {
18652         a => 'lcsh',
18653         b => 'lcshac',
18654         c => 'mesh',
18655         d => 'nal',
18656         k => 'cash',
18657         n => 'notapplicable',
18658         r => 'aat',
18659         s => 'sears',
18660         v => 'rvm',
18661     };
18662
18663     # Default to "No attempt to code" if the leader is horribly broken
18664     my $fixed_field = $r->field('008');
18665     my $thes_char = '|';
18666     if ($fixed_field) { 
18667         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18668     }
18669
18670     my $thes_code = 'UNDEFINED';
18671
18672     if ($thes_char eq 'z') {
18673         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18674         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18675     } elsif ($thes_code_map->{$thes_char}) {
18676         $thes_code = $thes_code_map->{$thes_char};
18677     }
18678
18679     my $auth_txt = '';
18680     my $head = $r->field('1..');
18681     if ($head) {
18682         # Concatenate all of these subfields together, prefixed by their code
18683         # to prevent collisions along the lines of "Fiction, North Carolina"
18684         foreach my $sf ($head->subfields()) {
18685             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18686         }
18687     }
18688     
18689     if ($auth_txt) {
18690         my $stmt = spi_prepare('SELECT public.naco_normalize($1) AS norm_text', 'TEXT');
18691         my $result = spi_exec_prepared($stmt, $auth_txt);
18692         my $norm_txt = $result->{rows}[0]->{norm_text};
18693         spi_freeplan($stmt);
18694         undef($stmt);
18695         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18696     }
18697
18698     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18699 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18700
18701 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18702 /**
18703 * Extract the authority heading, thesaurus, and NACO-normalized values
18704 * from an authority record. The primary purpose is to build a unique
18705 * index to defend against duplicated authority records from the same
18706 * thesaurus.
18707 */
18708 $$;
18709
18710 DROP INDEX authority.authority_record_unique_tcn;
18711 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18712 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18713
18714 ALTER TABLE acq.provider_contact
18715         ALTER COLUMN name SET NOT NULL;
18716
18717 ALTER TABLE actor.stat_cat
18718         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18719
18720 -- Per Robert Soulliere, it can be necessary in some cases to clean out bad
18721 -- data from action.reservation_transit_copy before applying the missing
18722 -- fkeys below.
18723 -- https://bugs.launchpad.net/evergreen/+bug/721450
18724 DELETE FROM action.reservation_transit_copy
18725     WHERE target_copy NOT IN (SELECT id FROM booking.resource);
18726 -- In the same spirit as the above delete, this can only fix bad data.
18727 UPDATE action.reservation_transit_copy
18728     SET reservation = NULL
18729     WHERE reservation NOT IN (SELECT id FROM booking.reservation);
18730
18731 -- Recreate some foreign keys that were somehow dropped, probably
18732 -- by some kind of cascade from an inherited table:
18733
18734 CREATE INDEX user_bucket_item_target_user_idx
18735         ON container.user_bucket_item ( target_user );
18736
18737 CREATE INDEX m_c_t_collector_idx
18738         ON money.collections_tracker ( collector );
18739
18740 CREATE INDEX aud_actor_usr_address_hist_id_idx
18741         ON auditor.actor_usr_address_history ( id );
18742
18743 CREATE INDEX aud_actor_usr_hist_id_idx
18744         ON auditor.actor_usr_history ( id );
18745
18746 CREATE INDEX aud_asset_cn_hist_creator_idx
18747         ON auditor.asset_call_number_history ( creator );
18748
18749 CREATE INDEX aud_asset_cn_hist_editor_idx
18750         ON auditor.asset_call_number_history ( editor );
18751
18752 CREATE INDEX aud_asset_cp_hist_creator_idx
18753         ON auditor.asset_copy_history ( creator );
18754
18755 CREATE INDEX aud_asset_cp_hist_editor_idx
18756         ON auditor.asset_copy_history ( editor );
18757
18758 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18759         ON auditor.biblio_record_entry_history ( creator );
18760
18761 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18762         ON auditor.biblio_record_entry_history ( editor );
18763
18764 CREATE TABLE action.hold_request_note (
18765
18766     id     BIGSERIAL PRIMARY KEY,
18767     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18768                               ON DELETE CASCADE
18769                               DEFERRABLE INITIALLY DEFERRED,
18770     title  TEXT      NOT NULL,
18771     body   TEXT      NOT NULL,
18772     slip   BOOL      NOT NULL DEFAULT FALSE,
18773     pub    BOOL      NOT NULL DEFAULT FALSE,
18774     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18775
18776 );
18777 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18778
18779 -- Tweak a constraint to add a CASCADE
18780
18781 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18782
18783 ALTER TABLE action.hold_notification
18784         ADD CONSTRAINT hold_notification_hold_fkey
18785                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18786                 ON DELETE CASCADE
18787                 DEFERRABLE INITIALLY DEFERRED;
18788
18789 CREATE TRIGGER asset_label_sortkey_trigger
18790     BEFORE UPDATE OR INSERT ON asset.call_number
18791     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18792
18793 -- Now populate the label_sortkey column via the trigger
18794 UPDATE asset.call_number SET id = id;
18795
18796 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18797 RETURNS VOID AS $$
18798 --
18799 -- Delete expired circulation bucket items for all users that have
18800 -- a setting for patron.max_reading_list_interval.
18801 --
18802 DECLARE
18803     today        TIMESTAMP WITH TIME ZONE;
18804     threshold    TIMESTAMP WITH TIME ZONE;
18805         usr_setting  RECORD;
18806 BEGIN
18807         SELECT date_trunc( 'day', now() ) INTO today;
18808         --
18809         FOR usr_setting in
18810                 SELECT
18811                         usr,
18812                         value
18813                 FROM
18814                         actor.usr_setting
18815                 WHERE
18816                         name = 'patron.max_reading_list_interval'
18817         LOOP
18818                 --
18819                 -- Make sure the setting is a valid interval
18820                 --
18821                 BEGIN
18822                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18823                 EXCEPTION
18824                         WHEN OTHERS THEN
18825                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18826                                         usr_setting.usr, usr_setting.value;
18827                                 CONTINUE;
18828                 END;
18829                 --
18830                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18831                 --
18832         DELETE FROM container.copy_bucket_item
18833         WHERE
18834                 bucket IN
18835                 (
18836                     SELECT
18837                         id
18838                     FROM
18839                         container.copy_bucket
18840                     WHERE
18841                         owner = usr_setting.usr
18842                         AND btype = 'circ_history'
18843                 )
18844                 AND create_time < threshold;
18845         END LOOP;
18846         --
18847 END;
18848 $$ LANGUAGE plpgsql;
18849
18850 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18851 /*
18852  * Delete expired circulation bucket items for all users that have
18853  * a setting for patron.max_reading_list_interval.
18854 */
18855 $$;
18856
18857 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18858          ac_usr IN INTEGER
18859 ) RETURNS VOID AS $$
18860 --
18861 -- Delete old circulation bucket items for a specified user.
18862 -- "Old" means older than the interval specified by a
18863 -- user-level setting, if it is so specified.
18864 --
18865 DECLARE
18866     threshold TIMESTAMP WITH TIME ZONE;
18867 BEGIN
18868         -- Sanity check
18869         IF ac_usr IS NULL THEN
18870                 RETURN;
18871         END IF;
18872         -- Determine the threshold date that defines "old".  Subtract the
18873         -- interval from the system date, then truncate to midnight.
18874         SELECT
18875                 date_trunc( 
18876                         'day',
18877                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18878                 )
18879         INTO
18880                 threshold
18881         FROM
18882                 actor.usr_setting
18883         WHERE
18884                 usr = ac_usr
18885                 AND name = 'patron.max_reading_list_interval';
18886         --
18887         IF threshold is null THEN
18888                 -- No interval defined; don't delete anything
18889                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18890                 return;
18891         END IF;
18892         --
18893         -- RAISE NOTICE 'Date threshold: %', threshold;
18894         --
18895         -- Threshold found; do the delete
18896         delete from container.copy_bucket_item
18897         where
18898                 bucket in
18899                 (
18900                         select
18901                                 id
18902                         from
18903                                 container.copy_bucket
18904                         where
18905                                 owner = ac_usr
18906                                 and btype = 'circ_history'
18907                 )
18908                 and create_time < threshold;
18909         --
18910         RETURN;
18911 END;
18912 $$ LANGUAGE plpgsql;
18913
18914 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18915 /*
18916  * Delete old circulation bucket items for a specified user.
18917  * "Old" means older than the interval specified by a
18918  * user-level setting, if it is so specified.
18919 */
18920 $$;
18921
18922 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18923 SELECT  id,
18924     target,
18925     hold_type,
18926     CASE
18927         WHEN hold_type = 'T'
18928             THEN target
18929         WHEN hold_type = 'I'
18930             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18931         WHEN hold_type = 'V'
18932             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18933         WHEN hold_type IN ('C','R','F')
18934             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18935         WHEN hold_type = 'M'
18936             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18937     END AS bib_record
18938   FROM  action.hold_request ahr;
18939
18940 UPDATE  metabib.rec_descriptor
18941   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18942         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18943
18944 -- Change some ints to bigints:
18945
18946 ALTER TABLE container.biblio_record_entry_bucket_item
18947         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18948
18949 ALTER TABLE vandelay.queued_bib_record
18950         ALTER COLUMN imported_as SET DATA TYPE bigint;
18951
18952 ALTER TABLE action.hold_copy_map
18953         ALTER COLUMN id SET DATA TYPE bigint;
18954
18955 -- Make due times get pushed to 23:59:59 on insert OR update
18956 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18957 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18958
18959 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18960 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18961 WHERE NOT EXISTS (
18962     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18963 );  
18964
18965 -- '@@' auto-placeholder barcode support
18966 CREATE OR REPLACE FUNCTION asset.autogenerate_placeholder_barcode ( ) RETURNS TRIGGER AS $f$
18967 BEGIN
18968         IF NEW.barcode LIKE '@@%' THEN
18969                 NEW.barcode := '@@' || NEW.id;
18970         END IF;
18971         RETURN NEW;
18972 END;
18973 $f$ LANGUAGE PLPGSQL;
18974
18975 CREATE TRIGGER autogenerate_placeholder_barcode
18976         BEFORE INSERT OR UPDATE ON asset.copy
18977         FOR EACH ROW EXECUTE PROCEDURE asset.autogenerate_placeholder_barcode();
18978
18979 COMMIT;
18980
18981 BEGIN;
18982 -- stick loading the staging schema into a separate transaction, as
18983 -- libraries upgrading from earlier stock versions of Evergreen won't have
18984 -- it, but at least one library is known to have it in a pre-2.0 variant
18985 -- setup.
18986 CREATE SCHEMA staging;
18987
18988 CREATE TABLE staging.user_stage (
18989         row_id                  BIGSERIAL PRIMARY KEY,
18990         row_date                            TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
18991         usrname                 TEXT NOT NULL,
18992         profile                 TEXT,
18993         email                   TEXT,
18994         passwd                  TEXT,
18995         ident_type              INT DEFAULT 3,
18996         first_given_name        TEXT,
18997         second_given_name       TEXT,
18998         family_name             TEXT,
18999         day_phone               TEXT,
19000         evening_phone           TEXT,
19001         home_ou                 INT DEFAULT 2,
19002         dob                     TEXT,
19003         complete                BOOL DEFAULT FALSE
19004 );
19005
19006 CREATE TABLE staging.card_stage ( -- for new library barcodes
19007         row_id          BIGSERIAL PRIMARY KEY,
19008         row_date        TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
19009         usrname         TEXT NOT NULL,
19010         barcode         TEXT NOT NULL,
19011         complete        BOOL DEFAULT FALSE
19012 );
19013
19014 CREATE TABLE staging.mailing_address_stage (
19015         row_id          BIGSERIAL PRIMARY KEY,
19016         row_date            TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
19017         usrname         TEXT NOT NULL,  -- user's SIS barcode, for linking
19018         street1         TEXT,
19019         street2         TEXT,
19020         city            TEXT NOT NULL DEFAULT '',
19021         state           TEXT    NOT NULL DEFAULT 'OK',
19022         country         TEXT NOT NULL DEFAULT 'US',
19023         post_code       TEXT NOT NULL,
19024         complete        BOOL DEFAULT FALSE
19025 );
19026
19027 CREATE TABLE staging.billing_address_stage (
19028         LIKE staging.mailing_address_stage INCLUDING DEFAULTS
19029 );
19030
19031 ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
19032
19033 CREATE TABLE staging.statcat_stage (
19034         row_id          BIGSERIAL PRIMARY KEY,
19035         row_date    TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
19036         usrname         TEXT NOT NULL,
19037         statcat         TEXT NOT NULL, -- for things like 'Year of study'
19038         value           TEXT NOT NULL, -- and the value, such as 'Freshman'
19039         complete        BOOL DEFAULT FALSE
19040 );
19041
19042 COMMIT;
19043
19044 -- Some operations go outside of the transaction, because they may
19045 -- legitimately fail.
19046
19047 ALTER TABLE action.reservation_transit_copy
19048         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
19049                 REFERENCES booking.resource(id)
19050                 ON DELETE CASCADE
19051                 DEFERRABLE INITIALLY DEFERRED,
19052         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
19053                 REFERENCES booking.reservation(id)
19054                 ON DELETE SET NULL
19055                 DEFERRABLE INITIALLY DEFERRED;
19056
19057
19058 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
19059 \qecho doesn't exist; ignore those errors if they occur.
19060
19061 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
19062
19063 ALTER TABLE auditor.action_hold_request_history
19064 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
19065
19066 ALTER TABLE auditor.action_hold_request_history
19067 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
19068
19069 \qecho Outside of the transaction: adding indexes that may or may not exist.
19070 \qecho If any of these CREATE INDEX statements fails because the index already
19071 \qecho exists, ignore the failure.
19072
19073 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
19074 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
19075 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
19076 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
19077 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
19078 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
19079 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
19080 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
19081 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
19082 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
19083 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
19084 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
19085 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
19086 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
19087 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
19088 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
19089 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
19090 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
19091 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
19092 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
19093 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
19094 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
19095 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
19096 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
19097 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
19098 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
19099 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
19100 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
19101 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
19102 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
19103 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
19104 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
19105 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
19106
19107 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
19108
19109 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
19110
19111 \qecho If the following CREATE INDEX fails, It will be necessary to do some
19112 \qecho data cleanup as described in the comments.
19113
19114 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
19115     ON authority.record_entry (authority.normalize_heading(marc))
19116         WHERE deleted IS FALSE or deleted = FALSE;
19117
19118 -- If the unique index fails, uncomment the following to create
19119 -- a regular index that will help find the duplicates in a hurry:
19120 --CREATE INDEX by_heading_and_thesaurus
19121 --    ON authority.record_entry (authority.normalize_heading(marc))
19122 --    WHERE deleted IS FALSE or deleted = FALSE
19123 --;
19124
19125 -- Then find the duplicates like so to get an idea of how much
19126 -- pain you're looking at to clean things up:
19127 --SELECT id, authority.normalize_heading(marc)
19128 --    FROM authority.record_entry
19129 --    WHERE authority.normalize_heading(marc) IN (
19130 --        SELECT authority.normalize_heading(marc)
19131 --        FROM authority.record_entry
19132 --        GROUP BY authority.normalize_heading(marc)
19133 --        HAVING COUNT(*) > 1
19134 --    )
19135 --;
19136
19137 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
19138 -- statement succeeds, drop the temporary index to avoid unnecessary
19139 -- duplication:
19140 -- DROP INDEX authority.by_heading_and_thesaurus;
19141
19142 -- 0448.data.trigger.circ.staff_age_to_lost.sql
19143
19144 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
19145     (   'circ.staff_age_to_lost',
19146         'circ', 
19147         oils_i18n_gettext(
19148             'circ.staff_age_to_lost',
19149             'An overdue circulation should be aged to a Lost status.',
19150             'ath',
19151             'description'
19152         ), 
19153         TRUE
19154     )
19155 ;
19156
19157 INSERT INTO action_trigger.event_definition (
19158         id,
19159         active,
19160         owner,
19161         name,
19162         hook,
19163         validator,
19164         reactor,
19165         delay_field
19166     ) VALUES (
19167         36,
19168         FALSE,
19169         1,
19170         'circ.staff_age_to_lost',
19171         'circ.staff_age_to_lost',
19172         'CircIsOverdue',
19173         'MarkItemLost',
19174         'due_date'
19175     )
19176 ;
19177
19178 -- Speed up item-age browse axis (new books feed)
19179 CREATE INDEX cp_create_date  ON asset.copy (create_date);
19180
19181 -- Speed up call number browsing
19182 CREATE INDEX asset_call_number_label_sortkey_browse ON asset.call_number(oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib) WHERE deleted IS FALSE OR deleted = FALSE;
19183
19184 -- Add MARC::Charset->assume_unicode(1) to improve handling of Unicode characters
19185 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
19186 use strict;
19187 use MARC::Record;
19188 use MARC::File::XML (BinaryEncoding => 'UTF-8');
19189 use MARC::Charset;
19190 use Encode;
19191 use Unicode::Normalize;
19192
19193 MARC::Charset->assume_unicode(1);
19194
19195 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
19196 my $schema = $_TD->{table_schema};
19197 my $rec_id = $_TD->{new}{id};
19198
19199 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
19200 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
19201 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
19202     return;
19203 }
19204
19205 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
19206 my $ou_cni = 'EVRGRN';
19207
19208 my $owner;
19209 if ($schema eq 'serial') {
19210     $owner = $_TD->{new}{owning_lib};
19211 } else {
19212     # are.owner and bre.owner can be null, so fall back to the consortial setting
19213     $owner = $_TD->{new}{owner} || 1;
19214 }
19215
19216 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
19217 if ($ous_rv->{processed}) {
19218     $ou_cni = $ous_rv->{rows}[0]->{value};
19219     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
19220 } else {
19221     # Fall back to the shortname of the OU if there was no OU setting
19222     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
19223     if ($ous_rv->{processed}) {
19224         $ou_cni = $ous_rv->{rows}[0]->{shortname};
19225     }
19226 }
19227
19228 my ($create, $munge) = (0, 0);
19229
19230 my @scns = $record->field('035');
19231
19232 foreach my $id_field ('001', '003') {
19233     my $spec_value;
19234     my @controls = $record->field($id_field);
19235
19236     if ($id_field eq '001') {
19237         $spec_value = $rec_id;
19238     } else {
19239         $spec_value = $ou_cni;
19240     }
19241
19242     # Create the 001/003 if none exist
19243     if (scalar(@controls) == 1) {
19244         # Only one field; check to see if we need to munge it
19245         unless (grep $_->data() eq $spec_value, @controls) {
19246             $munge = 1;
19247         }
19248     } else {
19249         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
19250         foreach my $control (@controls) {
19251             unless ($control->data() eq $spec_value) {
19252                 $record->delete_field($control);
19253             }
19254         }
19255         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
19256         $create = 1;
19257     }
19258 }
19259
19260 # Now, if we need to munge the 001, we will first push the existing 001/003
19261 # into the 035; but if the record did not have one (and one only) 001 and 003
19262 # to begin with, skip this process
19263 if ($munge and not $create) {
19264     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
19265
19266     # Do not create duplicate 035 fields
19267     unless (grep $_->subfield('a') eq $scn, @scns) {
19268         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
19269     }
19270 }
19271
19272 # Set the 001/003 and update the MARC
19273 if ($create or $munge) {
19274     $record->field('001')->data($rec_id);
19275     $record->field('003')->data($ou_cni);
19276
19277     my $xml = $record->as_xml_record();
19278     $xml =~ s/\n//sgo;
19279     $xml =~ s/^<\?xml.+\?\s*>//go;
19280     $xml =~ s/>\s+</></go;
19281     $xml =~ s/\p{Cc}//go;
19282
19283     # Embed a version of OpenILS::Application::AppUtils->entityize()
19284     # to avoid having to set PERL5LIB for PostgreSQL as well
19285
19286     # If we are going to convert non-ASCII characters to XML entities,
19287     # we had better be dealing with a UTF8 string to begin with
19288     $xml = decode_utf8($xml);
19289
19290     $xml = NFC($xml);
19291
19292     # Convert raw ampersands to entities
19293     $xml =~ s/&(?!\S+;)/&amp;/gso;
19294
19295     # Convert Unicode characters to entities
19296     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
19297
19298     $xml =~ s/[\x00-\x1f]//go;
19299     $_TD->{new}{marc} = $xml;
19300
19301     return "MODIFY";
19302 }
19303
19304 return;
19305 $func$ LANGUAGE PLPERLU;
19306
19307 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
19308
19309     use MARC::Record;
19310     use MARC::File::XML (BinaryEncoding => 'UTF-8');
19311     use MARC::Charset;
19312
19313     MARC::Charset->assume_unicode(1);
19314
19315     my $xml = shift;
19316     my $r = MARC::Record->new_from_xml( $xml );
19317
19318     return undef unless ($r);
19319
19320     my $id = shift() || $r->subfield( '901' => 'c' );
19321     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
19322     return undef unless ($id); # We need an ID!
19323
19324     my $tmpl = MARC::Record->new();
19325     $tmpl->encoding( 'UTF-8' );
19326
19327     my @rule_fields;
19328     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
19329
19330         my $tag = $field->tag;
19331         my $i1 = $field->indicator(1);
19332         my $i2 = $field->indicator(2);
19333         my $sf = join '', map { $_->[0] } $field->subfields;
19334         my @data = map { @$_ } $field->subfields;
19335
19336         my @replace_them;
19337
19338         # Map the authority field to bib fields it can control.
19339         if ($tag >= 100 and $tag <= 111) {       # names
19340             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
19341         } elsif ($tag eq '130') {                # uniform title
19342             @replace_them = qw/130 240 440 730 830/;
19343         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
19344             @replace_them = ($tag + 500);
19345         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
19346             @replace_them = qw/100 400 600 700 800 110 410 610 710 810 111 411 611 711 811 130 240 440 730 830 650 651 655/;
19347         } else {
19348             next;
19349         }
19350
19351         # Dummy up the bib-side data
19352         $tmpl->append_fields(
19353             map {
19354                 MARC::Field->new( $_, $i1, $i2, @data )
19355             } @replace_them
19356         );
19357
19358         # Construct some 'replace' rules
19359         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
19360     }
19361
19362     # Insert the replace rules into the template
19363     $tmpl->append_fields(
19364         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
19365     );
19366
19367     $xml = $tmpl->as_xml_record;
19368     $xml =~ s/^<\?.+?\?>$//mo;
19369     $xml =~ s/\n//sgo;
19370     $xml =~ s/>\s+</></sgo;
19371
19372     return $xml;
19373
19374 $func$ LANGUAGE PLPERLU;
19375
19376 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
19377
19378     use MARC::Record;
19379     use MARC::File::XML (BinaryEncoding => 'UTF-8');
19380     use MARC::Charset;
19381     use strict;
19382
19383     MARC::Charset->assume_unicode(1);
19384
19385     my $target_xml = shift;
19386     my $source_xml = shift;
19387     my $field_spec = shift;
19388     my $force_add = shift || 0;
19389
19390     my $target_r = MARC::Record->new_from_xml( $target_xml );
19391     my $source_r = MARC::Record->new_from_xml( $source_xml );
19392
19393     return $target_xml unless ($target_r && $source_r);
19394
19395     my @field_list = split(',', $field_spec);
19396
19397     my %fields;
19398     for my $f (@field_list) {
19399         $f =~ s/^\s*//; $f =~ s/\s*$//;
19400         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
19401             my $field = $1;
19402             $field =~ s/\s+//;
19403             my $sf = $2;
19404             $sf =~ s/\s+//;
19405             my $match = $3;
19406             $match =~ s/^\s*//; $match =~ s/\s*$//;
19407             $fields{$field} = { sf => [ split('', $sf) ] };
19408             if ($match) {
19409                 my ($msf,$mre) = split('~', $match);
19410                 if (length($msf) > 0 and length($mre) > 0) {
19411                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
19412                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
19413                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
19414                 }
19415             }
19416         }
19417     }
19418
19419     for my $f ( keys %fields) {
19420         if ( @{$fields{$f}{sf}} ) {
19421             for my $from_field ($source_r->field( $f )) {
19422                 my @tos = $target_r->field( $f );
19423                 if (!@tos) {
19424                     next if (exists($fields{$f}{match}) and !$force_add);
19425                     my @new_fields = map { $_->clone } $source_r->field( $f );
19426                     $target_r->insert_fields_ordered( @new_fields );
19427                 } else {
19428                     for my $to_field (@tos) {
19429                         if (exists($fields{$f}{match})) {
19430                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
19431                         }
19432                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
19433                         $to_field->add_subfields( @new_sf );
19434                     }
19435                 }
19436             }
19437         } else {
19438             my @new_fields = map { $_->clone } $source_r->field( $f );
19439             $target_r->insert_fields_ordered( @new_fields );
19440         }
19441     }
19442
19443     $target_xml = $target_r->as_xml_record;
19444     $target_xml =~ s/^<\?.+?\?>$//mo;
19445     $target_xml =~ s/\n//sgo;
19446     $target_xml =~ s/>\s+</></sgo;
19447
19448     return $target_xml;
19449
19450 $_$ LANGUAGE PLPERLU;
19451
19452 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
19453
19454     use MARC::Record;
19455     use MARC::File::XML (BinaryEncoding => 'UTF-8');
19456     use MARC::Charset;
19457     use strict;
19458
19459     MARC::Charset->assume_unicode(1);
19460
19461     my $xml = shift;
19462     my $r = MARC::Record->new_from_xml( $xml );
19463
19464     return $xml unless ($r);
19465
19466     my $field_spec = shift;
19467     my @field_list = split(',', $field_spec);
19468
19469     my %fields;
19470     for my $f (@field_list) {
19471         $f =~ s/^\s*//; $f =~ s/\s*$//;
19472         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
19473             my $field = $1;
19474             $field =~ s/\s+//;
19475             my $sf = $2;
19476             $sf =~ s/\s+//;
19477             my $match = $3;
19478             $match =~ s/^\s*//; $match =~ s/\s*$//;
19479             $fields{$field} = { sf => [ split('', $sf) ] };
19480             if ($match) {
19481                 my ($msf,$mre) = split('~', $match);
19482                 if (length($msf) > 0 and length($mre) > 0) {
19483                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
19484                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
19485                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
19486                 }
19487             }
19488         }
19489     }
19490
19491     for my $f ( keys %fields) {
19492         for my $to_field ($r->field( $f )) {
19493             if (exists($fields{$f}{match})) {
19494                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
19495             }
19496
19497             if ( @{$fields{$f}{sf}} ) {
19498                 $to_field->delete_subfield(code => $fields{$f}{sf});
19499             } else {
19500                 $r->delete_field( $to_field );
19501             }
19502         }
19503     }
19504
19505     $xml = $r->as_xml_record;
19506     $xml =~ s/^<\?.+?\?>$//mo;
19507     $xml =~ s/\n//sgo;
19508     $xml =~ s/>\s+</></sgo;
19509
19510     return $xml;
19511
19512 $_$ LANGUAGE PLPERLU;
19513
19514 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
19515
19516 use MARC::Record;
19517 use MARC::File::XML (BinaryEncoding => 'UTF-8');
19518 use MARC::Charset;
19519
19520 MARC::Charset->assume_unicode(1);
19521
19522 my $xml = shift;
19523 my $r = MARC::Record->new_from_xml( $xml );
19524
19525 return_next( { tag => 'LDR', value => $r->leader } );
19526
19527 for my $f ( $r->fields ) {
19528         if ($f->is_control_field) {
19529                 return_next({ tag => $f->tag, value => $f->data });
19530         } else {
19531                 for my $s ($f->subfields) {
19532                         return_next({
19533                                 tag      => $f->tag,
19534                                 ind1     => $f->indicator(1),
19535                                 ind2     => $f->indicator(2),
19536                                 subfield => $s->[0],
19537                                 value    => $s->[1]
19538                         });
19539
19540                         if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
19541                                 my $trim = $f->indicator(2) || 0;
19542                                 return_next({
19543                                         tag      => 'tnf',
19544                                         ind1     => $f->indicator(1),
19545                                         ind2     => $f->indicator(2),
19546                                         subfield => 'a',
19547                                         value    => substr( $s->[1], $trim )
19548                                 });
19549                         }
19550                 }
19551         }
19552 }
19553
19554 return undef;
19555
19556 $func$ LANGUAGE PLPERLU;
19557
19558 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
19559
19560 use MARC::Record;
19561 use MARC::File::XML (BinaryEncoding => 'UTF-8');
19562 use MARC::Charset;
19563
19564 MARC::Charset->assume_unicode(1);
19565
19566 my $xml = shift;
19567 my $r = MARC::Record->new_from_xml( $xml );
19568
19569 return_next( { tag => 'LDR', value => $r->leader } );
19570
19571 for my $f ( $r->fields ) {
19572     if ($f->is_control_field) {
19573         return_next({ tag => $f->tag, value => $f->data });
19574     } else {
19575         for my $s ($f->subfields) {
19576             return_next({
19577                 tag      => $f->tag,
19578                 ind1     => $f->indicator(1),
19579                 ind2     => $f->indicator(2),
19580                 subfield => $s->[0],
19581                 value    => $s->[1]
19582             });
19583
19584         }
19585     }
19586 }
19587
19588 return undef;
19589
19590 $func$ LANGUAGE PLPERLU;
19591
19592 \qecho Rewriting authority records to include a 901$c, so that
19593 \qecho they can be used to control bibs.  This may take a while...
19594
19595 UPDATE authority.record_entry SET active = active;
19596
19597 \qecho Upgrade script completed.
19598 \qecho But wait, there's more: please run reingest-1.6-2.0.pl
19599 \qecho in order to create an SQL script to run to partially reindex 
19600 \qecho the bib records; this is required to make the new facet
19601 \qecho sidebar in OPAC search results work and to upgrade the keyword 
19602 \qecho indexes to use the revised NACO normalization routine.