]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Per Robert Soulliere, it can be necessary in some cases to clean out bad
[Evergreen.git] / Open-ILS / src / sql / Pg / 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         ADD CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record);
114
115 -- Now upgrade permission.perm_list.  This is fairly complicated.
116
117 -- Add ON UPDATE CASCADE to some foreign keys so that, when we renumber the
118 -- permissions, the dependents will follow and stay in sync:
119
120 ALTER TABLE permission.grp_perm_map ADD CONSTRAINT grp_perm_map_perm_fkey FOREIGN KEY (perm)
121     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
122
123 ALTER TABLE permission.usr_perm_map ADD CONSTRAINT usr_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_object_perm_map ADD CONSTRAINT usr_object_perm_map_perm_fkey FOREIGN KEY (perm)
127     REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
128
129 UPDATE permission.perm_list
130     SET code = 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow'
131     WHERE code = 'UPDATE_ORG_UNIT_SETTING.global.credit.allow';
132
133 -- The following UPDATES were originally in an individual upgrade script, but should
134 -- no longer be necessary now that the foreign key has an ON UPDATE CASCADE clause.
135 -- We retain the UPDATES here, commented out, as historical relics.
136
137 -- UPDATE permission.grp_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
138 -- UPDATE permission.usr_perm_map SET perm = perm + 1000 WHERE perm NOT IN ( SELECT id FROM permission.perm_list );
139
140 -- Spelling correction
141 UPDATE permission.perm_list SET code = 'ADMIN_RECURRING_FINE_RULE' WHERE code = 'ADMIN_RECURING_FINE_RULE';
142
143 -- Now we engage in a Great Renumbering of the permissions in permission.perm_list,
144 -- in order to clean up accumulated cruft.
145
146 -- The first step is to establish some triggers so that, when we change the id of a permission,
147 -- the associated translations are updated accordingly.
148
149 CREATE OR REPLACE FUNCTION oils_i18n_update_apply(old_ident TEXT, new_ident TEXT, hint TEXT) RETURNS VOID AS $_$
150 BEGIN
151
152     EXECUTE $$
153         UPDATE  config.i18n_core
154           SET   identity_value = $$ || quote_literal( new_ident ) || $$ 
155           WHERE fq_field LIKE '$$ || hint || $$.%' 
156                 AND identity_value = $$ || quote_literal( old_ident ) || $$;$$;
157
158     RETURN;
159
160 END;
161 $_$ LANGUAGE PLPGSQL;
162
163 CREATE OR REPLACE FUNCTION oils_i18n_id_tracking(/* hint */) RETURNS TRIGGER AS $_$
164 BEGIN
165     PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
166     RETURN NEW;
167 END;
168 $_$ LANGUAGE PLPGSQL;
169
170 CREATE OR REPLACE FUNCTION oils_i18n_code_tracking(/* hint */) RETURNS TRIGGER AS $_$
171 BEGIN
172     PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
173     RETURN NEW;
174 END;
175 $_$ LANGUAGE PLPGSQL;
176
177
178 CREATE TRIGGER maintain_perm_i18n_tgr
179     AFTER UPDATE ON permission.perm_list
180     FOR EACH ROW EXECUTE PROCEDURE oils_i18n_id_tracking('ppl');
181
182 -- Next, create a new table as a convenience for sloshing data back and forth,
183 -- and for recording which permission went where.  It looks just like
184 -- permission.perm_list, but with two extra columns: one for the old id, and one to
185 -- distinguish between predefined permissions and non-predefined permissions.
186
187 -- This table is, in effect, a temporary table, because we can drop it once the
188 -- upgrade is complete.  It is not technically temporary as far as PostgreSQL is
189 -- concerned, because we don't want it to disappear at the end of the session.
190 -- We keep it around so that we have a map showing the old id and the new id for
191 -- each permission.  However there is no IDL entry for it, nor is it defined
192 -- in the base sql files.
193
194 CREATE TABLE permission.temp_perm (
195         id          INT        PRIMARY KEY,
196         code        TEXT       UNIQUE,
197         description TEXT,
198         old_id      INT,
199         predefined  BOOL       NOT NULL DEFAULT TRUE
200 );
201
202 -- Populate the temp table with a definitive set of predefined permissions,
203 -- hard-coding the ids.
204
205 -- The first set of permissions is derived from the database, as loaded in a
206 -- loaded 1.6.1 database, plus a few changes previously applied in this upgrade
207 -- script.  The second set is derived from the IDL -- permissions that are referenced
208 -- in <permacrud> elements but not defined in the database.
209
210 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( -1, 'EVERYTHING',
211      '' );
212 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 1, 'OPAC_LOGIN',
213      'Allow a user to log in to the OPAC' );
214 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 2, 'STAFF_LOGIN',
215      'Allow a user to log in to the staff client' );
216 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 3, 'MR_HOLDS',
217      'Allow a user to create a metarecord holds' );
218 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 4, 'TITLE_HOLDS',
219      'Allow a user to place a hold at the title level' );
220 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 5, 'VOLUME_HOLDS',
221      'Allow a user to place a volume level hold' );
222 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 6, 'COPY_HOLDS',
223      'Allow a user to place a hold on a specific copy' );
224 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 7, 'REQUEST_HOLDS',
225      '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)' );
226 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 8, 'REQUEST_HOLDS_OVERRIDE',
227      '* no longer applicable' );
228 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 9, 'VIEW_HOLD',
229      'Allow a user to view another user''s holds' );
230 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 10, 'DELETE_HOLDS',
231      '* no longer applicable' );
232 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 11, 'UPDATE_HOLD',
233      'Allow a user to update another user''s hold' );
234 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 12, 'RENEW_CIRC',
235      'Allow a user to renew items' );
236 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 13, 'VIEW_USER_FINES_SUMMARY',
237      'Allow a user to view bill details' );
238 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 14, 'VIEW_USER_TRANSACTIONS',
239      'Allow a user to see another user''s grocery or circulation transactions in the Bills Interface; duplicate of VIEW_TRANSACTION' );
240 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 15, 'UPDATE_MARC',
241      'Allow a user to edit a MARC record' );
242 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 16, 'CREATE_MARC',
243      'Allow a user to create new MARC records' );
244 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 17, 'IMPORT_MARC',
245      'Allow a user to import a MARC record via the Z39.50 interface' );
246 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 18, 'CREATE_VOLUME',
247      'Allow a user to create a volume' );
248 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 19, 'UPDATE_VOLUME',
249      '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.' );
250 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 20, 'DELETE_VOLUME',
251      'Allow a user to delete a volume' );
252 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 21, 'CREATE_COPY',
253      'Allow a user to create a new copy object' );
254 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 22, 'UPDATE_COPY',
255      'Allow a user to edit a copy' );
256 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 23, 'DELETE_COPY',
257      'Allow a user to delete a copy' );
258 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 24, 'RENEW_HOLD_OVERRIDE',
259      'Allow a user to continue to renew an item even if it is required for a hold' );
260 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 25, 'CREATE_USER',
261      'Allow a user to create another user' );
262 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 26, 'UPDATE_USER',
263      'Allow a user to edit a user''s record' );
264 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 27, 'DELETE_USER',
265      'Allow a user to mark a user as deleted' );
266 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 28, 'VIEW_USER',
267      'Allow a user to view another user''s Patron Record' );
268 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 29, 'COPY_CHECKIN',
269      'Allow a user to check in a copy' );
270 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 30, 'CREATE_TRANSIT',
271      'Allow a user to place an item in transit' );
272 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 31, 'VIEW_PERMISSION',
273      'Allow a user to view user permissions within the user permissions editor' );
274 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 32, 'CHECKIN_BYPASS_HOLD_FULFILL',
275      '* no longer applicable' );
276 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 33, 'CREATE_PAYMENT',
277      'Allow a user to record payments in the Billing Interface' );
278 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 34, 'SET_CIRC_LOST',
279      'Allow a user to mark an item as ''lost''' );
280 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 35, 'SET_CIRC_MISSING',
281      'Allow a user to mark an item as ''missing''' );
282 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 36, 'SET_CIRC_CLAIMS_RETURNED',
283      'Allow a user to mark an item as ''claims returned''' );
284 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 37, 'CREATE_TRANSACTION',
285      'Allow a user to create a new billable transaction' );
286 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 38, 'VIEW_TRANSACTION',
287      'Allow a user may view another user''s transactions' );
288 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 39, 'CREATE_BILL',
289      'Allow a user to create a new bill on a transaction' );
290 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 40, 'VIEW_CONTAINER',
291      'Allow a user to view another user''s containers (buckets)' );
292 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 41, 'CREATE_CONTAINER',
293      'Allow a user to create a new container for another user' );
294 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 42, 'UPDATE_ORG_UNIT',
295      'Allow a user to change the settings for an organization unit' );
296 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 43, 'VIEW_CIRCULATIONS',
297      'Allow a user to see what another user has checked out' );
298 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 44, 'DELETE_CONTAINER',
299      'Allow a user to delete another user''s container' );
300 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 45, 'CREATE_CONTAINER_ITEM',
301      'Allow a user to create a container item for another user' );
302 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 46, 'CREATE_USER_GROUP_LINK',
303      'Allow a user to add other users to permission groups' );
304 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 47, 'REMOVE_USER_GROUP_LINK',
305      'Allow a user to remove other users from permission groups' );
306 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 48, 'VIEW_PERM_GROUPS',
307      'Allow a user to view other users'' permission groups' );
308 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 49, 'VIEW_PERMIT_CHECKOUT',
309      'Allow a user to determine whether another user can check out an item' );
310 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 50, 'UPDATE_BATCH_COPY',
311      'Allow a user to edit copies in batch' );
312 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 51, 'CREATE_PATRON_STAT_CAT',
313      'User may create a new patron statistical category' );
314 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 52, 'CREATE_COPY_STAT_CAT',
315      'User may create a copy statistical category' );
316 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 53, 'CREATE_PATRON_STAT_CAT_ENTRY',
317      'User may create an entry in a patron statistical category' );
318 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 54, 'CREATE_COPY_STAT_CAT_ENTRY',
319      'User may create an entry in a copy statistical category' );
320 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 55, 'UPDATE_PATRON_STAT_CAT',
321      'User may update a patron statistical category' );
322 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 56, 'UPDATE_COPY_STAT_CAT',
323      'User may update a copy statistical category' );
324 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 57, 'UPDATE_PATRON_STAT_CAT_ENTRY',
325      'User may update an entry in a patron statistical category' );
326 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 58, 'UPDATE_COPY_STAT_CAT_ENTRY',
327      'User may update an entry in a copy statistical category' );
328 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 59, 'CREATE_PATRON_STAT_CAT_ENTRY_MAP',
329      'User may link another user to an entry in a statistical category' );
330 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 60, 'CREATE_COPY_STAT_CAT_ENTRY_MAP',
331      'User may link a copy to an entry in a statistical category' );
332 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 61, 'DELETE_PATRON_STAT_CAT',
333      'User may delete a patron statistical category' );
334 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 62, 'DELETE_COPY_STAT_CAT',
335      'User may delete a copy statistical category' );
336 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 63, 'DELETE_PATRON_STAT_CAT_ENTRY',
337      'User may delete an entry from a patron statistical category' );
338 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 64, 'DELETE_COPY_STAT_CAT_ENTRY',
339      'User may delete an entry from a copy statistical category' );
340 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 65, 'DELETE_PATRON_STAT_CAT_ENTRY_MAP',
341      'User may delete a patron statistical category entry map' );
342 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 66, 'DELETE_COPY_STAT_CAT_ENTRY_MAP',
343      'User may delete a copy statistical category entry map' );
344 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 67, 'CREATE_NON_CAT_TYPE',
345      'Allow a user to create a new non-cataloged item type' );
346 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 68, 'UPDATE_NON_CAT_TYPE',
347      'Allow a user to update a non-cataloged item type' );
348 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 69, 'CREATE_IN_HOUSE_USE',
349      'Allow a user to create a new in-house-use ' );
350 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 70, 'COPY_CHECKOUT',
351      'Allow a user to check out a copy' );
352 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 71, 'CREATE_COPY_LOCATION',
353      'Allow a user to create a new copy location' );
354 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 72, 'UPDATE_COPY_LOCATION',
355      'Allow a user to update a copy location' );
356 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 73, 'DELETE_COPY_LOCATION',
357      'Allow a user to delete a copy location' );
358 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 74, 'CREATE_COPY_TRANSIT',
359      'Allow a user to create a transit_copy object for transiting a copy' );
360 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 75, 'COPY_TRANSIT_RECEIVE',
361      'Allow a user to close out a transit on a copy' );
362 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 76, 'VIEW_HOLD_PERMIT',
363      'Allow a user to see if another user has permission to place a hold on a given copy' );
364 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 77, 'VIEW_COPY_CHECKOUT_HISTORY',
365      'Allow a user to view which users have checked out a given copy' );
366 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 78, 'REMOTE_Z3950_QUERY',
367      'Allow a user to perform Z39.50 queries against remote servers' );
368 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 79, 'REGISTER_WORKSTATION',
369      'Allow a user to register a new workstation' );
370 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 80, 'VIEW_COPY_NOTES',
371      'Allow a user to view all notes attached to a copy' );
372 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 81, 'VIEW_VOLUME_NOTES',
373      'Allow a user to view all notes attached to a volume' );
374 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 82, 'VIEW_TITLE_NOTES',
375      'Allow a user to view all notes attached to a title' );
376 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 83, 'CREATE_COPY_NOTE',
377      'Allow a user to create a new copy note' );
378 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 84, 'CREATE_VOLUME_NOTE',
379      'Allow a user to create a new volume note' );
380 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 85, 'CREATE_TITLE_NOTE',
381      'Allow a user to create a new title note' );
382 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 86, 'DELETE_COPY_NOTE',
383      'Allow a user to delete another user''s copy notes' );
384 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 87, 'DELETE_VOLUME_NOTE',
385      'Allow a user to delete another user''s volume note' );
386 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 88, 'DELETE_TITLE_NOTE',
387      'Allow a user to delete another user''s title note' );
388 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 89, 'UPDATE_CONTAINER',
389      'Allow a user to update another user''s container' );
390 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 90, 'CREATE_MY_CONTAINER',
391      'Allow a user to create a container for themselves' );
392 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 91, 'VIEW_HOLD_NOTIFICATION',
393      'Allow a user to view notifications attached to a hold' );
394 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 92, 'CREATE_HOLD_NOTIFICATION',
395      'Allow a user to create new hold notifications' );
396 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 93, 'UPDATE_ORG_SETTING',
397      'Allow a user to update an organization unit setting' );
398 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 94, 'OFFLINE_UPLOAD',
399      'Allow a user to upload an offline script' );
400 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 95, 'OFFLINE_VIEW',
401      'Allow a user to view uploaded offline script information' );
402 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 96, 'OFFLINE_EXECUTE',
403      'Allow a user to execute an offline script batch' );
404 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 97, 'CIRC_OVERRIDE_DUE_DATE',
405      'Allow a user to change the due date on an item to any date' );
406 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 98, 'CIRC_PERMIT_OVERRIDE',
407      'Allow a user to bypass the circulation permit call for check out' );
408 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 99, 'COPY_IS_REFERENCE.override',
409      'Allow a user to override the copy_is_reference event' );
410 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 100, 'VOID_BILLING',
411      'Allow a user to void a bill' );
412 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 101, 'CIRC_CLAIMS_RETURNED.override',
413      'Allow a user to check in or check out an item that has a status of ''claims returned''' );
414 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 102, 'COPY_BAD_STATUS.override',
415      'Allow a user to check out an item in a non-circulatable status' );
416 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 103, 'COPY_ALERT_MESSAGE.override',
417      'Allow a user to check in/out an item that has an alert message' );
418 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 104, 'COPY_STATUS_LOST.override',
419      'Allow a user to remove the lost status from a copy' );
420 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 105, 'COPY_STATUS_MISSING.override',
421      'Allow a user to change the missing status on a copy' );
422 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 106, 'ABORT_TRANSIT',
423      'Allow a user to abort a copy transit if the user is at the transit destination or source' );
424 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 107, 'ABORT_REMOTE_TRANSIT',
425      'Allow a user to abort a copy transit if the user is not at the transit source or dest' );
426 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 108, 'VIEW_ZIP_DATA',
427      'Allow a user to query the ZIP code data method' );
428 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 109, 'CANCEL_HOLDS',
429      'Allow a user to cancel holds' );
430 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 110, 'CREATE_DUPLICATE_HOLDS',
431      'Allow a user to create duplicate holds (two or more holds on the same title)' );
432 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 111, 'actor.org_unit.closed_date.delete',
433      'Allow a user to remove a closed date interval for a given location' );
434 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 112, 'actor.org_unit.closed_date.update',
435      'Allow a user to update a closed date interval for a given location' );
436 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 113, 'actor.org_unit.closed_date.create',
437      'Allow a user to create a new closed date for a location' );
438 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 114, 'DELETE_NON_CAT_TYPE',
439      'Allow a user to delete a non cataloged type' );
440 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 115, 'money.collections_tracker.create',
441      'Allow a user to put someone into collections' );
442 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 116, 'money.collections_tracker.delete',
443      'Allow a user to remove someone from collections' );
444 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 117, 'BAR_PATRON',
445      'Allow a user to bar a patron' );
446 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 118, 'UNBAR_PATRON',
447      'Allow a user to un-bar a patron' );
448 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 119, 'DELETE_WORKSTATION',
449      'Allow a user to remove an existing workstation so a new one can replace it' );
450 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 120, 'group_application.user',
451      'Allow a user to add/remove users to/from the "User" group' );
452 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 121, 'group_application.user.patron',
453      'Allow a user to add/remove users to/from the "Patron" group' );
454 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 122, 'group_application.user.staff',
455      'Allow a user to add/remove users to/from the "Staff" group' );
456 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 123, 'group_application.user.staff.circ',
457      'Allow a user to add/remove users to/from the "Circulator" group' );
458 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 124, 'group_application.user.staff.cat',
459      'Allow a user to add/remove users to/from the "Cataloger" group' );
460 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 125, 'group_application.user.staff.admin.global_admin',
461      'Allow a user to add/remove users to/from the "GlobalAdmin" group' );
462 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 126, 'group_application.user.staff.admin.local_admin',
463      'Allow a user to add/remove users to/from the "LocalAdmin" group' );
464 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 127, 'group_application.user.staff.admin.lib_manager',
465      'Allow a user to add/remove users to/from the "LibraryManager" group' );
466 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 128, 'group_application.user.staff.cat.cat1',
467      'Allow a user to add/remove users to/from the "Cat1" group' );
468 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 129, 'group_application.user.staff.supercat',
469      'Allow a user to add/remove users to/from the "Supercat" group' );
470 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 130, 'group_application.user.sip_client',
471      'Allow a user to add/remove users to/from the "SIP-Client" group' );
472 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 131, 'group_application.user.vendor',
473      'Allow a user to add/remove users to/from the "Vendor" group' );
474 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 132, 'ITEM_AGE_PROTECTED.override',
475      'Allow a user to place a hold on an age-protected item' );
476 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 133, 'MAX_RENEWALS_REACHED.override',
477      'Allow a user to renew an item past the maximum renewal count' );
478 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 134, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override',
479      'Allow staff to override checkout count failure' );
480 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 135, 'PATRON_EXCEEDS_OVERDUE_COUNT.override',
481      'Allow staff to override overdue count failure' );
482 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 136, 'PATRON_EXCEEDS_FINES.override',
483      'Allow staff to override fine amount checkout failure' );
484 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 137, 'CIRC_EXCEEDS_COPY_RANGE.override',
485      'Allow staff to override circulation copy range failure' );
486 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 138, 'ITEM_ON_HOLDS_SHELF.override',
487      'Allow staff to override item on holds shelf failure' );
488 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 139, 'COPY_NOT_AVAILABLE.override',
489      'Allow staff to force checkout of Missing/Lost type items' );
490 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 140, 'HOLD_EXISTS.override',
491      'Allow a user to place multiple holds on a single title' );
492 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 141, 'RUN_REPORTS',
493      'Allow a user to run reports' );
494 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 142, 'SHARE_REPORT_FOLDER',
495      'Allow a user to share report his own folders' );
496 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 143, 'VIEW_REPORT_OUTPUT',
497      'Allow a user to view report output' );
498 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 144, 'COPY_CIRC_NOT_ALLOWED.override',
499      'Allow a user to checkout an item that is marked as non-circ' );
500 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 145, 'DELETE_CONTAINER_ITEM',
501      'Allow a user to delete an item out of another user''s container' );
502 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 146, 'ASSIGN_WORK_ORG_UNIT',
503      'Allow a staff member to define where another staff member has their permissions' );
504 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 147, 'CREATE_FUNDING_SOURCE',
505      'Allow a user to create a new funding source' );
506 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 148, 'DELETE_FUNDING_SOURCE',
507      'Allow a user to delete a funding source' );
508 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 149, 'VIEW_FUNDING_SOURCE',
509      'Allow a user to view a funding source' );
510 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 150, 'UPDATE_FUNDING_SOURCE',
511      'Allow a user to update a funding source' );
512 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 151, 'CREATE_FUND',
513      'Allow a user to create a new fund' );
514 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 152, 'DELETE_FUND',
515      'Allow a user to delete a fund' );
516 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 153, 'VIEW_FUND',
517      'Allow a user to view a fund' );
518 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 154, 'UPDATE_FUND',
519      'Allow a user to update a fund' );
520 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 155, 'CREATE_FUND_ALLOCATION',
521      'Allow a user to create a new fund allocation' );
522 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 156, 'DELETE_FUND_ALLOCATION',
523      'Allow a user to delete a fund allocation' );
524 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 157, 'VIEW_FUND_ALLOCATION',
525      'Allow a user to view a fund allocation' );
526 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 158, 'UPDATE_FUND_ALLOCATION',
527      'Allow a user to update a fund allocation' );
528 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 159, 'GENERAL_ACQ',
529      'Lowest level permission required to access the ACQ interface' );
530 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 160, 'CREATE_PROVIDER',
531      'Allow a user to create a new provider' );
532 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 161, 'DELETE_PROVIDER',
533      'Allow a user to delate a provider' );
534 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 162, 'VIEW_PROVIDER',
535      'Allow a user to view a provider' );
536 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 163, 'UPDATE_PROVIDER',
537      'Allow a user to update a provider' );
538 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 164, 'ADMIN_FUNDING_SOURCE',
539      'Allow a user to create/view/update/delete a funding source' );
540 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 165, 'ADMIN_FUND',
541      '(Deprecated) Allow a user to create/view/update/delete a fund' );
542 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 166, 'MANAGE_FUNDING_SOURCE',
543      'Allow a user to view/credit/debit a funding source' );
544 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 167, 'MANAGE_FUND',
545      'Allow a user to view/credit/debit a fund' );
546 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 168, 'CREATE_PICKLIST',
547      'Allows a user to create a picklist' );
548 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 169, 'ADMIN_PROVIDER',
549      'Allow a user to create/view/update/delete a provider' );
550 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 170, 'MANAGE_PROVIDER',
551      'Allow a user to view and purchase from a provider' );
552 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 171, 'VIEW_PICKLIST',
553      'Allow a user to view another users picklist' );
554 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 172, 'DELETE_RECORD',
555      'Allow a staff member to directly remove a bibliographic record' );
556 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 173, 'ADMIN_CURRENCY_TYPE',
557      'Allow a user to create/view/update/delete a currency_type' );
558 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 174, 'MARK_BAD_DEBT',
559      'Allow a user to mark a transaction as bad (unrecoverable) debt' );
560 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 175, 'VIEW_BILLING_TYPE',
561      'Allow a user to view billing types' );
562 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 176, 'MARK_ITEM_AVAILABLE',
563      'Allow a user to mark an item status as ''available''' );
564 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 177, 'MARK_ITEM_CHECKED_OUT',
565      'Allow a user to mark an item status as ''checked out''' );
566 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 178, 'MARK_ITEM_BINDERY',
567      'Allow a user to mark an item status as ''bindery''' );
568 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 179, 'MARK_ITEM_LOST',
569      'Allow a user to mark an item status as ''lost''' );
570 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 180, 'MARK_ITEM_MISSING',
571      'Allow a user to mark an item status as ''missing''' );
572 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 181, 'MARK_ITEM_IN_PROCESS',
573      'Allow a user to mark an item status as ''in process''' );
574 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 182, 'MARK_ITEM_IN_TRANSIT',
575      'Allow a user to mark an item status as ''in transit''' );
576 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 183, 'MARK_ITEM_RESHELVING',
577      'Allow a user to mark an item status as ''reshelving''' );
578 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 184, 'MARK_ITEM_ON_HOLDS_SHELF',
579      'Allow a user to mark an item status as ''on holds shelf''' );
580 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 185, 'MARK_ITEM_ON_ORDER',
581      'Allow a user to mark an item status as ''on order''' );
582 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 186, 'MARK_ITEM_ILL',
583      'Allow a user to mark an item status as ''inter-library loan''' );
584 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 187, 'group_application.user.staff.acq',
585      'Allows a user to add/remove/edit users in the "ACQ" group' );
586 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 188, 'CREATE_PURCHASE_ORDER',
587      'Allows a user to create a purchase order' );
588 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 189, 'VIEW_PURCHASE_ORDER',
589      'Allows a user to view a purchase order' );
590 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 190, 'IMPORT_ACQ_LINEITEM_BIB_RECORD',
591      'Allows a user to import a bib record from the acq staging area (on-order record) into the ILS bib data set' );
592 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 191, 'RECEIVE_PURCHASE_ORDER',
593      'Allows a user to mark a purchase order, lineitem, or individual copy as received' );
594 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 192, 'VIEW_ORG_SETTINGS',
595      'Allows a user to view all org settings at the specified level' );
596 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 193, 'CREATE_MFHD_RECORD',
597      'Allows a user to create a new MFHD record' );
598 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 194, 'UPDATE_MFHD_RECORD',
599      'Allows a user to update an MFHD record' );
600 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 195, 'DELETE_MFHD_RECORD',
601      'Allows a user to delete an MFHD record' );
602 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 196, 'ADMIN_ACQ_FUND',
603      'Allow a user to create/view/update/delete a fund' );
604 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 197, 'group_application.user.staff.acq_admin',
605      'Allows a user to add/remove/edit users in the "Acquisitions Administrators" group' );
606 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 198, 'SET_CIRC_CLAIMS_RETURNED.override',
607      'Allows staff to override the max claims returned value for a patron' );
608 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 199, 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
609      'Allows staff to manually change a patron''s claims returned count' );
610 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 200, 'UPDATE_BILL_NOTE',
611      'Allows staff to edit the note for a bill on a transaction' );
612 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 201, 'UPDATE_PAYMENT_NOTE',
613      'Allows staff to edit the note for a payment on a transaction' );
614 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 202, 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
615      'Allows staff to manually change a patron''s claims never checkout out count' );
616 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 203, 'ADMIN_COPY_LOCATION_ORDER',
617      'Allow a user to create/view/update/delete a copy location order' );
618 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 204, 'ASSIGN_GROUP_PERM',
619      '' );
620 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 205, 'CREATE_AUDIENCE',
621      '' );
622 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 206, 'CREATE_BIB_LEVEL',
623      '' );
624 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 207, 'CREATE_CIRC_DURATION',
625      '' );
626 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 208, 'CREATE_CIRC_MOD',
627      '' );
628 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 209, 'CREATE_COPY_STATUS',
629      '' );
630 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 210, 'CREATE_HOURS_OF_OPERATION',
631      '' );
632 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 211, 'CREATE_ITEM_FORM',
633      '' );
634 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 212, 'CREATE_ITEM_TYPE',
635      '' );
636 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 213, 'CREATE_LANGUAGE',
637      '' );
638 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 214, 'CREATE_LASSO',
639      '' );
640 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 215, 'CREATE_LASSO_MAP',
641      '' );
642 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 216, 'CREATE_LIT_FORM',
643      '' );
644 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 217, 'CREATE_METABIB_FIELD',
645      '' );
646 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 218, 'CREATE_NET_ACCESS_LEVEL',
647      '' );
648 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 219, 'CREATE_ORG_ADDRESS',
649      '' );
650 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 220, 'CREATE_ORG_TYPE',
651      '' );
652 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 221, 'CREATE_ORG_UNIT',
653      '' );
654 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 222, 'CREATE_ORG_UNIT_CLOSING',
655      '' );
656 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 223, 'CREATE_PERM',
657      '' );
658 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 224, 'CREATE_RELEVANCE_ADJUSTMENT',
659      '' );
660 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 225, 'CREATE_SURVEY',
661      '' );
662 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 226, 'CREATE_VR_FORMAT',
663      '' );
664 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 227, 'CREATE_XML_TRANSFORM',
665      '' );
666 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 228, 'DELETE_AUDIENCE',
667      '' );
668 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 229, 'DELETE_BIB_LEVEL',
669      '' );
670 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 230, 'DELETE_CIRC_DURATION',
671      '' );
672 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 231, 'DELETE_CIRC_MOD',
673      '' );
674 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 232, 'DELETE_COPY_STATUS',
675      '' );
676 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 233, 'DELETE_HOURS_OF_OPERATION',
677      '' );
678 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 234, 'DELETE_ITEM_FORM',
679      '' );
680 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 235, 'DELETE_ITEM_TYPE',
681      '' );
682 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 236, 'DELETE_LANGUAGE',
683      '' );
684 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 237, 'DELETE_LASSO',
685      '' );
686 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 238, 'DELETE_LASSO_MAP',
687      '' );
688 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 239, 'DELETE_LIT_FORM',
689      '' );
690 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 240, 'DELETE_METABIB_FIELD',
691      '' );
692 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 241, 'DELETE_NET_ACCESS_LEVEL',
693      '' );
694 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 242, 'DELETE_ORG_ADDRESS',
695      '' );
696 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 243, 'DELETE_ORG_TYPE',
697      '' );
698 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 244, 'DELETE_ORG_UNIT',
699      '' );
700 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 245, 'DELETE_ORG_UNIT_CLOSING',
701      '' );
702 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 246, 'DELETE_PERM',
703      '' );
704 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 247, 'DELETE_RELEVANCE_ADJUSTMENT',
705      '' );
706 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 248, 'DELETE_SURVEY',
707      '' );
708 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 249, 'DELETE_TRANSIT',
709      '' );
710 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 250, 'DELETE_VR_FORMAT',
711      '' );
712 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 251, 'DELETE_XML_TRANSFORM',
713      '' );
714 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 252, 'REMOVE_GROUP_PERM',
715      '' );
716 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 253, 'TRANSIT_COPY',
717      '' );
718 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 254, 'UPDATE_AUDIENCE',
719      '' );
720 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 255, 'UPDATE_BIB_LEVEL',
721      '' );
722 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 256, 'UPDATE_CIRC_DURATION',
723      '' );
724 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 257, 'UPDATE_CIRC_MOD',
725      '' );
726 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 258, 'UPDATE_COPY_NOTE',
727      '' );
728 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 259, 'UPDATE_COPY_STATUS',
729      '' );
730 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 260, 'UPDATE_GROUP_PERM',
731      '' );
732 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 261, 'UPDATE_HOURS_OF_OPERATION',
733      '' );
734 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 262, 'UPDATE_ITEM_FORM',
735      '' );
736 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 263, 'UPDATE_ITEM_TYPE',
737      '' );
738 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 264, 'UPDATE_LANGUAGE',
739      '' );
740 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 265, 'UPDATE_LASSO',
741      '' );
742 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 266, 'UPDATE_LASSO_MAP',
743      '' );
744 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 267, 'UPDATE_LIT_FORM',
745      '' );
746 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 268, 'UPDATE_METABIB_FIELD',
747      '' );
748 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 269, 'UPDATE_NET_ACCESS_LEVEL',
749      '' );
750 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 270, 'UPDATE_ORG_ADDRESS',
751      '' );
752 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 271, 'UPDATE_ORG_TYPE',
753      '' );
754 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 272, 'UPDATE_ORG_UNIT_CLOSING',
755      '' );
756 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 273, 'UPDATE_PERM',
757      '' );
758 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 274, 'UPDATE_RELEVANCE_ADJUSTMENT',
759      '' );
760 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 275, 'UPDATE_SURVEY',
761      '' );
762 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 276, 'UPDATE_TRANSIT',
763      '' );
764 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 277, 'UPDATE_VOLUME_NOTE',
765      '' );
766 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 278, 'UPDATE_VR_FORMAT',
767      '' );
768 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 279, 'UPDATE_XML_TRANSFORM',
769      '' );
770 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 280, 'MERGE_BIB_RECORDS',
771      '' );
772 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 281, 'UPDATE_PICKUP_LIB_FROM_HOLDS_SHELF',
773      '' );
774 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 282, 'CREATE_ACQ_FUNDING_SOURCE',
775      '' );
776 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 283, 'CREATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
777      '' );
778 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 284, 'CREATE_AUTHORITY_IMPORT_QUEUE',
779      '' );
780 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 285, 'CREATE_AUTHORITY_RECORD_NOTE',
781      '' );
782 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 286, 'CREATE_BIB_IMPORT_FIELD_DEF',
783      '' );
784 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 287, 'CREATE_BIB_IMPORT_QUEUE',
785      '' );
786 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 288, 'CREATE_LOCALE',
787      '' );
788 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 289, 'CREATE_MARC_CODE',
789      '' );
790 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 290, 'CREATE_TRANSLATION',
791      '' );
792 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 291, 'DELETE_ACQ_FUNDING_SOURCE',
793      '' );
794 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 292, 'DELETE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
795      '' );
796 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 293, 'DELETE_AUTHORITY_IMPORT_QUEUE',
797      '' );
798 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 294, 'DELETE_AUTHORITY_RECORD_NOTE',
799      '' );
800 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 295, 'DELETE_BIB_IMPORT_IMPORT_FIELD_DEF',
801      '' );
802 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 296, 'DELETE_BIB_IMPORT_QUEUE',
803      '' );
804 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 297, 'DELETE_LOCALE',
805      '' );
806 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 298, 'DELETE_MARC_CODE',
807      '' );
808 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 299, 'DELETE_TRANSLATION',
809      '' );
810 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 300, 'UPDATE_ACQ_FUNDING_SOURCE',
811      '' );
812 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 301, 'UPDATE_AUTHORITY_IMPORT_IMPORT_FIELD_DEF',
813      '' );
814 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 302, 'UPDATE_AUTHORITY_IMPORT_QUEUE',
815      '' );
816 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 303, 'UPDATE_AUTHORITY_RECORD_NOTE',
817      '' );
818 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 304, 'UPDATE_BIB_IMPORT_IMPORT_FIELD_DEF',
819      '' );
820 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 305, 'UPDATE_BIB_IMPORT_QUEUE',
821      '' );
822 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 306, 'UPDATE_LOCALE',
823      '' );
824 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 307, 'UPDATE_MARC_CODE',
825      '' );
826 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 308, 'UPDATE_TRANSLATION',
827      '' );
828 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 309, 'VIEW_ACQ_FUNDING_SOURCE',
829      '' );
830 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 310, 'VIEW_AUTHORITY_RECORD_NOTES',
831      '' );
832 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 311, 'CREATE_IMPORT_ITEM',
833      '' );
834 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 312, 'CREATE_IMPORT_ITEM_ATTR_DEF',
835      '' );
836 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 313, 'CREATE_IMPORT_TRASH_FIELD',
837      '' );
838 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 314, 'DELETE_IMPORT_ITEM',
839      '' );
840 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 315, 'DELETE_IMPORT_ITEM_ATTR_DEF',
841      '' );
842 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 316, 'DELETE_IMPORT_TRASH_FIELD',
843      '' );
844 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 317, 'UPDATE_IMPORT_ITEM',
845      '' );
846 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 318, 'UPDATE_IMPORT_ITEM_ATTR_DEF',
847      '' );
848 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 319, 'UPDATE_IMPORT_TRASH_FIELD',
849      '' );
850 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 320, 'UPDATE_ORG_UNIT_SETTING_ALL',
851      '' );
852 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 321, 'UPDATE_ORG_UNIT_SETTING.circ.lost_materials_processing_fee',
853      '' );
854 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 322, 'UPDATE_ORG_UNIT_SETTING.cat.default_item_price',
855      '' );
856 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 323, 'UPDATE_ORG_UNIT_SETTING.auth.opac_timeout',
857      '' );
858 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 324, 'UPDATE_ORG_UNIT_SETTING.auth.staff_timeout',
859      '' );
860 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 325, 'UPDATE_ORG_UNIT_SETTING.org.bounced_emails',
861      '' );
862 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 326, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_alert_interval',
863      '' );
864 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 327, 'UPDATE_ORG_UNIT_SETTING.circ.hold_expire_interval',
865      '' );
866 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 328, 'UPDATE_ORG_UNIT_SETTING.credit.payments.allow',
867      '' );
868 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 329, 'UPDATE_ORG_UNIT_SETTING.circ.void_overdue_on_lost',
869      '' );
870 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 330, 'UPDATE_ORG_UNIT_SETTING.circ.hold_stalling.soft',
871      '' );
872 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 331, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.hard',
873      '' );
874 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 332, 'UPDATE_ORG_UNIT_SETTING.circ.hold_boundary.soft',
875      '' );
876 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 333, 'UPDATE_ORG_UNIT_SETTING.opac.barcode_regex',
877      '' );
878 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 334, 'UPDATE_ORG_UNIT_SETTING.global.password_regex',
879      '' );
880 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 335, 'UPDATE_ORG_UNIT_SETTING.circ.item_checkout_history.max',
881      '' );
882 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 336, 'UPDATE_ORG_UNIT_SETTING.circ.reshelving_complete.interval',
883      '' );
884 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 337, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.patron_login_timeout',
885      '' );
886 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 338, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.alert_on_checkout_event',
887      '' );
888 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 339, 'UPDATE_ORG_UNIT_SETTING.circ.selfcheck.require_patron_password',
889      '' );
890 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 340, 'UPDATE_ORG_UNIT_SETTING.global.juvenile_age_threshold',
891      '' );
892 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 341, 'UPDATE_ORG_UNIT_SETTING.cat.bib.keep_on_empty',
893      '' );
894 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 342, 'UPDATE_ORG_UNIT_SETTING.cat.bib.alert_on_empty',
895      '' );
896 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 343, 'UPDATE_ORG_UNIT_SETTING.patron.password.use_phone',
897      '' );
898 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 344, 'HOLD_ITEM_CHECKED_OUT.override',
899      'Allows a user to place a hold on an item that they already have checked out' );
900 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 345, 'ADMIN_ACQ_CANCEL_CAUSE',
901      'Allow a user to create/update/delete reasons for order cancellations' );
902 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 346, 'ACQ_XFER_MANUAL_DFUND_AMOUNT',
903      'Allow a user to transfer different amounts of money out of one fund and into another' );
904 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 347, 'OVERRIDE_HOLD_HAS_LOCAL_COPY',
905      'Allow a user to override the circ.holds.hold_has_copy_at.block setting' );
906 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 348, 'UPDATE_PICKUP_LIB_FROM_TRANSIT',
907      'Allow a user to change the pickup and transit destination for a captured hold item already in transit' );
908 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 349, 'COPY_NEEDED_FOR_HOLD.override',
909      'Allow a user to force renewal of an item that could fulfill a hold request' );
910 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 350, 'MERGE_AUTH_RECORDS',
911      'Allow a user to merge authority records together' );
912 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 351, 'ALLOW_ALT_TCN',
913      'Allows staff to import a record using an alternate TCN to avoid conflicts' );
914 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 352, 'ADMIN_TRIGGER_EVENT_DEF',
915      'Allow a user to administer trigger event definitions' );
916 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 353, 'ADMIN_TRIGGER_CLEANUP',
917      'Allow a user to create, delete, and update trigger cleanup entries' );
918 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 354, 'CREATE_TRIGGER_CLEANUP',
919      'Allow a user to create trigger cleanup entries' );
920 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 355, 'DELETE_TRIGGER_CLEANUP',
921      'Allow a user to delete trigger cleanup entries' );
922 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 356, 'UPDATE_TRIGGER_CLEANUP',
923      'Allow a user to update trigger cleanup entries' );
924 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 357, 'CREATE_TRIGGER_EVENT_DEF',
925      'Allow a user to create trigger event definitions' );
926 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 358, 'DELETE_TRIGGER_EVENT_DEF',
927      'Allow a user to delete trigger event definitions' );
928 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 359, 'UPDATE_TRIGGER_EVENT_DEF',
929      'Allow a user to update trigger event definitions' );
930 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 360, 'VIEW_TRIGGER_EVENT_DEF',
931      'Allow a user to view trigger event definitions' );
932 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 361, 'ADMIN_TRIGGER_HOOK',
933      'Allow a user to create, update, and delete trigger hooks' );
934 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 362, 'CREATE_TRIGGER_HOOK',
935      'Allow a user to create trigger hooks' );
936 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 363, 'DELETE_TRIGGER_HOOK',
937      'Allow a user to delete trigger hooks' );
938 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 364, 'UPDATE_TRIGGER_HOOK',
939      'Allow a user to update trigger hooks' );
940 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 365, 'ADMIN_TRIGGER_REACTOR',
941      'Allow a user to create, update, and delete trigger reactors' );
942 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 366, 'CREATE_TRIGGER_REACTOR',
943      'Allow a user to create trigger reactors' );
944 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 367, 'DELETE_TRIGGER_REACTOR',
945      'Allow a user to delete trigger reactors' );
946 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 368, 'UPDATE_TRIGGER_REACTOR',
947      'Allow a user to update trigger reactors' );
948 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 369, 'ADMIN_TRIGGER_TEMPLATE_OUTPUT',
949      'Allow a user to delete trigger template output' );
950 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 370, 'DELETE_TRIGGER_TEMPLATE_OUTPUT',
951      'Allow a user to delete trigger template output' );
952 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 371, 'ADMIN_TRIGGER_VALIDATOR',
953      'Allow a user to create, update, and delete trigger validators' );
954 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 372, 'CREATE_TRIGGER_VALIDATOR',
955      'Allow a user to create trigger validators' );
956 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 373, 'DELETE_TRIGGER_VALIDATOR',
957      'Allow a user to delete trigger validators' );
958 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 374, 'UPDATE_TRIGGER_VALIDATOR',
959      'Allow a user to update trigger validators' );
960 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 376, 'ADMIN_BOOKING_RESOURCE',
961      'Enables the user to create/update/delete booking resources' );
962 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 377, 'ADMIN_BOOKING_RESOURCE_TYPE',
963      'Enables the user to create/update/delete booking resource types' );
964 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 378, 'ADMIN_BOOKING_RESOURCE_ATTR',
965      'Enables the user to create/update/delete booking resource attributes' );
966 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 379, 'ADMIN_BOOKING_RESOURCE_ATTR_MAP',
967      'Enables the user to create/update/delete booking resource attribute maps' );
968 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 380, 'ADMIN_BOOKING_RESOURCE_ATTR_VALUE',
969      'Enables the user to create/update/delete booking resource attribute values' );
970 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 381, 'ADMIN_BOOKING_RESERVATION',
971      'Enables the user to create/update/delete booking reservations' );
972 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 382, 'ADMIN_BOOKING_RESERVATION_ATTR_VALUE_MAP',
973      'Enables the user to create/update/delete booking reservation attribute value maps' );
974 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 383, 'RETRIEVE_RESERVATION_PULL_LIST',
975      'Allows a user to retrieve a booking reservation pull list' );
976 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 384, 'CAPTURE_RESERVATION',
977      'Allows a user to capture booking reservations' );
978 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 385, 'UPDATE_RECORD',
979      '' );
980 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 386, 'UPDATE_ORG_UNIT_SETTING.circ.block_renews_for_holds',
981      '' );
982 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 387, 'MERGE_USERS',
983      'Allows user records to be merged' );
984 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 388, 'ISSUANCE_HOLDS',
985      'Allow a user to place holds on serials issuances' );
986 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 389, 'VIEW_CREDIT_CARD_PROCESSING',
987      'View org unit settings related to credit card processing' );
988 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 390, 'ADMIN_CREDIT_CARD_PROCESSING',
989      'Update org unit settings related to credit card processing' );
990 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 391, 'ADMIN_SERIAL_CAPTION_PATTERN',
991         'Create/update/delete serial caption and pattern objects' );
992 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 392, 'ADMIN_SERIAL_SUBSCRIPTION',
993         'Create/update/delete serial subscription objects' );
994 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 393, 'ADMIN_SERIAL_DISTRIBUTION',
995         'Create/update/delete serial distribution objects' );
996 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 394, 'ADMIN_SERIAL_STREAM',
997         'Create/update/delete serial stream objects' );
998 INSERT INTO permission.temp_perm ( id, code, description ) VALUES ( 395, 'RECEIVE_SERIAL',
999         'Receive serial items' );
1000
1001 -- Now for the permissions from the IDL.  We don't have descriptions for them.
1002
1003 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 396, 'ADMIN_ACQ_CLAIM' );
1004 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 397, 'ADMIN_ACQ_CLAIM_EVENT_TYPE' );
1005 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 398, 'ADMIN_ACQ_CLAIM_TYPE' );
1006 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 399, 'ADMIN_ACQ_DISTRIB_FORMULA' );
1007 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 400, 'ADMIN_ACQ_FISCAL_YEAR' );
1008 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 401, 'ADMIN_ACQ_FUND_ALLOCATION_PERCENT' );
1009 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 402, 'ADMIN_ACQ_FUND_TAG' );
1010 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 403, 'ADMIN_ACQ_LINEITEM_ALERT_TEXT' );
1011 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 404, 'ADMIN_AGE_PROTECT_RULE' );
1012 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 405, 'ADMIN_ASSET_COPY_TEMPLATE' );
1013 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 406, 'ADMIN_BOOKING_RESERVATION_ATTR_MAP' );
1014 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 407, 'ADMIN_CIRC_MATRIX_MATCHPOINT' );
1015 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 408, 'ADMIN_CIRC_MOD' );
1016 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 409, 'ADMIN_CLAIM_POLICY' );
1017 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 410, 'ADMIN_CONFIG_REMOTE_ACCOUNT' );
1018 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 411, 'ADMIN_FIELD_DOC' );
1019 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 412, 'ADMIN_GLOBAL_FLAG' );
1020 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 413, 'ADMIN_GROUP_PENALTY_THRESHOLD' );
1021 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 414, 'ADMIN_HOLD_CANCEL_CAUSE' );
1022 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 415, 'ADMIN_HOLD_MATRIX_MATCHPOINT' );
1023 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 416, 'ADMIN_IDENT_TYPE' );
1024 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 417, 'ADMIN_IMPORT_ITEM_ATTR_DEF' );
1025 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 418, 'ADMIN_INDEX_NORMALIZER' );
1026 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 419, 'ADMIN_INVOICE' );
1027 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 420, 'ADMIN_INVOICE_METHOD' );
1028 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 421, 'ADMIN_INVOICE_PAYMENT_METHOD' );
1029 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 422, 'ADMIN_LINEITEM_MARC_ATTR_DEF' );
1030 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 423, 'ADMIN_MARC_CODE' );
1031 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 424, 'ADMIN_MAX_FINE_RULE' );
1032 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 425, 'ADMIN_MERGE_PROFILE' );
1033 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 426, 'ADMIN_ORG_UNIT_SETTING_TYPE' );
1034 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 427, 'ADMIN_RECURRING_FINE_RULE' );
1035 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 428, 'ADMIN_STANDING_PENALTY' );
1036 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 429, 'ADMIN_SURVEY' );
1037 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 430, 'ADMIN_USER_REQUEST_TYPE' );
1038 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 431, 'ADMIN_USER_SETTING_GROUP' );
1039 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 432, 'ADMIN_USER_SETTING_TYPE' );
1040 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 433, 'ADMIN_Z3950_SOURCE' );
1041 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 434, 'CREATE_BIB_BTYPE' );
1042 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 435, 'CREATE_BIBLIO_FINGERPRINT' );
1043 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 436, 'CREATE_BIB_SOURCE' );
1044 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 437, 'CREATE_BILLING_TYPE' );
1045 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 438, 'CREATE_CN_BTYPE' );
1046 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 439, 'CREATE_COPY_BTYPE' );
1047 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 440, 'CREATE_INVOICE' );
1048 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 441, 'CREATE_INVOICE_ITEM_TYPE' );
1049 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 442, 'CREATE_INVOICE_METHOD' );
1050 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 443, 'CREATE_MERGE_PROFILE' );
1051 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 444, 'CREATE_METABIB_CLASS' );
1052 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 445, 'CREATE_METABIB_SEARCH_ALIAS' );
1053 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 446, 'CREATE_USER_BTYPE' );
1054 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 447, 'DELETE_BIB_BTYPE' );
1055 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 448, 'DELETE_BIBLIO_FINGERPRINT' );
1056 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 449, 'DELETE_BIB_SOURCE' );
1057 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 450, 'DELETE_BILLING_TYPE' );
1058 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 451, 'DELETE_CN_BTYPE' );
1059 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 452, 'DELETE_COPY_BTYPE' );
1060 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 453, 'DELETE_INVOICE_ITEM_TYPE' );
1061 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 454, 'DELETE_INVOICE_METHOD' );
1062 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 455, 'DELETE_MERGE_PROFILE' );
1063 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 456, 'DELETE_METABIB_CLASS' );
1064 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 457, 'DELETE_METABIB_SEARCH_ALIAS' );
1065 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 458, 'DELETE_USER_BTYPE' );
1066 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 459, 'MANAGE_CLAIM' );
1067 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 460, 'UPDATE_BIB_BTYPE' );
1068 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 461, 'UPDATE_BIBLIO_FINGERPRINT' );
1069 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 462, 'UPDATE_BIB_SOURCE' );
1070 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 463, 'UPDATE_BILLING_TYPE' );
1071 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 464, 'UPDATE_CN_BTYPE' );
1072 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 465, 'UPDATE_COPY_BTYPE' );
1073 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 466, 'UPDATE_INVOICE_ITEM_TYPE' );
1074 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 467, 'UPDATE_INVOICE_METHOD' );
1075 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 468, 'UPDATE_MERGE_PROFILE' );
1076 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 469, 'UPDATE_METABIB_CLASS' );
1077 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 470, 'UPDATE_METABIB_SEARCH_ALIAS' );
1078 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 471, 'UPDATE_USER_BTYPE' );
1079 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 472, 'user_request.create' );
1080 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 473, 'user_request.delete' );
1081 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 474, 'user_request.update' );
1082 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 475, 'user_request.view' );
1083 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 476, 'VIEW_ACQ_FUND_ALLOCATION_PERCENT' );
1084 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 477, 'VIEW_CIRC_MATRIX_MATCHPOINT' );
1085 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 478, 'VIEW_CLAIM' );
1086 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 479, 'VIEW_GROUP_PENALTY_THRESHOLD' );
1087 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 480, 'VIEW_HOLD_MATRIX_MATCHPOINT' );
1088 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 481, 'VIEW_INVOICE' );
1089 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 482, 'VIEW_MERGE_PROFILE' );
1090 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 483, 'VIEW_SERIAL_SUBSCRIPTION' );
1091 INSERT INTO permission.temp_perm ( id, code ) VALUES ( 484, 'VIEW_STANDING_PENALTY' );
1092
1093 -- For every permission in the temp_perm table that has a matching
1094 -- permission in the real table: record the original id.
1095
1096 UPDATE permission.temp_perm AS tp
1097 SET old_id =
1098         (
1099                 SELECT id
1100                 FROM permission.perm_list AS ppl
1101                 WHERE ppl.code = tp.code
1102         )
1103 WHERE code IN ( SELECT code FROM permission.perm_list );
1104
1105 -- Start juggling ids.
1106
1107 -- If any permissions have negative ids (with the special exception of -1),
1108 -- we need to move them into the positive range in order to avoid duplicate
1109 -- key problems (since we are going to use the negative range as a temporary
1110 -- staging area).
1111
1112 -- First, move any predefined permissions that have negative ids (again with
1113 -- the special exception of -1).  Temporarily give them positive ids based on
1114 -- the sequence.
1115
1116 UPDATE permission.perm_list
1117 SET id = NEXTVAL('permission.perm_list_id_seq'::regclass)
1118 WHERE id < -1
1119   AND code IN (SELECT code FROM permission.temp_perm);
1120
1121 -- Identify any non-predefined permissions whose ids are either negative
1122 -- or within the range (0-1000) reserved for predefined permissions.
1123 -- Assign them ids above 1000, based on the sequence.  Record the new
1124 -- ids in the temp_perm table.
1125
1126 INSERT INTO permission.temp_perm ( id, code, description, old_id, predefined )
1127 (
1128         SELECT NEXTVAL('permission.perm_list_id_seq'::regclass),
1129                 code, description, id, false
1130         FROM permission.perm_list
1131         WHERE  ( id < -1 OR id BETWEEN 0 AND 1000 )
1132         AND code NOT IN (SELECT code FROM permission.temp_perm)
1133 );
1134
1135 -- Now update the ids of those non-predefined permissions, using the
1136 -- values assigned in the previous step.
1137
1138 UPDATE permission.perm_list AS ppl
1139 SET id = (
1140                 SELECT id
1141                 FROM permission.temp_perm AS tp
1142                 WHERE tp.code = ppl.code
1143         )
1144 WHERE id IN ( SELECT old_id FROM permission.temp_perm WHERE NOT predefined );
1145
1146 -- Now the negative ids have been eliminated, except for -1.  Move all the
1147 -- predefined permissions temporarily into the negative range.
1148
1149 UPDATE permission.perm_list
1150 SET id = -1 - id
1151 WHERE id <> -1
1152 AND code IN ( SELECT code from permission.temp_perm WHERE predefined );
1153
1154 -- Apply the final ids to the existing predefined permissions.
1155
1156 UPDATE permission.perm_list AS ppl
1157 SET id =
1158         (
1159                 SELECT id
1160                 FROM permission.temp_perm AS tp
1161                 WHERE tp.code = ppl.code
1162         )
1163 WHERE
1164         id <> -1
1165         AND ppl.code IN
1166         (
1167                 SELECT code from permission.temp_perm
1168                 WHERE predefined
1169                 AND old_id IS NOT NULL
1170         );
1171
1172 -- If there are any predefined permissions that don't exist yet in
1173 -- permission.perm_list, insert them now.
1174
1175 INSERT INTO permission.perm_list ( id, code, description )
1176 (
1177         SELECT id, code, description
1178         FROM permission.temp_perm
1179         WHERE old_id IS NULL
1180 );
1181
1182 -- Reset the sequence to the lowest feasible value.  This may or may not
1183 -- accomplish anything, but it will do no harm.
1184
1185 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, GREATEST( 
1186         (SELECT MAX(id) FROM permission.perm_list), 1000 ));
1187
1188 -- If any permission lacks a description, use the code as a description.
1189 -- It's better than nothing.
1190
1191 UPDATE permission.perm_list
1192 SET description = code
1193 WHERE description IS NULL
1194    OR description = '';
1195
1196 -- Thus endeth the Great Renumbering.
1197
1198 -- Having massaged the permissions, massage the way they are assigned, by inserting
1199 -- rows into permission.grp_perm_map.  Some of these permissions may have already
1200 -- been assigned, so we insert the rows only if they aren't already there.
1201
1202 -- for backwards compat, give everyone the permission
1203 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1204     SELECT 1, id, 0, false FROM permission.perm_list AS perm
1205         WHERE code = 'HOLD_ITEM_CHECKED_OUT.override'
1206                 AND NOT EXISTS (
1207                         SELECT 1
1208                         FROM permission.grp_perm_map AS map
1209                         WHERE
1210                                 grp = 1
1211                                 AND map.perm = perm.id
1212                 );
1213
1214 -- Add trigger administration permissions to the Local System Administrator group.
1215 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1216     SELECT 10, id, 1, false FROM permission.perm_list AS perm
1217     WHERE (
1218                 perm.code LIKE 'ADMIN_TRIGGER%'
1219         OR perm.code LIKE 'CREATE_TRIGGER%'
1220         OR perm.code LIKE 'DELETE_TRIGGER%'
1221         OR perm.code LIKE 'UPDATE_TRIGGER%'
1222         ) AND NOT EXISTS (
1223                 SELECT 1
1224                 FROM permission.grp_perm_map AS map
1225                 WHERE
1226                         grp = 10
1227                         AND map.perm = perm.id
1228         );
1229
1230 -- View trigger permissions are required at a consortial level for initial setup
1231 -- (as before, only if the row doesn't already exist)
1232 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1233     SELECT 10, id, 0, false FROM permission.perm_list AS perm
1234         WHERE code LIKE 'VIEW_TRIGGER%'
1235                 AND NOT EXISTS (
1236                         SELECT 1
1237                         FROM permission.grp_perm_map AS map
1238                         WHERE
1239                                 grp = 10
1240                                 AND map.perm = perm.id
1241                 );
1242
1243 -- Permission for merging auth records may already be defined,
1244 -- so add it only if it isn't there.
1245 INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
1246     SELECT 4, id, 1, false FROM permission.perm_list AS perm
1247         WHERE code = 'MERGE_AUTH_RECORDS'
1248                 AND NOT EXISTS (
1249                         SELECT 1
1250                         FROM permission.grp_perm_map AS map
1251                         WHERE
1252                                 grp = 4
1253                                 AND map.perm = perm.id
1254                 );
1255
1256 -- Create a reference table as parent to both
1257 -- config.org_unit_setting_type and config_usr_setting_type
1258
1259 CREATE TABLE config.settings_group (
1260     name    TEXT PRIMARY KEY,
1261     label   TEXT UNIQUE NOT NULL -- I18N
1262 );
1263
1264 -- org_unit setting types
1265 CREATE TABLE config.org_unit_setting_type (
1266     name            TEXT    PRIMARY KEY,
1267     label           TEXT    UNIQUE NOT NULL,
1268     grp             TEXT    REFERENCES config.settings_group (name),
1269     description     TEXT,
1270     datatype        TEXT    NOT NULL DEFAULT 'string',
1271     fm_class        TEXT,
1272     view_perm       INT,
1273     update_perm     INT,
1274     --
1275     -- define valid datatypes
1276     --
1277     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1278     ( 'bool', 'integer', 'float', 'currency', 'interval',
1279       'date', 'string', 'object', 'array', 'link' ) ),
1280     --
1281     -- fm_class is meaningful only for 'link' datatype
1282     --
1283     CONSTRAINT coust_no_empty_link CHECK
1284     ( ( datatype =  'link' AND fm_class IS NOT NULL ) OR
1285       ( datatype <> 'link' AND fm_class IS NULL ) ),
1286         CONSTRAINT view_perm_fkey FOREIGN KEY (view_perm) REFERENCES permission.perm_list (id)
1287                 ON UPDATE CASCADE
1288                 ON DELETE RESTRICT
1289                 DEFERRABLE INITIALLY DEFERRED,
1290         CONSTRAINT update_perm_fkey FOREIGN KEY (update_perm) REFERENCES permission.perm_list (id)
1291                 ON UPDATE CASCADE
1292                 DEFERRABLE INITIALLY DEFERRED
1293 );
1294
1295 CREATE TABLE config.usr_setting_type (
1296
1297     name TEXT PRIMARY KEY,
1298     opac_visible BOOL NOT NULL DEFAULT FALSE,
1299     label TEXT UNIQUE NOT NULL,
1300     description TEXT,
1301     grp             TEXT    REFERENCES config.settings_group (name),
1302     datatype TEXT NOT NULL DEFAULT 'string',
1303     fm_class TEXT,
1304
1305     --
1306     -- define valid datatypes
1307     --
1308     CONSTRAINT coust_valid_datatype CHECK ( datatype IN
1309     ( 'bool', 'integer', 'float', 'currency', 'interval',
1310         'date', 'string', 'object', 'array', 'link' ) ),
1311
1312     --
1313     -- fm_class is meaningful only for 'link' datatype
1314     --
1315     CONSTRAINT coust_no_empty_link CHECK
1316     ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR
1317         ( datatype <> 'link' AND fm_class IS NULL ) )
1318
1319 );
1320
1321 --------------------------------------
1322 -- Seed data for org_unit_setting_type
1323 --------------------------------------
1324
1325 INSERT into config.org_unit_setting_type
1326 ( name, label, description, datatype ) VALUES
1327
1328 ( 'auth.opac_timeout',
1329   'OPAC Inactivity Timeout (in seconds)',
1330   null,
1331   'integer' ),
1332
1333 ( 'auth.staff_timeout',
1334   'Staff Login Inactivity Timeout (in seconds)',
1335   null,
1336   'integer' ),
1337
1338 ( 'circ.lost_materials_processing_fee',
1339   'Lost Materials Processing Fee',
1340   null,
1341   'currency' ),
1342
1343 ( 'cat.default_item_price',
1344   'Default Item Price',
1345   null,
1346   'currency' ),
1347
1348 ( 'org.bounced_emails',
1349   'Sending email address for patron notices',
1350   null,
1351   'string' ),
1352
1353 ( 'circ.hold_expire_alert_interval',
1354   'Holds: Expire Alert Interval',
1355   'Amount of time before a hold expires at which point the patron should be alerted',
1356   'interval' ),
1357
1358 ( 'circ.hold_expire_interval',
1359   'Holds: Expire Interval',
1360   'Amount of time after a hold is placed before the hold expires.  Example "100 days"',
1361   'interval' ),
1362
1363 ( 'credit.payments.allow',
1364   'Allow Credit Card Payments',
1365   'If enabled, patrons will be able to pay fines accrued at this location via credit card',
1366   'bool' ),
1367
1368 ( 'global.default_locale',
1369   'Global Default Locale',
1370   null,
1371   'string' ),
1372
1373 ( 'circ.void_overdue_on_lost',
1374   'Void overdue fines when items are marked lost',
1375   null,
1376   'bool' ),
1377
1378 ( 'circ.hold_stalling.soft',
1379   'Holds: Soft stalling interval',
1380   'How long to wait before allowing remote items to be opportunistically captured for a hold.  Example "5 days"',
1381   'interval' ),
1382
1383 ( 'circ.hold_stalling_hard',
1384   'Holds: Hard stalling interval',
1385   '',
1386   'interval' ),
1387
1388 ( 'circ.hold_boundary.hard',
1389   'Holds: Hard boundary',
1390   null,
1391   'integer' ),
1392
1393 ( 'circ.hold_boundary.soft',
1394   'Holds: Soft boundary',
1395   null,
1396   'integer' ),
1397
1398 ( 'opac.barcode_regex',
1399   'Patron barcode format',
1400   'Regular expression defining the patron barcode format',
1401   'string' ),
1402
1403 ( 'global.password_regex',
1404   'Password format',
1405   'Regular expression defining the password format',
1406   'string' ),
1407
1408 ( 'circ.item_checkout_history.max',
1409   'Maximum previous checkouts displayed',
1410   'This is the maximum number of previous circulations the staff client will display when investigating item details',
1411   'integer' ),
1412
1413 ( 'circ.reshelving_complete.interval',
1414   'Change reshelving status interval',
1415   'Amount of time to wait before changing an item from "reshelving" status to "available".  Examples: "1 day", "6 hours"',
1416   'interval' ),
1417
1418 ( 'circ.holds.default_estimated_wait_interval',
1419   'Holds: Default Estimated Wait',
1420   '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.',
1421   'interval' ),
1422
1423 ( 'circ.holds.min_estimated_wait_interval',
1424   'Holds: Minimum Estimated Wait',
1425   '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.',
1426   'interval' ),
1427
1428 ( 'circ.selfcheck.patron_login_timeout',
1429   'Selfcheck: Patron Login Timeout (in seconds)',
1430   'Number of seconds of inactivity before the patron is logged out of the selfcheck interface',
1431   'integer' ),
1432
1433 ( 'circ.selfcheck.alert.popup',
1434   'Selfcheck: Pop-up alert for errors',
1435   'If true, checkout/renewal errors will cause a pop-up window in addition to the on-screen message',
1436   'bool' ),
1437
1438 ( 'circ.selfcheck.require_patron_password',
1439   'Selfcheck: Require patron password',
1440   'If true, patrons will be required to enter their password in addition to their username/barcode to log into the selfcheck interface',
1441   'bool' ),
1442
1443 ( 'global.juvenile_age_threshold',
1444   'Juvenile Age Threshold',
1445   'The age at which a user is no long considered a juvenile.  For example, "18 years".',
1446   'interval' ),
1447
1448 ( 'cat.bib.keep_on_empty',
1449   'Retain empty bib records',
1450   'Retain a bib record even when all attached copies are deleted',
1451   'bool' ),
1452
1453 ( 'cat.bib.alert_on_empty',
1454   'Alert on empty bib records',
1455   'Alert staff when the last copy for a record is being deleted',
1456   'bool' ),
1457
1458 ( 'patron.password.use_phone',
1459   'Patron: password from phone #',
1460   'Use the last 4 digits of the patrons phone number as the default password when creating new users',
1461   'bool' ),
1462
1463 ( 'circ.charge_on_damaged',
1464   'Charge item price when marked damaged',
1465   'Charge item price when marked damaged',
1466   'bool' ),
1467
1468 ( 'circ.charge_lost_on_zero',
1469   'Charge lost on zero',
1470   '',
1471   'bool' ),
1472
1473 ( 'circ.damaged_item_processing_fee',
1474   'Charge processing fee for damaged items',
1475   'Charge processing fee for damaged items',
1476   'currency' ),
1477
1478 ( 'circ.void_lost_on_checkin',
1479   'Circ: Void lost item billing when returned',
1480   'Void lost item billing when returned',
1481   'bool' ),
1482
1483 ( 'circ.max_accept_return_of_lost',
1484   'Circ: Void lost max interval',
1485   'Items that have been lost this long will not result in voided billings when returned.  E.g. ''6 months''',
1486   'interval' ),
1487
1488 ( 'circ.void_lost_proc_fee_on_checkin',
1489   'Circ: Void processing fee on lost item return',
1490   'Void processing fee when lost item returned',
1491   'bool' ),
1492
1493 ( 'circ.restore_overdue_on_lost_return',
1494   'Circ: Restore overdues on lost item return',
1495   'Restore overdue fines on lost item return',
1496   'bool' ),
1497
1498 ( 'circ.lost_immediately_available',
1499   'Circ: Lost items usable on checkin',
1500   'Lost items are usable on checkin instead of going ''home'' first',
1501   'bool' ),
1502
1503 ( 'circ.holds_fifo',
1504   'Holds: FIFO',
1505   'Force holds to a more strict First-In, First-Out capture',
1506   'bool' ),
1507
1508 ( 'opac.allow_pending_address',
1509   'OPAC: Allow pending addresses',
1510   'If enabled, patrons can create and edit existing addresses.  Addresses are kept in a pending state until staff approves the changes',
1511   'bool' ),
1512
1513 ( 'ui.circ.show_billing_tab_on_bills',
1514   'Show billing tab first when bills are present',
1515   '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',
1516   'bool' ),
1517
1518 ( 'ui.general.idle_timeout',
1519     'GUI: Idle timeout',
1520     '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).',
1521     'integer' ),
1522
1523 ( 'ui.circ.in_house_use.entry_cap',
1524   'GUI: Record In-House Use: Maximum # of uses allowed per entry.',
1525   'The # of uses entry in the Record In-House Use interface may not exceed the value of this setting.',
1526   'integer' ),
1527
1528 ( 'ui.circ.in_house_use.entry_warn',
1529   'GUI: Record In-House Use: # of uses threshold for Are You Sure? dialog.',
1530   'In the Record In-House Use interface, a submission attempt will warn if the # of uses field exceeds the value of this setting.',
1531   'integer' ),
1532
1533 ( 'acq.default_circ_modifier',
1534   'Default circulation modifier',
1535   null,
1536   'string' ),
1537
1538 ( 'acq.tmp_barcode_prefix',
1539   'Temporary barcode prefix',
1540   null,
1541   'string' ),
1542
1543 ( 'acq.tmp_callnumber_prefix',
1544   'Temporary call number prefix',
1545   null,
1546   'string' ),
1547
1548 ( 'ui.circ.patron_summary.horizontal',
1549   'Patron circulation summary is horizontal',
1550   null,
1551   'bool' ),
1552
1553 ( 'ui.staff.require_initials',
1554   oils_i18n_gettext('ui.staff.require_initials', 'GUI: Require staff initials for entry/edit of item/patron/penalty notes/messages.', 'coust', 'label'),
1555   oils_i18n_gettext('ui.staff.require_initials', 'Appends staff initials and edit date into note content.', 'coust', 'description'),
1556   'bool' ),
1557
1558 ( 'ui.general.button_bar',
1559   'Button bar',
1560   null,
1561   'bool' ),
1562
1563 ( 'circ.hold_shelf_status_delay',
1564   'Hold Shelf Status Delay',
1565   '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.',
1566   'interval' ),
1567
1568 ( 'circ.patron_invalid_address_apply_penalty',
1569   'Invalid patron address penalty',
1570   'When set, if a patron address is set to invalid, a penalty is applied.',
1571   'bool' ),
1572
1573 ( 'circ.checkout_fills_related_hold',
1574   'Checkout Fills Related Hold',
1575   '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',
1576   'bool'),
1577
1578 ( 'circ.selfcheck.auto_override_checkout_events',
1579   'Selfcheck override events list',
1580   'List of checkout/renewal events that the selfcheck interface should automatically override instead instead of alerting and stopping the transaction',
1581   'array' ),
1582
1583 ( 'circ.staff_client.do_not_auto_attempt_print',
1584   'Disable Automatic Print Attempt Type List',
1585   '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).',
1586   'array' ),
1587
1588 ( 'ui.patron.default_inet_access_level',
1589   'Default level of patrons'' internet access',
1590   null,
1591   'integer' ),
1592
1593 ( 'circ.max_patron_claim_return_count',
1594     'Max Patron Claims Returned Count',
1595     'When this count is exceeded, a staff override is required to mark the item as claims returned',
1596     'integer' ),
1597
1598 ( 'circ.obscure_dob',
1599     'Obscure the Date of Birth field',
1600     '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.',
1601     'bool' ),
1602
1603 ( 'circ.auto_hide_patron_summary',
1604     'GUI: Toggle off the patron summary sidebar after first view.',
1605     'When true, the patron summary sidebar will collapse after a new patron sub-interface is selected.',
1606     'bool' ),
1607
1608 ( 'credit.processor.default',
1609     'Credit card processing: Name default credit processor',
1610     'This can be "AuthorizeNet", "PayPal" (for the Website Payment Pro API), or "PayflowPro".',
1611     'string' ),
1612
1613 ( 'credit.processor.authorizenet.enabled',
1614     'Credit card processing: AuthorizeNet enabled',
1615     '',
1616     'bool' ),
1617
1618 ( 'credit.processor.authorizenet.login',
1619     'Credit card processing: AuthorizeNet login',
1620     '',
1621     'string' ),
1622
1623 ( 'credit.processor.authorizenet.password',
1624     'Credit card processing: AuthorizeNet password',
1625     '',
1626     'string' ),
1627
1628 ( 'credit.processor.authorizenet.server',
1629     'Credit card processing: AuthorizeNet server',
1630     'Required if using a developer/test account with AuthorizeNet',
1631     'string' ),
1632
1633 ( 'credit.processor.authorizenet.testmode',
1634     'Credit card processing: AuthorizeNet test mode',
1635     '',
1636     'bool' ),
1637
1638 ( 'credit.processor.paypal.enabled',
1639     'Credit card processing: PayPal enabled',
1640     '',
1641     'bool' ),
1642 ( 'credit.processor.paypal.login',
1643     'Credit card processing: PayPal login',
1644     '',
1645     'string' ),
1646 ( 'credit.processor.paypal.password',
1647     'Credit card processing: PayPal password',
1648     '',
1649     'string' ),
1650 ( 'credit.processor.paypal.signature',
1651     'Credit card processing: PayPal signature',
1652     '',
1653     'string' ),
1654 ( 'credit.processor.paypal.testmode',
1655     'Credit card processing: PayPal test mode',
1656     '',
1657     'bool' ),
1658
1659 ( 'ui.admin.work_log.max_entries',
1660     oils_i18n_gettext('ui.admin.work_log.max_entries', 'GUI: Work Log: Maximum Actions Logged', 'coust', 'label'),
1661     oils_i18n_gettext('ui.admin.work_log.max_entries', 'Maximum entries for "Most Recent Staff Actions" section of the Work Log interface.', 'coust', 'description'),
1662   'interval' ),
1663
1664 ( 'ui.admin.patron_log.max_entries',
1665     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'GUI: Work Log: Maximum Patrons Logged', 'coust', 'label'),
1666     oils_i18n_gettext('ui.admin.patron_log.max_entries', 'Maximum entries for "Most Recently Affected Patrons..." section of the Work Log interface.', 'coust', 'description'),
1667   'interval' ),
1668
1669 ( 'lib.courier_code',
1670     oils_i18n_gettext('lib.courier_code', 'Courier Code', 'coust', 'label'),
1671     oils_i18n_gettext('lib.courier_code', 'Courier Code for the library.  Available in transit slip templates as the %courier_code% macro.', 'coust', 'description'),
1672     'string'),
1673
1674 ( 'circ.block_renews_for_holds',
1675     oils_i18n_gettext('circ.block_renews_for_holds', 'Holds: Block Renewal of Items Needed for Holds', 'coust', 'label'),
1676     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'),
1677     'bool' ),
1678
1679 ( 'circ.password_reset_request_per_user_limit',
1680     oils_i18n_gettext('circ.password_reset_request_per_user_limit', 'Circulation: Maximum concurrently active self-serve password reset requests per user', 'coust', 'label'),
1681     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'),
1682     'string'),
1683
1684 ( 'circ.password_reset_request_time_to_live',
1685     oils_i18n_gettext('circ.password_reset_request_time_to_live', 'Circulation: Self-serve password reset request time-to-live', 'coust', 'label'),
1686     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'),
1687     'string'),
1688
1689 ( 'circ.password_reset_request_throttle',
1690     oils_i18n_gettext('circ.password_reset_request_throttle', 'Circulation: Maximum concurrently active self-serve password reset requests', 'coust', 'label'),
1691     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'),
1692     'string')
1693 ;
1694
1695 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1696         'ui.circ.suppress_checkin_popups',
1697         oils_i18n_gettext(
1698             'ui.circ.suppress_checkin_popups', 
1699             'Circ: Suppress popup-dialogs during check-in.', 
1700             'coust', 
1701             'label'),
1702         oils_i18n_gettext(
1703             'ui.circ.suppress_checkin_popups', 
1704             'Circ: Suppress popup-dialogs during check-in.', 
1705             'coust', 
1706             'description'),
1707         'bool'
1708 );
1709
1710 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1711         'format.date',
1712         oils_i18n_gettext(
1713             'format.date',
1714             'GUI: Format Dates with this pattern.', 
1715             'coust', 
1716             'label'),
1717         oils_i18n_gettext(
1718             'format.date',
1719             'GUI: Format Dates with this pattern (examples: "yyyy-MM-dd" for "2010-04-26", "MMM d, yyyy" for "Apr 26, 2010")', 
1720             'coust', 
1721             'description'),
1722         'string'
1723 ), (
1724         'format.time',
1725         oils_i18n_gettext(
1726             'format.time',
1727             'GUI: Format Times with this pattern.', 
1728             'coust', 
1729             'label'),
1730         oils_i18n_gettext(
1731             'format.time',
1732             '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")', 
1733             'coust', 
1734             'description'),
1735         'string'
1736 );
1737
1738 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1739         'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1740         oils_i18n_gettext(
1741             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1742             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1743             'coust', 
1744             'label'),
1745         oils_i18n_gettext(
1746             'cat.bib.delete_on_no_copy_via_acq_lineitem_cancel',
1747             'CAT: Delete bib if all copies are deleted via Acquisitions lineitem cancellation.', 
1748             'coust', 
1749             'description'),
1750         'bool'
1751 );
1752
1753 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1754         'url.remote_column_settings',
1755         oils_i18n_gettext(
1756             'url.remote_column_settings',
1757             'GUI: URL for remote directory containing list column settings.', 
1758             'coust', 
1759             'label'),
1760         oils_i18n_gettext(
1761             'url.remote_column_settings',
1762             '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.', 
1763             'coust', 
1764             'description'),
1765         'string'
1766 );
1767
1768 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1769         'gui.disable_local_save_columns',
1770         oils_i18n_gettext(
1771             'gui.disable_local_save_columns',
1772             'GUI: Disable the ability to save list column configurations locally.', 
1773             'coust', 
1774             'label'),
1775         oils_i18n_gettext(
1776             'gui.disable_local_save_columns',
1777             '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.', 
1778             'coust', 
1779             'description'),
1780         'bool'
1781 );
1782
1783 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1784         'circ.password_reset_request_requires_matching_email',
1785         oils_i18n_gettext(
1786             'circ.password_reset_request_requires_matching_email',
1787             'Circulation: Require matching email address for password reset requests', 
1788             'coust', 
1789             'label'),
1790         oils_i18n_gettext(
1791             'circ.password_reset_request_requires_matching_email',
1792             'Circulation: Require matching email address for password reset requests', 
1793             'coust', 
1794             'description'),
1795         'bool'
1796 );
1797
1798 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1799         'circ.holds.expired_patron_block',
1800         oils_i18n_gettext(
1801             'circ.holds.expired_patron_block',
1802             'Circulation: Block hold request if hold recipient privileges have expired', 
1803             'coust', 
1804             'label'),
1805         oils_i18n_gettext(
1806             'circ.holds.expired_patron_block',
1807             'Circulation: Block hold request if hold recipient privileges have expired', 
1808             'coust', 
1809             'description'),
1810         'bool'
1811 );
1812
1813 INSERT INTO config.org_unit_setting_type
1814     (name, label, description, datatype) VALUES (
1815         'circ.booking_reservation.default_elbow_room',
1816         oils_i18n_gettext(
1817             'circ.booking_reservation.default_elbow_room',
1818             'Booking: Elbow room',
1819             'coust',
1820             'label'
1821         ),
1822         oils_i18n_gettext(
1823             'circ.booking_reservation.default_elbow_room',
1824             '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.',
1825             'coust',
1826             'label'
1827         ),
1828         'interval'
1829     );
1830
1831 -- Org_unit_setting_type(s) that need an fm_class:
1832 INSERT into config.org_unit_setting_type
1833 ( name, label, description, datatype, fm_class ) VALUES
1834 ( 'acq.default_copy_location',
1835   'Default copy location',
1836   null,
1837   'link',
1838   'acpl' );
1839
1840 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1841     'circ.holds.org_unit_target_weight',
1842     'Holds: Org Unit Target Weight',
1843     '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.',
1844     'integer'
1845 );
1846
1847 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1848     'circ.holds.target_holds_by_org_unit_weight',
1849     'Holds: Use weight-based hold targeting',
1850     'Use library weight based hold targeting',
1851     'bool'
1852 );
1853
1854 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1855     'circ.holds.max_org_unit_target_loops',
1856     'Holds: Maximum library target attempts',
1857     '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',
1858     'integer'
1859 );
1860
1861
1862 -- Org setting for overriding the circ lib of a precat copy
1863 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1864     'circ.pre_cat_copy_circ_lib',
1865     'Pre-cat Item Circ Lib',
1866     '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',
1867     'string'
1868 );
1869
1870 -- Circ auto-renew interval setting
1871 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1872     'circ.checkout_auto_renew_age',
1873     'Checkout auto renew age',
1874     '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',
1875     'interval'
1876 );
1877
1878 -- Setting for behind the desk hold pickups
1879 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1880     'circ.holds.behind_desk_pickup_supported',
1881     'Holds: Behind Desk Pickup Supported',
1882     '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',
1883     'bool'
1884 );
1885
1886 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
1887         'acq.holds.allow_holds_from_purchase_request',
1888         oils_i18n_gettext(
1889             'acq.holds.allow_holds_from_purchase_request', 
1890             'Allows patrons to create automatic holds from purchase requests.', 
1891             'coust', 
1892             'label'),
1893         oils_i18n_gettext(
1894             'acq.holds.allow_holds_from_purchase_request', 
1895             'Allows patrons to create automatic holds from purchase requests.', 
1896             'coust', 
1897             'description'),
1898         'bool'
1899 );
1900
1901 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
1902     'circ.holds.target_skip_me',
1903     'Skip For Hold Targeting',
1904     'When true, don''t target any copies at this org unit for holds',
1905     'bool'
1906 );
1907
1908 -- claims returned mark item missing 
1909 INSERT INTO
1910     config.org_unit_setting_type ( name, label, description, datatype )
1911     VALUES (
1912         'circ.claim_return.mark_missing',
1913         'Claim Return: Mark copy as missing', 
1914         'When a circ is marked as claims-returned, also mark the copy as missing',
1915         'bool'
1916     );
1917
1918 -- claims never checked out mark item missing 
1919 INSERT INTO
1920     config.org_unit_setting_type ( name, label, description, datatype )
1921     VALUES (
1922         'circ.claim_never_checked_out.mark_missing',
1923         'Claim Never Checked Out: Mark copy as missing', 
1924         'When a circ is marked as claims-never-checked-out, mark the copy as missing',
1925         'bool'
1926     );
1927
1928 -- mark damaged void overdue setting
1929 INSERT INTO
1930     config.org_unit_setting_type ( name, label, description, datatype )
1931     VALUES (
1932         'circ.damaged.void_ovedue',
1933         'Mark item damaged voids overdues',
1934         'When an item is marked damaged, overdue fines on the most recent circulation are voided.',
1935         'bool'
1936     );
1937
1938 -- hold cancel display limits
1939 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1940     VALUES (
1941         'circ.holds.canceled.display_count',
1942         'Holds: Canceled holds display count',
1943         'How many canceled holds to show in patron holds interfaces',
1944         'integer'
1945     );
1946
1947 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1948     VALUES (
1949         'circ.holds.canceled.display_age',
1950         'Holds: Canceled holds display age',
1951         'Show all canceled holds that were canceled within this amount of time',
1952         'interval'
1953     );
1954
1955 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1956     VALUES (
1957         'circ.holds.uncancel.reset_request_time',
1958         'Holds: Reset request time on un-cancel',
1959         'When a hold is uncanceled, reset the request time to push it to the end of the queue',
1960         'bool'
1961     );
1962
1963 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
1964     VALUES (
1965         'circ.holds.default_shelf_expire_interval',
1966         'Default hold shelf expire interval',
1967         '',
1968         'interval'
1969 );
1970
1971 INSERT INTO config.org_unit_setting_type (name, label, description, datatype, fm_class)
1972     VALUES (
1973         'circ.claim_return.copy_status', 
1974         'Claim Return Copy Status', 
1975         'Claims returned copies are put into this status.  Default is to leave the copy in the Checked Out status',
1976         'link', 
1977         'ccs' 
1978     );
1979
1980 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) 
1981     VALUES ( 
1982         'circ.max_fine.cap_at_price',
1983         oils_i18n_gettext('circ.max_fine.cap_at_price', 'Circ: Cap Max Fine at Item Price', 'coust', 'label'),
1984         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'),
1985         'bool' 
1986     );
1987
1988 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) 
1989     VALUES ( 
1990         'circ.holds.clear_shelf.copy_status',
1991         oils_i18n_gettext('circ.holds.clear_shelf.copy_status', 'Holds: Clear shelf copy status', 'coust', 'label'),
1992         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'),
1993         'link',
1994         'ccs'
1995     );
1996
1997 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
1998     VALUES ( 
1999         'circ.selfcheck.workstation_required',
2000         oils_i18n_gettext('circ.selfcheck.workstation_required', 'Selfcheck: Workstation Required', 'coust', 'label'),
2001         oils_i18n_gettext('circ.selfcheck.workstation_required', 'All selfcheck stations must use a workstation', 'coust', 'description'),
2002         'bool'
2003     ), (
2004         'circ.selfcheck.patron_password_required',
2005         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Selfcheck: Require Patron Password', 'coust', 'label'),
2006         oils_i18n_gettext('circ.selfcheck.patron_password_required', 'Patron must log in with barcode and password at selfcheck station', 'coust', 'description'),
2007         'bool'
2008     );
2009
2010 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2011     VALUES ( 
2012         'circ.selfcheck.alert.sound',
2013         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Selfcheck: Audio Alerts', 'coust', 'label'),
2014         oils_i18n_gettext('circ.selfcheck.alert.sound', 'Use audio alerts for selfcheck events', 'coust', 'description'),
2015         'bool'
2016     );
2017
2018 INSERT INTO
2019     config.org_unit_setting_type (name, label, description, datatype)
2020     VALUES (
2021         'notice.telephony.callfile_lines',
2022         'Telephony: Arbitrary line(s) to include in each notice callfile',
2023         $$
2024         This overrides lines from opensrf.xml.
2025         Line(s) must be valid for your target server and platform
2026         (e.g. Asterisk 1.4).
2027         $$,
2028         'string'
2029     );
2030
2031 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2032     VALUES ( 
2033         'circ.offline.username_allowed',
2034         oils_i18n_gettext('circ.offline.username_allowed', 'Offline: Patron Usernames Allowed', 'coust', 'label'),
2035         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'),
2036         'bool'
2037     );
2038
2039 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2040 VALUES (
2041     'acq.fund.balance_limit.warn',
2042     oils_i18n_gettext('acq.fund.balance_limit.warn', 'Fund Spending Limit for Warning', 'coust', 'label'),
2043     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'),
2044     'integer'
2045 );
2046
2047 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2048 VALUES (
2049     'acq.fund.balance_limit.block',
2050     oils_i18n_gettext('acq.fund.balance_limit.block', 'Fund Spending Limit for Block', 'coust', 'label'),
2051     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'),
2052     'integer'
2053 );
2054
2055 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2056     VALUES (
2057         'circ.holds.hold_has_copy_at.alert',
2058         oils_i18n_gettext('circ.holds.hold_has_copy_at.alert', 'Holds: Has Local Copy Alert', 'coust', 'label'),
2059         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'),
2060         'bool'
2061     ),(
2062         'circ.holds.hold_has_copy_at.block',
2063         oils_i18n_gettext('circ.holds.hold_has_copy_at.block', 'Holds: Has Local Copy Block', 'coust', 'label'),
2064         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'),
2065         'bool'
2066     );
2067
2068 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2069 VALUES (
2070     'auth.persistent_login_interval',
2071     oils_i18n_gettext('auth.persistent_login_interval', 'Persistent Login Duration', 'coust', 'label'),
2072     oils_i18n_gettext('auth.persistent_login_interval', 'How long a persistent login lasts.  E.g. ''2 weeks''', 'coust', 'description'),
2073     'interval'
2074 );
2075
2076 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2077         'cat.marc_control_number_identifier',
2078         oils_i18n_gettext(
2079             'cat.marc_control_number_identifier', 
2080             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2081             'coust', 
2082             'label'),
2083         oils_i18n_gettext(
2084             'cat.marc_control_number_identifier', 
2085             'Cat: Defines the control number identifier used in 003 and 035 fields.', 
2086             'coust', 
2087             'description'),
2088         'string'
2089 );
2090
2091 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) 
2092     VALUES (
2093         'circ.selfcheck.block_checkout_on_copy_status',
2094         oils_i18n_gettext(
2095             'circ.selfcheck.block_checkout_on_copy_status',
2096             'Selfcheck: Block copy checkout status',
2097             'coust',
2098             'label'
2099         ),
2100         oils_i18n_gettext(
2101             'circ.selfcheck.block_checkout_on_copy_status',
2102             'List of copy status IDs that will block checkout even if the generic COPY_NOT_AVAILABLE event is overridden',
2103             'coust',
2104             'description'
2105         ),
2106         'array'
2107     );
2108
2109 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2110 VALUES (
2111     'serial.prev_issuance_copy_location',
2112     oils_i18n_gettext(
2113         'serial.prev_issuance_copy_location',
2114         'Serials: Previous Issuance Copy Location',
2115         'coust',
2116         'label'
2117     ),
2118     oils_i18n_gettext(
2119         'serial.prev_issuance_copy_location',
2120         'When a serial issuance is received, copies (units) of the previous issuance will be automatically moved into the configured shelving location',
2121         'coust',
2122         'descripton'
2123         ),
2124     'link',
2125     'acpl'
2126 );
2127
2128 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class )
2129 VALUES (
2130     'cat.default_classification_scheme',
2131     oils_i18n_gettext(
2132         'cat.default_classification_scheme',
2133         'Cataloging: Default Classification Scheme',
2134         'coust',
2135         'label'
2136     ),
2137     oils_i18n_gettext(
2138         'cat.default_classification_scheme',
2139         'Defines the default classification scheme for new call numbers: 1 = Generic; 2 = Dewey; 3 = LC',
2140         'coust',
2141         'descripton'
2142         ),
2143     'link',
2144     'acnc'
2145 );
2146
2147 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2148         'opac.org_unit_hiding.depth',
2149         oils_i18n_gettext(
2150             'opac.org_unit_hiding.depth',
2151             'OPAC: Org Unit Hiding Depth', 
2152             'coust', 
2153             'label'),
2154         oils_i18n_gettext(
2155             'opac.org_unit_hiding.depth',
2156             '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.', 
2157             'coust', 
2158             'description'),
2159         'integer'
2160 );
2161
2162 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype )
2163     VALUES ( 
2164         'circ.holds.clear_shelf.no_capture_holds',
2165         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'Holds: Bypass hold capture during clear shelf process', 'coust', 'label'),
2166         oils_i18n_gettext('circ.holds.clear_shelf.no_capture_holds', 'During the clear shelf process, avoid capturing new holds on cleared items.', 'coust', 'description'),
2167         'bool'
2168     );
2169
2170 INSERT INTO config.org_unit_setting_type (name, label, description, datatype) VALUES (
2171     'circ.booking_reservation.stop_circ',
2172     'Disallow circulation of items when they are on booking reserve and that reserve overlaps with the checkout period',
2173     '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.',
2174     'bool'
2175 );
2176
2177 ---------------------------------
2178 -- Seed data for usr_setting_type
2179 ----------------------------------
2180
2181 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2182     VALUES ('opac.default_font', TRUE, 'OPAC Font Size', 'OPAC Font Size', 'string');
2183
2184 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2185     VALUES ('opac.default_search_depth', TRUE, 'OPAC Search Depth', 'OPAC Search Depth', 'integer');
2186
2187 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2188     VALUES ('opac.default_search_location', TRUE, 'OPAC Search Location', 'OPAC Search Location', 'integer');
2189
2190 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2191     VALUES ('opac.hits_per_page', TRUE, 'Hits per Page', 'Hits per Page', 'string');
2192
2193 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2194     VALUES ('opac.hold_notify', TRUE, 'Hold Notification Format', 'Hold Notification Format', 'string');
2195
2196 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2197     VALUES ('staff_client.catalog.record_view.default', TRUE, 'Default Record View', 'Default Record View', 'string');
2198
2199 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2200     VALUES ('staff_client.copy_editor.templates', TRUE, 'Copy Editor Template', 'Copy Editor Template', 'object');
2201
2202 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2203     VALUES ('circ.holds_behind_desk', FALSE, 'Hold is behind Circ Desk', 'Hold is behind Circ Desk', 'bool');
2204
2205 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2206     VALUES (
2207         'history.circ.retention_age',
2208         TRUE,
2209         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','label'),
2210         oils_i18n_gettext('history.circ.retention_age','Historical Circulation Retention Age','cust','description'),
2211         'interval'
2212     ),(
2213         'history.circ.retention_start',
2214         FALSE,
2215         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','label'),
2216         oils_i18n_gettext('history.circ.retention_start','Historical Circulation Retention Start Date','cust','description'),
2217         'date'
2218     );
2219
2220 INSERT INTO config.usr_setting_type (name,opac_visible,label,description,datatype)
2221     VALUES (
2222         'history.hold.retention_age',
2223         TRUE,
2224         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','label'),
2225         oils_i18n_gettext('history.hold.retention_age','Historical Hold Retention Age','cust','description'),
2226         'interval'
2227     ),(
2228         'history.hold.retention_start',
2229         TRUE,
2230         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','label'),
2231         oils_i18n_gettext('history.hold.retention_start','Historical Hold Retention Start Date','cust','description'),
2232         'interval'
2233     ),(
2234         'history.hold.retention_count',
2235         TRUE,
2236         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','label'),
2237         oils_i18n_gettext('history.hold.retention_count','Historical Hold Retention Count','cust','description'),
2238         'integer'
2239     );
2240
2241 INSERT INTO config.usr_setting_type (name, opac_visible, label, description, datatype)
2242     VALUES (
2243         'opac.default_sort',
2244         TRUE,
2245         oils_i18n_gettext(
2246             'opac.default_sort',
2247             'OPAC Default Search Sort',
2248             'cust',
2249             'label'
2250         ),
2251         oils_i18n_gettext(
2252             'opac.default_sort',
2253             'OPAC Default Search Sort',
2254             'cust',
2255             'description'
2256         ),
2257         'string'
2258     );
2259
2260 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2261         'circ.missing_pieces.copy_status',
2262         oils_i18n_gettext(
2263             'circ.missing_pieces.copy_status',
2264             'Circulation: Item Status for Missing Pieces',
2265             'coust',
2266             'label'),
2267         oils_i18n_gettext(
2268             'circ.missing_pieces.copy_status',
2269             '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.',
2270             'coust',
2271             'description'),
2272         'link',
2273         'ccs'
2274 );
2275
2276 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2277         'circ.do_not_tally_claims_returned',
2278         oils_i18n_gettext(
2279             'circ.do_not_tally_claims_returned',
2280             'Circulation: Do not include outstanding Claims Returned circulations in lump sum tallies in Patron Display.',
2281             'coust',
2282             'label'),
2283         oils_i18n_gettext(
2284             'circ.do_not_tally_claims_returned',
2285             '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.',
2286             'coust',
2287             'description'),
2288         'bool'
2289 );
2290
2291 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2292     VALUES
2293         ('cat.label.font.size',
2294             oils_i18n_gettext('cat.label.font.size',
2295                 'Cataloging: Spine and pocket label font size', 'coust', 'label'),
2296             oils_i18n_gettext('cat.label.font.size',
2297                 'Set the default font size for spine and pocket labels', 'coust', 'description'),
2298             'integer'
2299         )
2300         ,('cat.label.font.family',
2301             oils_i18n_gettext('cat.label.font.family',
2302                 'Cataloging: Spine and pocket label font family', 'coust', 'label'),
2303             oils_i18n_gettext('cat.label.font.family',
2304                 '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".',
2305                 'coust', 'description'),
2306             'string'
2307         )
2308         ,('cat.spine.line.width',
2309             oils_i18n_gettext('cat.spine.line.width',
2310                 'Cataloging: Spine label line width', 'coust', 'label'),
2311             oils_i18n_gettext('cat.spine.line.width',
2312                 'Set the default line width for spine labels in number of characters. This specifies the boundary at which lines must be wrapped.',
2313                 'coust', 'description'),
2314             'integer'
2315         )
2316         ,('cat.spine.line.height',
2317             oils_i18n_gettext('cat.spine.line.height',
2318                 'Cataloging: Spine label maximum lines', 'coust', 'label'),
2319             oils_i18n_gettext('cat.spine.line.height',
2320                 'Set the default maximum number of lines for spine labels.',
2321                 'coust', 'description'),
2322             'integer'
2323         )
2324         ,('cat.spine.line.margin',
2325             oils_i18n_gettext('cat.spine.line.margin',
2326                 'Cataloging: Spine label left margin', 'coust', 'label'),
2327             oils_i18n_gettext('cat.spine.line.margin',
2328                 'Set the left margin for spine labels in number of characters.',
2329                 'coust', 'description'),
2330             'integer'
2331         )
2332 ;
2333
2334 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
2335     VALUES
2336         ('cat.label.font.weight',
2337             oils_i18n_gettext('cat.label.font.weight',
2338                 'Cataloging: Spine and pocket label font weight', 'coust', 'label'),
2339             oils_i18n_gettext('cat.label.font.weight',
2340                 'Set the preferred font weight for spine and pocket labels. You can specify "normal", "bold", "bolder", or "lighter".',
2341                 'coust', 'description'),
2342             'string'
2343         )
2344 ;
2345
2346 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2347         'circ.patron_edit.clone.copy_address',
2348         oils_i18n_gettext(
2349             'circ.patron_edit.clone.copy_address',
2350             'Patron Registration: Cloned patrons get address copy',
2351             'coust',
2352             'label'
2353         ),
2354         oils_i18n_gettext(
2355             'circ.patron_edit.clone.copy_address',
2356             'In the Patron editor, copy addresses from the cloned user instead of linking directly to the address',
2357             'coust',
2358             'description'
2359         ),
2360         'bool'
2361 );
2362
2363 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype, fm_class ) VALUES (
2364         'ui.patron.default_ident_type',
2365         oils_i18n_gettext(
2366             'ui.patron.default_ident_type',
2367             'GUI: Default Ident Type for Patron Registration',
2368             'coust',
2369             'label'),
2370         oils_i18n_gettext(
2371             'ui.patron.default_ident_type',
2372             'This is the default Ident Type for new users in the patron editor.',
2373             'coust',
2374             'description'),
2375         'link',
2376         'cit'
2377 );
2378
2379 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2380         'ui.patron.default_country',
2381         oils_i18n_gettext(
2382             'ui.patron.default_country',
2383             'GUI: Default Country for New Addresses in Patron Editor',
2384             'coust',
2385             'label'),
2386         oils_i18n_gettext(
2387             'ui.patron.default_country',
2388             'This is the default Country for new addresses in the patron editor.',
2389             'coust',
2390             'description'),
2391         'string'
2392 );
2393
2394 INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
2395         'ui.patron.registration.require_address',
2396         oils_i18n_gettext(
2397             'ui.patron.registration.require_address',
2398             'GUI: Require at least one address for Patron Registration',
2399             'coust',
2400             'label'),
2401         oils_i18n_gettext(
2402             'ui.patron.registration.require_address',
2403             'Enforces a requirement for having at least one address for a patron during registration.',
2404             'coust',
2405             'description'),
2406         'bool'
2407 );
2408
2409 INSERT INTO config.org_unit_setting_type (
2410     name, label, description, datatype
2411 ) VALUES
2412     ('credit.processor.payflowpro.enabled',
2413         'Credit card processing: Enable PayflowPro payments',
2414         'This is NOT the same thing as the settings labeled with just "PayPal."',
2415         'bool'
2416     ),
2417     ('credit.processor.payflowpro.login',
2418         'Credit card processing: PayflowPro login/merchant ID',
2419         'Often the same thing as the PayPal manager login',
2420         'string'
2421     ),
2422     ('credit.processor.payflowpro.password',
2423         'Credit card processing: PayflowPro password',
2424         'PayflowPro password',
2425         'string'
2426     ),
2427     ('credit.processor.payflowpro.testmode',
2428         'Credit card processing: PayflowPro test mode',
2429         'Do not really process transactions, but stay in test mode - uses pilot-payflowpro.paypal.com instead of the usual host',
2430         'bool'
2431     ),
2432     ('credit.processor.payflowpro.vendor',
2433         'Credit card processing: PayflowPro vendor',
2434         'Often the same thing as the login',
2435         'string'
2436     ),
2437     ('credit.processor.payflowpro.partner',
2438         'Credit card processing: PayflowPro partner',
2439         'Often "PayPal" or "VeriSign", sometimes others',
2440         'string'
2441     );
2442
2443 -- Patch the name of an old selfcheck setting
2444 UPDATE actor.org_unit_setting
2445     SET name = 'circ.selfcheck.alert.popup'
2446     WHERE name = 'circ.selfcheck.alert_on_checkout_event';
2447
2448 -- Rename certain existing org_unit settings, if present,
2449 -- and make sure their values are JSON
2450 UPDATE actor.org_unit_setting SET
2451     name = 'circ.holds.default_estimated_wait_interval',
2452     --
2453     -- The value column should be JSON.  The old value should be a number,
2454     -- but it may or may not be quoted.  The following CASE behaves
2455     -- differently depending on whether value is quoted.  It is simplistic,
2456     -- and will be defeated by leading or trailing white space, or various
2457     -- malformations.
2458     --
2459     value = CASE WHEN SUBSTR( value, 1, 1 ) = '"'
2460                 THEN '"' || SUBSTR( value, 2, LENGTH(value) - 2 ) || ' days"'
2461                 ELSE '"' || value || ' days"'
2462             END
2463 WHERE name = 'circ.hold_estimate_wait_interval';
2464
2465 -- Create types for existing org unit settings
2466 -- not otherwise accounted for
2467
2468 INSERT INTO config.org_unit_setting_type(
2469  name,
2470  label,
2471  description
2472 )
2473 SELECT DISTINCT
2474         name,
2475         name,
2476         'FIXME'
2477 FROM
2478         actor.org_unit_setting
2479 WHERE
2480         name NOT IN (
2481                 SELECT name
2482                 FROM config.org_unit_setting_type
2483         );
2484
2485 -- Add foreign key to org_unit_setting
2486
2487 ALTER TABLE actor.org_unit_setting
2488         ADD FOREIGN KEY (name) REFERENCES config.org_unit_setting_type (name)
2489                 DEFERRABLE INITIALLY DEFERRED;
2490
2491 -- Create types for existing user settings
2492 -- not otherwise accounted for
2493
2494 INSERT INTO config.usr_setting_type (
2495         name,
2496         label,
2497         description
2498 )
2499 SELECT DISTINCT
2500         name,
2501         name,
2502         'FIXME'
2503 FROM
2504         actor.usr_setting
2505 WHERE
2506         name NOT IN (
2507                 SELECT name
2508                 FROM config.usr_setting_type
2509         );
2510
2511 -- Add foreign key to user_setting_type
2512
2513 ALTER TABLE actor.usr_setting
2514         ADD FOREIGN KEY (name) REFERENCES config.usr_setting_type (name)
2515                 ON DELETE CASCADE ON UPDATE CASCADE
2516                 DEFERRABLE INITIALLY DEFERRED;
2517
2518 INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES
2519     (1, 'cat.spine.line.margin', 0)
2520     ,(1, 'cat.spine.line.height', 9)
2521     ,(1, 'cat.spine.line.width', 8)
2522     ,(1, 'cat.label.font.family', '"monospace"')
2523     ,(1, 'cat.label.font.size', 10)
2524     ,(1, 'cat.label.font.weight', '"normal"')
2525 ;
2526
2527 ALTER TABLE action_trigger.event_definition ADD COLUMN granularity TEXT;
2528 ALTER TABLE action_trigger.event ADD COLUMN async_output BIGINT REFERENCES action_trigger.event_output (id);
2529 ALTER TABLE action_trigger.event_definition ADD COLUMN usr_field TEXT;
2530 ALTER TABLE action_trigger.event_definition ADD COLUMN opt_in_setting TEXT REFERENCES config.usr_setting_type (name) DEFERRABLE INITIALLY DEFERRED;
2531
2532 CREATE OR REPLACE FUNCTION is_json( TEXT ) RETURNS BOOL AS $f$
2533     use JSON::XS;
2534     my $json = shift();
2535     eval { JSON::XS->new->allow_nonref->decode( $json ) };
2536     return $@ ? 0 : 1;
2537 $f$ LANGUAGE PLPERLU;
2538
2539 ALTER TABLE action_trigger.event ADD COLUMN user_data TEXT CHECK (user_data IS NULL OR is_json( user_data ));
2540
2541 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2542     'hold_request.cancel.expire_no_target',
2543     'ahr',
2544     'A hold is cancelled because no copies were found'
2545 );
2546
2547 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2548     'hold_request.cancel.expire_holds_shelf',
2549     'ahr',
2550     'A hold is cancelled because it was on the holds shelf too long'
2551 );
2552
2553 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2554     'hold_request.cancel.staff',
2555     'ahr',
2556     'A hold is cancelled because it was cancelled by staff'
2557 );
2558
2559 INSERT INTO action_trigger.hook (key,core_type,description) VALUES (
2560     'hold_request.cancel.patron',
2561     'ahr',
2562     'A hold is cancelled by the patron'
2563 );
2564
2565 -- Fix typos in descriptions
2566 UPDATE action_trigger.hook SET description = 'A hold is successfully placed' WHERE key = 'hold_request.success';
2567 UPDATE action_trigger.hook SET description = 'A hold is attempted but not successfully placed' WHERE key = 'hold_request.failure';
2568
2569 -- Add a hook for renewals
2570 INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('renewal','circ','Item renewed to user');
2571
2572 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');
2573
2574 -- Sample Pre-due Notice --
2575
2576 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, delay_field, group_field, template) 
2577     VALUES (6, 'f', 1, '3 Day Courtesy Notice', 'checkout.due', 'CircIsOpen', 'SendEmail', '-3 days', 'due_date', 'usr', 
2578 $$
2579 [%- USE date -%]
2580 [%- user = target.0.usr -%]
2581 To: [%- params.recipient_email || user.email %]
2582 From: [%- params.sender_email || default_sender %]
2583 Subject: Courtesy Notice
2584
2585 Dear [% user.family_name %], [% user.first_given_name %]
2586 As a reminder, the following items are due in 3 days.
2587
2588 [% FOR circ IN target %]
2589     Title: [% circ.target_copy.call_number.record.simple_record.title %] 
2590     Barcode: [% circ.target_copy.barcode %] 
2591     Due: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
2592     Item Cost: [% helpers.get_copy_price(circ.target_copy) %]
2593     Library: [% circ.circ_lib.name %]
2594     Library Phone: [% circ.circ_lib.phone %]
2595 [% END %]
2596
2597 $$);
2598
2599 INSERT INTO action_trigger.environment (event_def, path) VALUES 
2600     (6, 'target_copy.call_number.record.simple_record'),
2601     (6, 'usr'),
2602     (6, 'circ_lib.billing_address');
2603
2604 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2605     (6, 'max_delay_age', '"1 day"');
2606
2607 -- also add the max delay age to the default overdue notice event def
2608 INSERT INTO action_trigger.event_params (event_def, param, value) VALUES
2609     (1, 'max_delay_age', '"1 day"');
2610   
2611 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');
2612
2613 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.');
2614
2615 INSERT INTO action_trigger.hook (
2616         key,
2617         core_type,
2618         description,
2619         passive
2620     ) VALUES (
2621         'hold_request.shelf_expires_soon',
2622         'ahr',
2623         'A hold on the shelf will expire there soon.',
2624         TRUE
2625     );
2626
2627 INSERT INTO action_trigger.event_definition (
2628         id,
2629         active,
2630         owner,
2631         name,
2632         hook,
2633         validator,
2634         reactor,
2635         delay,
2636         delay_field,
2637         group_field,
2638         template
2639     ) VALUES (
2640         7,
2641         FALSE,
2642         1,
2643         'Hold Expires from Shelf Soon',
2644         'hold_request.shelf_expires_soon',
2645         'HoldIsAvailable',
2646         'SendEmail',
2647         '- 1 DAY',
2648         'shelf_expire_time',
2649         'usr',
2650 $$
2651 [%- USE date -%]
2652 [%- user = target.0.usr -%]
2653 To: [%- params.recipient_email || user.email %]
2654 From: [%- params.sender_email || default_sender %]
2655 Subject: Hold Available Notification
2656
2657 Dear [% user.family_name %], [% user.first_given_name %]
2658 You requested holds on the following item(s), which are available for
2659 pickup, but these holds will soon expire.
2660
2661 [% FOR hold IN target %]
2662     [%- data = helpers.get_copy_bib_basics(hold.current_copy.id) -%]
2663     Title: [% data.title %]
2664     Author: [% data.author %]
2665     Library: [% hold.pickup_lib.name %]
2666 [% END %]
2667 $$
2668     );
2669
2670 INSERT INTO action_trigger.environment (
2671         event_def,
2672         path
2673     ) VALUES
2674     ( 7, 'current_copy'),
2675     ( 7, 'pickup_lib.billing_address'),
2676     ( 7, 'usr');
2677
2678 INSERT INTO action_trigger.hook (
2679         key,
2680         core_type,
2681         description,
2682         passive
2683     ) VALUES (
2684         'hold_request.long_wait',
2685         'ahr',
2686         'A patron has been waiting on a hold to be fulfilled for a long time.',
2687         TRUE
2688     );
2689
2690 INSERT INTO action_trigger.event_definition (
2691         id,
2692         active,
2693         owner,
2694         name,
2695         hook,
2696         validator,
2697         reactor,
2698         delay,
2699         delay_field,
2700         group_field,
2701         template
2702     ) VALUES (
2703         9,
2704         FALSE,
2705         1,
2706         'Hold waiting for pickup for long time',
2707         'hold_request.long_wait',
2708         'NOOP_True',
2709         'SendEmail',
2710         '6 MONTHS',
2711         'request_time',
2712         'usr',
2713 $$
2714 [%- USE date -%]
2715 [%- user = target.0.usr -%]
2716 To: [%- params.recipient_email || user.email %]
2717 From: [%- params.sender_email || default_sender %]
2718 Subject: Long Wait Hold Notification
2719
2720 Dear [% user.family_name %], [% user.first_given_name %]
2721
2722 You requested hold(s) on the following item(s), but unfortunately
2723 we have not been able to fulfill your request after a considerable
2724 length of time.  If you would still like to receive these items,
2725 no action is required.
2726
2727 [% FOR hold IN target %]
2728     Title: [% hold.bib_rec.bib_record.simple_record.title %]
2729     Author: [% hold.bib_rec.bib_record.simple_record.author %]
2730 [% END %]
2731 $$
2732 );
2733
2734 INSERT INTO action_trigger.environment (
2735         event_def,
2736         path
2737     ) VALUES
2738     (9, 'pickup_lib'),
2739     (9, 'usr'),
2740     (9, 'bib_rec.bib_record.simple_record');
2741
2742 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2743     VALUES (
2744         'format.selfcheck.checkout',
2745         'circ',
2746         'Formats circ objects for self-checkout receipt',
2747         TRUE
2748     );
2749
2750 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2751     VALUES (
2752         10,
2753         TRUE,
2754         1,
2755         'Self-Checkout Receipt',
2756         'format.selfcheck.checkout',
2757         'NOOP_True',
2758         'ProcessTemplate',
2759         'usr',
2760         'print-on-demand',
2761 $$
2762 [%- USE date -%]
2763 [%- SET user = target.0.usr -%]
2764 [%- SET lib = target.0.circ_lib -%]
2765 [%- SET lib_addr = target.0.circ_lib.billing_address -%]
2766 [%- SET hours = lib.hours_of_operation -%]
2767 <div>
2768     <style> li { padding: 8px; margin 5px; }</style>
2769     <div>[% date.format %]</div>
2770     <div>[% lib.name %]</div>
2771     <div>[% lib_addr.street1 %] [% lib_addr.street2 %]</div>
2772     <div>[% lib_addr.city %], [% lib_addr.state %] [% lb_addr.post_code %]</div>
2773     <div>[% lib.phone %]</div>
2774     <br/>
2775
2776     [% user.family_name %], [% user.first_given_name %]
2777     <ol>
2778     [% FOR circ IN target %]
2779         [%-
2780             SET idx = loop.count - 1;
2781             SET udata =  user_data.$idx
2782         -%]
2783         <li>
2784             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2785             <div>Barcode: [% circ.target_copy.barcode %]</div>
2786             [% IF udata.renewal_failure %]
2787                 <div style='color:red;'>Renewal Failed</div>
2788             [% ELSE %]
2789                 <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2790             [% END %]
2791         </li>
2792     [% END %]
2793     </ol>
2794     
2795     <div>
2796         Library Hours
2797         [%- BLOCK format_time; date.format(time _ ' 1/1/1000', format='%I:%M %p'); END -%]
2798         <div>
2799             Monday 
2800             [% PROCESS format_time time = hours.dow_0_open %] 
2801             [% PROCESS format_time time = hours.dow_0_close %] 
2802         </div>
2803         <div>
2804             Tuesday 
2805             [% PROCESS format_time time = hours.dow_1_open %] 
2806             [% PROCESS format_time time = hours.dow_1_close %] 
2807         </div>
2808         <div>
2809             Wednesday 
2810             [% PROCESS format_time time = hours.dow_2_open %] 
2811             [% PROCESS format_time time = hours.dow_2_close %] 
2812         </div>
2813         <div>
2814             Thursday
2815             [% PROCESS format_time time = hours.dow_3_open %] 
2816             [% PROCESS format_time time = hours.dow_3_close %] 
2817         </div>
2818         <div>
2819             Friday
2820             [% PROCESS format_time time = hours.dow_4_open %] 
2821             [% PROCESS format_time time = hours.dow_4_close %] 
2822         </div>
2823         <div>
2824             Saturday
2825             [% PROCESS format_time time = hours.dow_5_open %] 
2826             [% PROCESS format_time time = hours.dow_5_close %] 
2827         </div>
2828         <div>
2829             Sunday 
2830             [% PROCESS format_time time = hours.dow_6_open %] 
2831             [% PROCESS format_time time = hours.dow_6_close %] 
2832         </div>
2833     </div>
2834 </div>
2835 $$
2836 );
2837
2838 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2839     ( 10, 'target_copy'),
2840     ( 10, 'circ_lib.billing_address'),
2841     ( 10, 'circ_lib.hours_of_operation'),
2842     ( 10, 'usr');
2843
2844 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2845     VALUES (
2846         'format.selfcheck.items_out',
2847         'circ',
2848         'Formats items out for self-checkout receipt',
2849         TRUE
2850     );
2851
2852 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2853     VALUES (
2854         11,
2855         TRUE,
2856         1,
2857         'Self-Checkout Items Out Receipt',
2858         'format.selfcheck.items_out',
2859         'NOOP_True',
2860         'ProcessTemplate',
2861         'usr',
2862         'print-on-demand',
2863 $$
2864 [%- USE date -%]
2865 [%- SET user = target.0.usr -%]
2866 <div>
2867     <style> li { padding: 8px; margin 5px; }</style>
2868     <div>[% date.format %]</div>
2869     <br/>
2870
2871     [% user.family_name %], [% user.first_given_name %]
2872     <ol>
2873     [% FOR circ IN target %]
2874         <li>
2875             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
2876             <div>Barcode: [% circ.target_copy.barcode %]</div>
2877             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
2878         </li>
2879     [% END %]
2880     </ol>
2881 </div>
2882 $$
2883 );
2884
2885
2886 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2887     ( 11, 'target_copy'),
2888     ( 11, 'circ_lib.billing_address'),
2889     ( 11, 'circ_lib.hours_of_operation'),
2890     ( 11, 'usr');
2891
2892 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2893     VALUES (
2894         'format.selfcheck.holds',
2895         'ahr',
2896         'Formats holds for self-checkout receipt',
2897         TRUE
2898     );
2899
2900 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, group_field, granularity, template )
2901     VALUES (
2902         12,
2903         TRUE,
2904         1,
2905         'Self-Checkout Holds Receipt',
2906         'format.selfcheck.holds',
2907         'NOOP_True',
2908         'ProcessTemplate',
2909         'usr',
2910         'print-on-demand',
2911 $$
2912 [%- USE date -%]
2913 [%- SET user = target.0.usr -%]
2914 <div>
2915     <style> li { padding: 8px; margin 5px; }</style>
2916     <div>[% date.format %]</div>
2917     <br/>
2918
2919     [% user.family_name %], [% user.first_given_name %]
2920     <ol>
2921     [% FOR hold IN target %]
2922         [%-
2923             SET idx = loop.count - 1;
2924             SET udata =  user_data.$idx
2925         -%]
2926         <li>
2927             <div>Title: [% hold.bib_rec.bib_record.simple_record.title %]</div>
2928             <div>Author: [% hold.bib_rec.bib_record.simple_record.author %]</div>
2929             <div>Pickup Location: [% hold.pickup_lib.name %]</div>
2930             <div>Status: 
2931                 [%- IF udata.ready -%]
2932                     Ready for pickup
2933                 [% ELSE %]
2934                     #[% udata.queue_position %] of [% udata.potential_copies %] copies.
2935                 [% END %]
2936             </div>
2937         </li>
2938     [% END %]
2939     </ol>
2940 </div>
2941 $$
2942 );
2943
2944
2945 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2946     ( 12, 'bib_rec.bib_record.simple_record'),
2947     ( 12, 'pickup_lib'),
2948     ( 12, 'usr');
2949
2950 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
2951     VALUES (
2952         'format.selfcheck.fines',
2953         'au',
2954         'Formats fines for self-checkout receipt',
2955         TRUE
2956     );
2957
2958 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template )
2959     VALUES (
2960         13,
2961         TRUE,
2962         1,
2963         'Self-Checkout Fines Receipt',
2964         'format.selfcheck.fines',
2965         'NOOP_True',
2966         'ProcessTemplate',
2967         'print-on-demand',
2968 $$
2969 [%- USE date -%]
2970 [%- SET user = target -%]
2971 <div>
2972     <style> li { padding: 8px; margin 5px; }</style>
2973     <div>[% date.format %]</div>
2974     <br/>
2975
2976     [% user.family_name %], [% user.first_given_name %]
2977     <ol>
2978     [% FOR xact IN user.open_billable_transactions_summary %]
2979         <li>
2980             <div>Details: 
2981                 [% IF xact.xact_type == 'circulation' %]
2982                     [%- helpers.get_copy_bib_basics(xact.circulation.target_copy).title -%]
2983                 [% ELSE %]
2984                     [%- xact.last_billing_type -%]
2985                 [% END %]
2986             </div>
2987             <div>Total Billed: [% xact.total_owed %]</div>
2988             <div>Total Paid: [% xact.total_paid %]</div>
2989             <div>Balance Owed : [% xact.balance_owed %]</div>
2990         </li>
2991     [% END %]
2992     </ol>
2993 </div>
2994 $$
2995 );
2996
2997 INSERT INTO action_trigger.environment ( event_def, path) VALUES
2998     ( 13, 'open_billable_transactions_summary.circulation' );
2999
3000 INSERT INTO action_trigger.reactor (module,description) VALUES
3001 (   'SendFile',
3002     oils_i18n_gettext(
3003         'SendFile',
3004         '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.',
3005         'atreact',
3006         'description'
3007     )
3008 );
3009
3010 INSERT INTO action_trigger.hook (key, core_type, description, passive) 
3011     VALUES (
3012         'format.acqli.html',
3013         'jub',
3014         'Formats lineitem worksheet for titles received',
3015         TRUE
3016     );
3017
3018 INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, granularity, template)
3019     VALUES (
3020         14,
3021         TRUE,
3022         1,
3023         'Lineitem Worksheet',
3024         'format.acqli.html',
3025         'NOOP_True',
3026         'ProcessTemplate',
3027         'print-on-demand',
3028 $$
3029 [%- USE date -%]
3030 [%- SET li = target; -%]
3031 <div class="wrapper">
3032     <div class="summary" style='font-size:110%; font-weight:bold;'>
3033
3034         <div>Title: [% helpers.get_li_attr("title", "", li.attributes) %]</div>
3035         <div>Author: [% helpers.get_li_attr("author", "", li.attributes) %]</div>
3036         <div class="count">Item Count: [% li.lineitem_details.size %]</div>
3037         <div class="lineid">Lineitem ID: [% li.id %]</div>
3038
3039         [% IF li.distribution_formulas.size > 0 %]
3040             [% SET forms = [] %]
3041             [% FOREACH form IN li.distribution_formulas; forms.push(form.formula.name); END %]
3042             <div>Distribution Formulas: [% forms.join(',') %]</div>
3043         [% END %]
3044
3045         [% IF li.lineitem_notes.size > 0 %]
3046             Lineitem Notes:
3047             <ul>
3048                 [%- FOR note IN li.lineitem_notes -%]
3049                     <li>
3050                     [% IF note.alert_text %]
3051                         [% note.alert_text.code -%] 
3052                         [% IF note.value -%]
3053                             : [% note.value %]
3054                         [% END %]
3055                     [% ELSE %]
3056                         [% note.value -%] 
3057                     [% END %]
3058                     </li>
3059                 [% END %]
3060             </ul>
3061         [% END %]
3062     </div>
3063     <br/>
3064     <table>
3065         <thead>
3066             <tr>
3067                 <th>Branch</th>
3068                 <th>Barcode</th>
3069                 <th>Call Number</th>
3070                 <th>Fund</th>
3071                 <th>Recd.</th>
3072                 <th>Notes</th>
3073             </tr>
3074         </thead>
3075         <tbody>
3076         [% FOREACH detail IN li.lineitem_details.sort('owning_lib') %]
3077             [% 
3078                 IF copy.eg_copy_id;
3079                     SET copy = copy.eg_copy_id;
3080                     SET cn_label = copy.call_number.label;
3081                 ELSE; 
3082                     SET copy = detail; 
3083                     SET cn_label = detail.cn_label;
3084                 END 
3085             %]
3086             <tr>
3087                 <!-- acq.lineitem_detail.id = [%- detail.id -%] -->
3088                 <td style='padding:5px;'>[% detail.owning_lib.shortname %]</td>
3089                 <td style='padding:5px;'>[% IF copy.barcode   %]<span class="barcode"  >[% detail.barcode   %]</span>[% END %]</td>
3090                 <td style='padding:5px;'>[% IF cn_label %]<span class="cn_label" >[% cn_label  %]</span>[% END %]</td>
3091                 <td style='padding:5px;'>[% IF detail.fund %]<span class="fund">[% detail.fund.code %] ([% detail.fund.year %])</span>[% END %]</td>
3092                 <td style='padding:5px;'>[% IF detail.recv_time %]<span class="recv_time">[% detail.recv_time %]</span>[% END %]</td>
3093                 <td style='padding:5px;'>[% detail.note %]</td>
3094             </tr>
3095         [% END %]
3096         </tbody>
3097     </table>
3098 </div>
3099 $$
3100 );
3101
3102
3103 INSERT INTO action_trigger.environment (event_def, path) VALUES
3104     ( 14, 'attributes' ),
3105     ( 14, 'lineitem_details' ),
3106     ( 14, 'lineitem_details.owning_lib' ),
3107     ( 14, 'lineitem_notes' )
3108 ;
3109
3110 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3111         'aur.ordered',
3112         'aur', 
3113         oils_i18n_gettext(
3114             'aur.ordered',
3115             'A patron acquisition request has been marked On-Order.',
3116             'ath',
3117             'description'
3118         ), 
3119         TRUE
3120     ), (
3121         'aur.received', 
3122         'aur', 
3123         oils_i18n_gettext(
3124             'aur.received', 
3125             'A patron acquisition request has been marked Received.',
3126             'ath',
3127             'description'
3128         ),
3129         TRUE
3130     ), (
3131         'aur.cancelled',
3132         'aur',
3133         oils_i18n_gettext(
3134             'aur.cancelled',
3135             'A patron acquisition request has been marked Cancelled.',
3136             'ath',
3137             'description'
3138         ),
3139         TRUE
3140     )
3141 ;
3142
3143 INSERT INTO action_trigger.validator (module,description) VALUES (
3144         'Acq::UserRequestOrdered',
3145         oils_i18n_gettext(
3146             'Acq::UserRequestOrdered',
3147             'Tests to see if the corresponding Line Item has a state of "on-order".',
3148             'atval',
3149             'description'
3150         )
3151     ), (
3152         'Acq::UserRequestReceived',
3153         oils_i18n_gettext(
3154             'Acq::UserRequestReceived',
3155             'Tests to see if the corresponding Line Item has a state of "received".',
3156             'atval',
3157             'description'
3158         )
3159     ), (
3160         'Acq::UserRequestCancelled',
3161         oils_i18n_gettext(
3162             'Acq::UserRequestCancelled',
3163             'Tests to see if the corresponding Line Item has a state of "cancelled".',
3164             'atval',
3165             'description'
3166         )
3167     )
3168 ;
3169
3170 -- What was event_definition #15 in v1.6.1 will be recreated as #20.  This
3171 -- renumbering requires some juggling:
3172 --
3173 -- 1. Update any child rows to point to #20.  These updates will temporarily
3174 -- violate foreign key constraints, but that's okay as long as we create
3175 -- #20 before committing.
3176 --
3177 -- 2. Delete the old #15.
3178 --
3179 -- 3. Insert the new #15.
3180 --
3181 -- 4. Insert #20.
3182 --
3183 -- We could combine steps 2 and 3 into a single update, but that would create
3184 -- additional opportunities for typos, since we already have the insert from
3185 -- an upgrade script.
3186
3187 UPDATE action_trigger.environment
3188 SET event_def = 20
3189 WHERE event_def = 15;
3190
3191 UPDATE action_trigger.event
3192 SET event_def = 20
3193 WHERE event_def = 15;
3194
3195 UPDATE action_trigger.event_params
3196 SET event_def = 20
3197 WHERE event_def = 15;
3198
3199 DELETE FROM action_trigger.event_definition
3200 WHERE id = 15;
3201
3202 INSERT INTO action_trigger.event_definition (
3203         id,
3204         active,
3205         owner,
3206         name,
3207         hook,
3208         validator,
3209         reactor,
3210         template
3211     ) VALUES (
3212         15,
3213         FALSE,
3214         1,
3215         'Email Notice: Patron Acquisition Request marked On-Order.',
3216         'aur.ordered',
3217         'Acq::UserRequestOrdered',
3218         'SendEmail',
3219 $$
3220 [%- USE date -%]
3221 [%- SET li = target.lineitem; -%]
3222 [%- SET user = target.usr -%]
3223 [%- SET title = helpers.get_li_attr("title", "", li.attributes) -%]
3224 [%- SET author = helpers.get_li_attr("author", "", li.attributes) -%]
3225 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) -%]
3226 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) -%]
3227 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3228 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3229
3230 To: [%- params.recipient_email || user.email %]
3231 From: [%- params.sender_email || default_sender %]
3232 Subject: Acquisition Request Notification
3233
3234 Dear [% user.family_name %], [% user.first_given_name %]
3235 Our records indicate the following acquisition request has been placed on order.
3236
3237 Title: [% title %]
3238 [% IF author %]Author: [% author %][% END %]
3239 [% IF edition %]Edition: [% edition %][% END %]
3240 [% IF isbn %]ISBN: [% isbn %][% END %]
3241 [% IF publisher %]Publisher: [% publisher %][% END %]
3242 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3243 Lineitem ID: [% li.id %]
3244 $$
3245     ), (
3246         16,
3247         FALSE,
3248         1,
3249         'Email Notice: Patron Acquisition Request marked Received.',
3250         'aur.received',
3251         'Acq::UserRequestReceived',
3252         'SendEmail',
3253 $$
3254 [%- USE date -%]
3255 [%- SET li = target.lineitem; -%]
3256 [%- SET user = target.usr -%]
3257 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3258 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3259 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3260 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3261 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3262 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3263
3264 To: [%- params.recipient_email || user.email %]
3265 From: [%- params.sender_email || default_sender %]
3266 Subject: Acquisition Request Notification
3267
3268 Dear [% user.family_name %], [% user.first_given_name %]
3269 Our records indicate the materials for the following acquisition request have been received.
3270
3271 Title: [% title %]
3272 [% IF author %]Author: [% author %][% END %]
3273 [% IF edition %]Edition: [% edition %][% END %]
3274 [% IF isbn %]ISBN: [% isbn %][% END %]
3275 [% IF publisher %]Publisher: [% publisher %][% END %]
3276 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3277 Lineitem ID: [% li.id %]
3278 $$
3279     ), (
3280         17,
3281         FALSE,
3282         1,
3283         'Email Notice: Patron Acquisition Request marked Cancelled.',
3284         'aur.cancelled',
3285         'Acq::UserRequestCancelled',
3286         'SendEmail',
3287 $$
3288 [%- USE date -%]
3289 [%- SET li = target.lineitem; -%]
3290 [%- SET user = target.usr -%]
3291 [%- SET title = helpers.get_li_attr("title", "", li.attributes) %]
3292 [%- SET author = helpers.get_li_attr("author", "", li.attributes) %]
3293 [%- SET edition = helpers.get_li_attr("edition", "", li.attributes) %]
3294 [%- SET isbn = helpers.get_li_attr("isbn", "", li.attributes) %]
3295 [%- SET publisher = helpers.get_li_attr("publisher", "", li.attributes) -%]
3296 [%- SET pubdate = helpers.get_li_attr("pubdate", "", li.attributes) -%]
3297
3298 To: [%- params.recipient_email || user.email %]
3299 From: [%- params.sender_email || default_sender %]
3300 Subject: Acquisition Request Notification
3301
3302 Dear [% user.family_name %], [% user.first_given_name %]
3303 Our records indicate the following acquisition request has been cancelled.
3304
3305 Title: [% title %]
3306 [% IF author %]Author: [% author %][% END %]
3307 [% IF edition %]Edition: [% edition %][% END %]
3308 [% IF isbn %]ISBN: [% isbn %][% END %]
3309 [% IF publisher %]Publisher: [% publisher %][% END %]
3310 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3311 Lineitem ID: [% li.id %]
3312 $$
3313     );
3314
3315 INSERT INTO action_trigger.environment (
3316         event_def,
3317         path
3318     ) VALUES 
3319         ( 15, 'lineitem' ),
3320         ( 15, 'lineitem.attributes' ),
3321         ( 15, 'usr' ),
3322
3323         ( 16, 'lineitem' ),
3324         ( 16, 'lineitem.attributes' ),
3325         ( 16, 'usr' ),
3326
3327         ( 17, 'lineitem' ),
3328         ( 17, 'lineitem.attributes' ),
3329         ( 17, 'usr' )
3330     ;
3331
3332 INSERT INTO action_trigger.event_definition
3333 (id, active, owner, name, hook, validator, reactor, cleanup_success, cleanup_failure, delay, delay_field, group_field, template) VALUES
3334 (23, true, 1, 'PO JEDI', 'acqpo.activated', 'Acq::PurchaseOrderEDIRequired', 'GeneratePurchaseOrderJEDI', NULL, NULL, '00:05:00', NULL, NULL,
3335 $$
3336 [%- USE date -%]
3337 [%# start JEDI document 
3338   # Vendor specific kludges:
3339   # BT      - vendcode goes to NAD/BY *suffix*  w/ 91 qualifier
3340   # INGRAM  - vendcode goes to NAD/BY *segment* w/ 91 qualifier (separately)
3341   # BRODART - vendcode goes to FTX segment (lineitem level)
3342 -%]
3343 [%- 
3344 IF target.provider.edi_default.vendcode && target.provider.code == 'BRODART';
3345     xtra_ftx = target.provider.edi_default.vendcode;
3346 END;
3347 -%]
3348 [%- BLOCK big_block -%]
3349 {
3350    "recipient":"[% target.provider.san %]",
3351    "sender":"[% target.ordering_agency.mailing_address.san %]",
3352    "body": [{
3353      "ORDERS":[ "order", {
3354         "po_number":[% target.id %],
3355         "date":"[% date.format(date.now, '%Y%m%d') %]",
3356         "buyer":[
3357             [%   IF   target.provider.edi_default.vendcode && (target.provider.code == 'BT' || target.provider.name.match('(?i)^BAKER & TAYLOR'))  -%]
3358                 {"id-qualifier": 91, "id":"[% target.ordering_agency.mailing_address.san _ ' ' _ target.provider.edi_default.vendcode %]"}
3359             [%- ELSIF target.provider.edi_default.vendcode && target.provider.code == 'INGRAM' -%]
3360                 {"id":"[% target.ordering_agency.mailing_address.san %]"},
3361                 {"id-qualifier": 91, "id":"[% target.provider.edi_default.vendcode %]"}
3362             [%- ELSE -%]
3363                 {"id":"[% target.ordering_agency.mailing_address.san %]"}
3364             [%- END -%]
3365         ],
3366         "vendor":[
3367             [%- # target.provider.name (target.provider.id) -%]
3368             "[% target.provider.san %]",
3369             {"id-qualifier": 92, "id":"[% target.provider.id %]"}
3370         ],
3371         "currency":"[% target.provider.currency_type %]",
3372                 
3373         "items":[
3374         [%- FOR li IN target.lineitems %]
3375         {
3376             "line_index":"[% li.id %]",
3377             "identifiers":[   [%-# li.isbns = helpers.get_li_isbns(li.attributes) %]
3378             [% FOR isbn IN helpers.get_li_isbns(li.attributes) -%]
3379                 [% IF isbn.length == 13 -%]
3380                 {"id-qualifier":"EN","id":"[% isbn %]"},
3381                 [% ELSE -%]
3382                 {"id-qualifier":"IB","id":"[% isbn %]"},
3383                 [%- END %]
3384             [% END %]
3385                 {"id-qualifier":"IN","id":"[% li.id %]"}
3386             ],
3387             "price":[% li.estimated_unit_price || '0.00' %],
3388             "desc":[
3389                 {"BTI":"[% helpers.get_li_attr('title',     '', li.attributes) %]"},
3390                 {"BPU":"[% helpers.get_li_attr('publisher', '', li.attributes) %]"},
3391                 {"BPD":"[% helpers.get_li_attr('pubdate',   '', li.attributes) %]"},
3392                 {"BPH":"[% helpers.get_li_attr('pagination','', li.attributes) %]"}
3393             ],
3394             [%- ftx_vals = []; 
3395                 FOR note IN li.lineitem_notes; 
3396                     NEXT UNLESS note.vendor_public == 't'; 
3397                     ftx_vals.push(note.value); 
3398                 END; 
3399                 IF xtra_ftx;           ftx_vals.unshift(xtra_ftx); END; 
3400                 IF ftx_vals.size == 0; ftx_vals.unshift('');       END;  # BT needs FTX+LIN for every LI, even if it is an empty one
3401             -%]
3402
3403             "free-text":[ 
3404                 [% FOR note IN ftx_vals -%] "[% note %]"[% UNLESS loop.last %], [% END %][% END %] 
3405             ],            
3406             "quantity":[% li.lineitem_details.size %]
3407         }[% UNLESS loop.last %],[% END %]
3408         [%-# TODO: lineitem details (later) -%]
3409         [% END %]
3410         ],
3411         "line_items":[% target.lineitems.size %]
3412      }]  [%# close ORDERS array %]
3413    }]    [%# close  body  array %]
3414 }
3415 [% END %]
3416 [% tempo = PROCESS big_block; helpers.escape_json(tempo) %]
3417
3418 $$);
3419
3420 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3421   (23, 'lineitems.attributes'), 
3422   (23, 'lineitems.lineitem_details'), 
3423   (23, 'lineitems.lineitem_notes'), 
3424   (23, 'ordering_agency.mailing_address'), 
3425   (23, 'provider');
3426
3427 UPDATE action_trigger.event_definition SET template = 
3428 $$
3429 [%- USE date -%]
3430 [%-
3431     # find a lineitem attribute by name and optional type
3432     BLOCK get_li_attr;
3433         FOR attr IN li.attributes;
3434             IF attr.attr_name == attr_name;
3435                 IF !attr_type OR attr_type == attr.attr_type;
3436                     attr.attr_value;
3437                     LAST;
3438                 END;
3439             END;
3440         END;
3441     END
3442 -%]
3443
3444 <h2>Purchase Order [% target.id %]</h2>
3445 <br/>
3446 date <b>[% date.format(date.now, '%Y%m%d') %]</b>
3447 <br/>
3448
3449 <style>
3450     table td { padding:5px; border:1px solid #aaa;}
3451     table { width:95%; border-collapse:collapse; }
3452     #vendor-notes { padding:5px; border:1px solid #aaa; }
3453 </style>
3454 <table id='vendor-table'>
3455   <tr>
3456     <td valign='top'>Vendor</td>
3457     <td>
3458       <div>[% target.provider.name %]</div>
3459       <div>[% target.provider.addresses.0.street1 %]</div>
3460       <div>[% target.provider.addresses.0.street2 %]</div>
3461       <div>[% target.provider.addresses.0.city %]</div>
3462       <div>[% target.provider.addresses.0.state %]</div>
3463       <div>[% target.provider.addresses.0.country %]</div>
3464       <div>[% target.provider.addresses.0.post_code %]</div>
3465     </td>
3466     <td valign='top'>Ship to / Bill to</td>
3467     <td>
3468       <div>[% target.ordering_agency.name %]</div>
3469       <div>[% target.ordering_agency.billing_address.street1 %]</div>
3470       <div>[% target.ordering_agency.billing_address.street2 %]</div>
3471       <div>[% target.ordering_agency.billing_address.city %]</div>
3472       <div>[% target.ordering_agency.billing_address.state %]</div>
3473       <div>[% target.ordering_agency.billing_address.country %]</div>
3474       <div>[% target.ordering_agency.billing_address.post_code %]</div>
3475     </td>
3476   </tr>
3477 </table>
3478
3479 <br/><br/>
3480 <fieldset id='vendor-notes'>
3481     <legend>Notes to the Vendor</legend>
3482     <ul>
3483     [% FOR note IN target.notes %]
3484         [% IF note.vendor_public == 't' %]
3485             <li>[% note.value %]</li>
3486         [% END %]
3487     [% END %]
3488     </ul>
3489 </fieldset>
3490 <br/><br/>
3491
3492 <table>
3493   <thead>
3494     <tr>
3495       <th>PO#</th>
3496       <th>ISBN or Item #</th>
3497       <th>Title</th>
3498       <th>Quantity</th>
3499       <th>Unit Price</th>
3500       <th>Line Total</th>
3501       <th>Notes</th>
3502     </tr>
3503   </thead>
3504   <tbody>
3505
3506   [% subtotal = 0 %]
3507   [% FOR li IN target.lineitems %]
3508
3509   <tr>
3510     [% count = li.lineitem_details.size %]
3511     [% price = li.estimated_unit_price %]
3512     [% litotal = (price * count) %]
3513     [% subtotal = subtotal + litotal %]
3514     [% isbn = PROCESS get_li_attr attr_name = 'isbn' %]
3515     [% ident = PROCESS get_li_attr attr_name = 'identifier' %]
3516
3517     <td>[% target.id %]</td>
3518     <td>[% isbn || ident %]</td>
3519     <td>[% PROCESS get_li_attr attr_name = 'title' %]</td>
3520     <td>[% count %]</td>
3521     <td>[% price %]</td>
3522     <td>[% litotal %]</td>
3523     <td>
3524         <ul>
3525         [% FOR note IN li.lineitem_notes %]
3526             [% IF note.vendor_public == 't' %]
3527                 <li>[% note.value %]</li>
3528             [% END %]
3529         [% END %]
3530         </ul>
3531     </td>
3532   </tr>
3533   [% END %]
3534   <tr>
3535     <td/><td/><td/><td/>
3536     <td>Subtotal</td>
3537     <td>[% subtotal %]</td>
3538   </tr>
3539   </tbody>
3540 </table>
3541
3542 <br/>
3543
3544 Total Line Item Count: [% target.lineitems.size %]
3545 $$
3546 WHERE id = 4;
3547
3548 INSERT INTO action_trigger.environment (event_def, path) VALUES 
3549     (4, 'lineitems.lineitem_notes'),
3550     (4, 'notes');
3551
3552 INSERT INTO action_trigger.environment (event_def, path) VALUES
3553     ( 14, 'lineitem_notes.alert_text' ),
3554     ( 14, 'distribution_formulas.formula' ),
3555     ( 14, 'lineitem_details.fund' ),
3556     ( 14, 'lineitem_details.eg_copy_id' ),
3557     ( 14, 'lineitem_details.eg_copy_id.call_number' )
3558 ;
3559
3560 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
3561         'aur.created',
3562         'aur',
3563         oils_i18n_gettext(
3564             'aur.created',
3565             'A patron has made an acquisitions request.',
3566             'ath',
3567             'description'
3568         ),
3569         TRUE
3570     ), (
3571         'aur.rejected',
3572         'aur',
3573         oils_i18n_gettext(
3574             'aur.rejected',
3575             'A patron acquisition request has been rejected.',
3576             'ath',
3577             'description'
3578         ),
3579         TRUE
3580     )
3581 ;
3582
3583 INSERT INTO action_trigger.event_definition (
3584         id,
3585         active,
3586         owner,
3587         name,
3588         hook,
3589         validator,
3590         reactor,
3591         template
3592     ) VALUES (
3593         18,
3594         FALSE,
3595         1,
3596         'Email Notice: Acquisition Request created.',
3597         'aur.created',
3598         'NOOP_True',
3599         'SendEmail',
3600 $$
3601 [%- USE date -%]
3602 [%- SET user = target.usr -%]
3603 [%- SET title = target.title -%]
3604 [%- SET author = target.author -%]
3605 [%- SET isxn = target.isxn -%]
3606 [%- SET publisher = target.publisher -%]
3607 [%- SET pubdate = target.pubdate -%]
3608
3609 To: [%- params.recipient_email || user.email %]
3610 From: [%- params.sender_email || default_sender %]
3611 Subject: Acquisition Request Notification
3612
3613 Dear [% user.family_name %], [% user.first_given_name %]
3614 Our records indicate that you have made the following acquisition request:
3615
3616 Title: [% title %]
3617 [% IF author %]Author: [% author %][% END %]
3618 [% IF edition %]Edition: [% edition %][% END %]
3619 [% IF isbn %]ISXN: [% isxn %][% END %]
3620 [% IF publisher %]Publisher: [% publisher %][% END %]
3621 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3622 $$
3623     ), (
3624         19,
3625         FALSE,
3626         1,
3627         'Email Notice: Acquisition Request Rejected.',
3628         'aur.rejected',
3629         'NOOP_True',
3630         'SendEmail',
3631 $$
3632 [%- USE date -%]
3633 [%- SET user = target.usr -%]
3634 [%- SET title = target.title -%]
3635 [%- SET author = target.author -%]
3636 [%- SET isxn = target.isxn -%]
3637 [%- SET publisher = target.publisher -%]
3638 [%- SET pubdate = target.pubdate -%]
3639 [%- SET cancel_reason = target.cancel_reason.description -%]
3640
3641 To: [%- params.recipient_email || user.email %]
3642 From: [%- params.sender_email || default_sender %]
3643 Subject: Acquisition Request Notification
3644
3645 Dear [% user.family_name %], [% user.first_given_name %]
3646 Our records indicate the following acquisition request has been rejected for this reason: [% cancel_reason %]
3647
3648 Title: [% title %]
3649 [% IF author %]Author: [% author %][% END %]
3650 [% IF edition %]Edition: [% edition %][% END %]
3651 [% IF isbn %]ISBN: [% isbn %][% END %]
3652 [% IF publisher %]Publisher: [% publisher %][% END %]
3653 [% IF pubdate %]Publication Date: [% pubdate %][% END %]
3654 $$
3655     );
3656
3657 INSERT INTO action_trigger.environment (
3658         event_def,
3659         path
3660     ) VALUES 
3661         ( 18, 'usr' ),
3662         ( 19, 'usr' ),
3663         ( 19, 'cancel_reason' )
3664     ;
3665
3666 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3667     VALUES (
3668         'format.acqcle.html',
3669         'acqcle',
3670         'Formats claim events into a voucher',
3671         TRUE
3672     );
3673
3674 INSERT INTO action_trigger.event_definition (
3675         id, active, owner, name, hook, group_field,
3676         validator, reactor, granularity, template
3677     ) VALUES (
3678         21,
3679         TRUE,
3680         1,
3681         'Claim Voucher',
3682         'format.acqcle.html',
3683         'claim',
3684         'NOOP_True',
3685         'ProcessTemplate',
3686         'print-on-demand',
3687 $$
3688 [%- USE date -%]
3689 [%- SET claim = target.0.claim -%]
3690 <!-- This will need refined/prettified. -->
3691 <div class="acq-claim-voucher">
3692     <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
3693     <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
3694     <ul>
3695         [% FOR event IN target %]
3696         <li>
3697             Event type: [% event.type.code %]
3698             [% IF event.type.library_initiated %](Library initiated)[% END %]
3699             <br />
3700             Event date: [% event.event_date %]<br />
3701             Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
3702             Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
3703             Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
3704             Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
3705             [% event.claim.lineitem_detail.fund.code %]
3706             ([% event.claim.lineitem_detail.fund.year %])
3707         </li>
3708         [% END %]
3709     </ul>
3710 </div>
3711 $$
3712 );
3713
3714 INSERT INTO action_trigger.environment (event_def, path) VALUES
3715     (21, 'claim'),
3716     (21, 'claim.type'),
3717     (21, 'claim.lineitem_detail'),
3718     (21, 'claim.lineitem_detail.fund'),
3719     (21, 'claim.lineitem_detail.lineitem.attributes'),
3720     (21, 'claim.lineitem_detail.lineitem.purchase_order'),
3721     (21, 'creator'),
3722     (21, 'type')
3723 ;
3724
3725 INSERT INTO action_trigger.hook (key, core_type, description, passive)
3726     VALUES (
3727         'format.acqinv.html',
3728         'acqinv',
3729         'Formats invoices into a voucher',
3730         TRUE
3731     );
3732
3733 INSERT INTO action_trigger.event_definition (
3734         id, active, owner, name, hook,
3735         validator, reactor, granularity, template
3736     ) VALUES (
3737         22,
3738         TRUE,
3739         1,
3740         'Invoice',
3741         'format.acqinv.html',
3742         'NOOP_True',
3743         'ProcessTemplate',
3744         'print-on-demand',
3745 $$
3746 [% FILTER collapse %]
3747 [%- SET invoice = target -%]
3748 <!-- This lacks totals, info about funds (for invoice entries,
3749     funds are per-LID!), and general refinement -->
3750 <div class="acq-invoice-voucher">
3751     <h1>Invoice</h1>
3752     <div>
3753         <strong>No.</strong> [% invoice.inv_ident %]
3754         [% IF invoice.inv_type %]
3755             / <strong>Type:</strong>[% invoice.inv_type %]
3756         [% END %]
3757     </div>
3758     <div>
3759         <dl>
3760             [% BLOCK ent_with_address %]
3761             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3762             <dd>
3763                 [% IF ent.addresses.0 %]
3764                     [% SET addr = ent.addresses.0 %]
3765                     [% addr.street1 %]<br />
3766                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3767                     [% addr.city %],
3768                     [% IF addr.county %] [% addr.county %], [% END %]
3769                     [% IF addr.state %] [% addr.state %] [% END %]
3770                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3771                     [% IF addr.country %] [% addr.country %] [% END %]
3772                 [% END %]
3773                 <p>
3774                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3775                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3776                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3777                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3778                 </p>
3779             </dd>
3780             [% END %]
3781             [% INCLUDE ent_with_address
3782                 ent = invoice.provider
3783                 ent_label = "Provider" %]
3784             [% INCLUDE ent_with_address
3785                 ent = invoice.shipper
3786                 ent_label = "Shipper" %]
3787             <dt>Receiver</dt>
3788             <dd>
3789                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3790             </dd>
3791             <dt>Received</dt>
3792             <dd>
3793                 [% helpers.format_date(invoice.recv_date) %] by
3794                 [% invoice.recv_method %]
3795             </dd>
3796             [% IF invoice.note %]
3797                 <dt>Note</dt>
3798                 <dd>
3799                     [% invoice.note %]
3800                 </dd>
3801             [% END %]
3802         </dl>
3803     </div>
3804     <ul>
3805         [% FOR entry IN invoice.entries %]
3806             <li>
3807                 [% IF entry.lineitem %]
3808                     Title: [% helpers.get_li_attr(
3809                         "title", "", entry.lineitem.attributes
3810                     ) %]<br />
3811                     Author: [% helpers.get_li_attr(
3812                         "author", "", entry.lineitem.attributes
3813                     ) %]
3814                 [% END %]
3815                 [% IF entry.purchase_order %]
3816                     (PO: [% entry.purchase_order.name %])
3817                 [% END %]<br />
3818                 Invoice item count: [% entry.inv_item_count %]
3819                 [% IF entry.phys_item_count %]
3820                     / Physical item count: [% entry.phys_item_count %]
3821                 [% END %]
3822                 <br />
3823                 [% IF entry.cost_billed %]
3824                     Cost billed: [% entry.cost_billed %]
3825                     [% IF entry.billed_per_item %](per item)[% END %]
3826                     <br />
3827                 [% END %]
3828                 [% IF entry.actual_cost %]
3829                     Actual cost: [% entry.actual_cost %]<br />
3830                 [% END %]
3831                 [% IF entry.amount_paid %]
3832                     Amount paid: [% entry.amount_paid %]<br />
3833                 [% END %]
3834                 [% IF entry.note %]Note: [% entry.note %][% END %]
3835             </li>
3836         [% END %]
3837         [% FOR item IN invoice.items %]
3838             <li>
3839                 [% IF item.inv_item_type %]
3840                     Item Type: [% item.inv_item_type %]<br />
3841                 [% END %]
3842                 [% IF item.title %]Title/Description:
3843                     [% item.title %]<br />
3844                 [% END %]
3845                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3846                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3847                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3848                 [% IF item.cost_billed %]
3849                     Cost billed: [% item.cost_billed %]<br />
3850                 [% END %]
3851                 [% IF item.actual_cost %]
3852                     Actual cost: [% item.actual_cost %]<br />
3853                 [% END %]
3854                 [% IF item.amount_paid %]
3855                     Amount paid: [% item.amount_paid %]<br />
3856                 [% END %]
3857             </li>
3858         [% END %]
3859     </ul>
3860 </div>
3861 [% END %]
3862 $$
3863 );
3864
3865 UPDATE action_trigger.event_definition SET template = $$[% FILTER collapse %]
3866 [%- SET invoice = target -%]
3867 <!-- This lacks general refinement -->
3868 <div class="acq-invoice-voucher">
3869     <h1>Invoice</h1>
3870     <div>
3871         <strong>No.</strong> [% invoice.inv_ident %]
3872         [% IF invoice.inv_type %]
3873             / <strong>Type:</strong>[% invoice.inv_type %]
3874         [% END %]
3875     </div>
3876     <div>
3877         <dl>
3878             [% BLOCK ent_with_address %]
3879             <dt>[% ent_label %]: [% ent.name %] ([% ent.code %])</dt>
3880             <dd>
3881                 [% IF ent.addresses.0 %]
3882                     [% SET addr = ent.addresses.0 %]
3883                     [% addr.street1 %]<br />
3884                     [% IF addr.street2 %][% addr.street2 %]<br />[% END %]
3885                     [% addr.city %],
3886                     [% IF addr.county %] [% addr.county %], [% END %]
3887                     [% IF addr.state %] [% addr.state %] [% END %]
3888                     [% IF addr.post_code %][% addr.post_code %][% END %]<br />
3889                     [% IF addr.country %] [% addr.country %] [% END %]
3890                 [% END %]
3891                 <p>
3892                     [% IF ent.phone %] Phone: [% ent.phone %]<br />[% END %]
3893                     [% IF ent.fax_phone %] Fax: [% ent.fax_phone %]<br />[% END %]
3894                     [% IF ent.url %] URL: [% ent.url %]<br />[% END %]
3895                     [% IF ent.email %] E-mail: [% ent.email %] [% END %]
3896                 </p>
3897             </dd>
3898             [% END %]
3899             [% INCLUDE ent_with_address
3900                 ent = invoice.provider
3901                 ent_label = "Provider" %]
3902             [% INCLUDE ent_with_address
3903                 ent = invoice.shipper
3904                 ent_label = "Shipper" %]
3905             <dt>Receiver</dt>
3906             <dd>
3907                 [% invoice.receiver.name %] ([% invoice.receiver.shortname %])
3908             </dd>
3909             <dt>Received</dt>
3910             <dd>
3911                 [% helpers.format_date(invoice.recv_date) %] by
3912                 [% invoice.recv_method %]
3913             </dd>
3914             [% IF invoice.note %]
3915                 <dt>Note</dt>
3916                 <dd>
3917                     [% invoice.note %]
3918                 </dd>
3919             [% END %]
3920         </dl>
3921     </div>
3922     <ul>
3923         [% FOR entry IN invoice.entries %]
3924             <li>
3925                 [% IF entry.lineitem %]
3926                     Title: [% helpers.get_li_attr(
3927                         "title", "", entry.lineitem.attributes
3928                     ) %]<br />
3929                     Author: [% helpers.get_li_attr(
3930                         "author", "", entry.lineitem.attributes
3931                     ) %]
3932                 [% END %]
3933                 [% IF entry.purchase_order %]
3934                     (PO: [% entry.purchase_order.name %])
3935                 [% END %]<br />
3936                 Invoice item count: [% entry.inv_item_count %]
3937                 [% IF entry.phys_item_count %]
3938                     / Physical item count: [% entry.phys_item_count %]
3939                 [% END %]
3940                 <br />
3941                 [% IF entry.cost_billed %]
3942                     Cost billed: [% entry.cost_billed %]
3943                     [% IF entry.billed_per_item %](per item)[% END %]
3944                     <br />
3945                 [% END %]
3946                 [% IF entry.actual_cost %]
3947                     Actual cost: [% entry.actual_cost %]<br />
3948                 [% END %]
3949                 [% IF entry.amount_paid %]
3950                     Amount paid: [% entry.amount_paid %]<br />
3951                 [% END %]
3952                 [% IF entry.note %]Note: [% entry.note %][% END %]
3953             </li>
3954         [% END %]
3955         [% FOR item IN invoice.items %]
3956             <li>
3957                 [% IF item.inv_item_type %]
3958                     Item Type: [% item.inv_item_type %]<br />
3959                 [% END %]
3960                 [% IF item.title %]Title/Description:
3961                     [% item.title %]<br />
3962                 [% END %]
3963                 [% IF item.author %]Author: [% item.author %]<br />[% END %]
3964                 [% IF item.purchase_order %]PO: [% item.purchase_order %]<br />[% END %]
3965                 [% IF item.note %]Note: [% item.note %]<br />[% END %]
3966                 [% IF item.cost_billed %]
3967                     Cost billed: [% item.cost_billed %]<br />
3968                 [% END %]
3969                 [% IF item.actual_cost %]
3970                     Actual cost: [% item.actual_cost %]<br />
3971                 [% END %]
3972                 [% IF item.amount_paid %]
3973                     Amount paid: [% item.amount_paid %]<br />
3974                 [% END %]
3975             </li>
3976         [% END %]
3977     </ul>
3978     <div>
3979         Amounts spent per fund:
3980         <table>
3981         [% FOR blob IN user_data %]
3982             <tr>
3983                 <th style="text-align: left;">[% blob.fund.code %] ([% blob.fund.year %]):</th>
3984                 <td>$[% blob.total %]</td>
3985             </tr>
3986         [% END %]
3987         </table>
3988     </div>
3989 </div>
3990 [% END %]$$ WHERE id = 22;
3991 INSERT INTO action_trigger.environment (event_def, path) VALUES
3992     (22, 'provider'),
3993     (22, 'provider.addresses'),
3994     (22, 'shipper'),
3995     (22, 'shipper.addresses'),
3996     (22, 'receiver'),
3997     (22, 'entries'),
3998     (22, 'entries.purchase_order'),
3999     (22, 'entries.lineitem'),
4000     (22, 'entries.lineitem.attributes'),
4001     (22, 'items')
4002 ;
4003
4004 INSERT INTO action_trigger.environment (event_def, path) VALUES 
4005   (23, 'provider.edi_default');
4006
4007 INSERT INTO action_trigger.validator (module, description) 
4008     VALUES (
4009         'Acq::PurchaseOrderEDIRequired',
4010         oils_i18n_gettext(
4011             'Acq::PurchaseOrderEDIRequired',
4012             'Purchase order is delivered via EDI',
4013             'atval',
4014             'description'
4015         )
4016     );
4017
4018 INSERT INTO action_trigger.reactor (module, description)
4019     VALUES (
4020         'GeneratePurchaseOrderJEDI',
4021         oils_i18n_gettext(
4022             'GeneratePurchaseOrderJEDI',
4023             'Creates purchase order JEDI (JSON EDI) for subsequent EDI processing',
4024             'atreact',
4025             'description'
4026         )
4027     );
4028
4029 UPDATE action_trigger.hook 
4030     SET 
4031         key = 'acqpo.activated', 
4032         passive = FALSE,
4033         description = oils_i18n_gettext(
4034             'acqpo.activated',
4035             'Purchase order was activated',
4036             'ath',
4037             'description'
4038         )
4039     WHERE key = 'format.po.jedi';
4040
4041 -- We just changed a key in action_trigger.hook.  Now redirect any
4042 -- child rows to point to the new key.  (There probably aren't any;
4043 -- this is just a precaution against possible local modifications.)
4044
4045 UPDATE action_trigger.event_definition
4046 SET hook = 'acqpo.activated'
4047 WHERE hook = 'format.po.jedi';
4048
4049 INSERT INTO action_trigger.reactor (module, description) VALUES (
4050     'AstCall', 'Possibly place a phone call with Asterisk'
4051 );
4052
4053 INSERT INTO
4054     action_trigger.event_definition (
4055         id, active, owner, name, hook, validator, reactor,
4056         cleanup_success, cleanup_failure, delay, delay_field, group_field,
4057         max_delay, granularity, usr_field, opt_in_setting, template
4058     ) VALUES (
4059         24,
4060         FALSE,
4061         1,
4062         'Telephone Overdue Notice',
4063         'checkout.due', 'NOOP_True', 'AstCall',
4064         DEFAULT, DEFAULT, '5 seconds', 'due_date', 'usr',
4065         DEFAULT, DEFAULT, DEFAULT, DEFAULT,
4066         $$
4067 [% phone = target.0.usr.day_phone | replace('[\s\-\(\)]', '') -%]
4068 [% IF phone.match('^[2-9]') %][% country = 1 %][% ELSE %][% country = '' %][% END -%]
4069 Channel: [% channel_prefix %]/[% country %][% phone %]
4070 Context: overdue-test
4071 MaxRetries: 1
4072 RetryTime: 60
4073 WaitTime: 30
4074 Extension: 10
4075 Archive: 1
4076 Set: eg_user_id=[% target.0.usr.id %]
4077 Set: items=[% target.size %]
4078 Set: titlestring=[% titles = [] %][% FOR circ IN target %][% titles.push(circ.target_copy.call_number.record.simple_record.title) %][% END %][% titles.join(". ") %]
4079 $$
4080     );
4081
4082 INSERT INTO
4083     action_trigger.environment (id, event_def, path)
4084     VALUES
4085         (DEFAULT, 24, 'target_copy.call_number.record.simple_record'),
4086         (DEFAULT, 24, 'usr')
4087     ;
4088
4089 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4090         'circ.format.history.email',
4091         'circ', 
4092         oils_i18n_gettext(
4093             'circ.format.history.email',
4094             'An email has been requested for a circ history.',
4095             'ath',
4096             'description'
4097         ), 
4098         FALSE
4099     )
4100     ,(
4101         'circ.format.history.print',
4102         'circ', 
4103         oils_i18n_gettext(
4104             'circ.format.history.print',
4105             'A circ history needs to be formatted for printing.',
4106             'ath',
4107             'description'
4108         ), 
4109         FALSE
4110     )
4111     ,(
4112         'ahr.format.history.email',
4113         'ahr', 
4114         oils_i18n_gettext(
4115             'ahr.format.history.email',
4116             'An email has been requested for a hold request history.',
4117             'ath',
4118             'description'
4119         ), 
4120         FALSE
4121     )
4122     ,(
4123         'ahr.format.history.print',
4124         'ahr', 
4125         oils_i18n_gettext(
4126             'ahr.format.history.print',
4127             'A hold request history needs to be formatted for printing.',
4128             'ath',
4129             'description'
4130         ), 
4131         FALSE
4132     )
4133
4134 ;
4135
4136 INSERT INTO action_trigger.event_definition (
4137         id,
4138         active,
4139         owner,
4140         name,
4141         hook,
4142         validator,
4143         reactor,
4144         group_field,
4145         granularity,
4146         template
4147     ) VALUES (
4148         25,
4149         TRUE,
4150         1,
4151         'circ.history.email',
4152         'circ.format.history.email',
4153         'NOOP_True',
4154         'SendEmail',
4155         'usr',
4156         NULL,
4157 $$
4158 [%- USE date -%]
4159 [%- SET user = target.0.usr -%]
4160 To: [%- params.recipient_email || user.email %]
4161 From: [%- params.sender_email || default_sender %]
4162 Subject: Circulation History
4163
4164     [% FOR circ IN target %]
4165             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4166             Barcode: [% circ.target_copy.barcode %]
4167             Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]
4168             Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]
4169             Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]
4170     [% END %]
4171 $$
4172     )
4173     ,(
4174         26,
4175         TRUE,
4176         1,
4177         'circ.history.print',
4178         'circ.format.history.print',
4179         'NOOP_True',
4180         'ProcessTemplate',
4181         'usr',
4182         'print-on-demand',
4183 $$
4184 [%- USE date -%]
4185 <div>
4186     <style> li { padding: 8px; margin 5px; }</style>
4187     <div>[% date.format %]</div>
4188     <br/>
4189
4190     [% user.family_name %], [% user.first_given_name %]
4191     <ol>
4192     [% FOR circ IN target %]
4193         <li>
4194             <div>[% helpers.get_copy_bib_basics(circ.target_copy.id).title %]</div>
4195             <div>Barcode: [% circ.target_copy.barcode %]</div>
4196             <div>Checked Out: [% date.format(helpers.format_date(circ.xact_start), '%Y-%m-%d') %]</div>
4197             <div>Due Date: [% date.format(helpers.format_date(circ.due_date), '%Y-%m-%d') %]</div>
4198             <div>Returned: [% date.format(helpers.format_date(circ.checkin_time), '%Y-%m-%d') %]</div>
4199         </li>
4200     [% END %]
4201     </ol>
4202 </div>
4203 $$
4204     )
4205     ,(
4206         27,
4207         TRUE,
4208         1,
4209         'ahr.history.email',
4210         'ahr.format.history.email',
4211         'NOOP_True',
4212         'SendEmail',
4213         'usr',
4214         NULL,
4215 $$
4216 [%- USE date -%]
4217 [%- SET user = target.0.usr -%]
4218 To: [%- params.recipient_email || user.email %]
4219 From: [%- params.sender_email || default_sender %]
4220 Subject: Hold Request History
4221
4222     [% FOR hold IN target %]
4223             [% helpers.get_copy_bib_basics(hold.current_copy.id).title %]
4224             Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]
4225             [% IF hold.fulfillment_time %]Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %][% END %]
4226     [% END %]
4227 $$
4228     )
4229     ,(
4230         28,
4231         TRUE,
4232         1,
4233         'ahr.history.print',
4234         'ahr.format.history.print',
4235         'NOOP_True',
4236         'ProcessTemplate',
4237         'usr',
4238         'print-on-demand',
4239 $$
4240 [%- USE date -%]
4241 <div>
4242     <style> li { padding: 8px; margin 5px; }</style>
4243     <div>[% date.format %]</div>
4244     <br/>
4245
4246     [% user.family_name %], [% user.first_given_name %]
4247     <ol>
4248     [% FOR hold IN target %]
4249         <li>
4250             <div>[% helpers.get_copy_bib_basics(hold.current_copy.id).title %]</div>
4251             <div>Requested: [% date.format(helpers.format_date(hold.request_time), '%Y-%m-%d') %]</div>
4252             [% IF hold.fulfillment_time %]<div>Fulfilled: [% date.format(helpers.format_date(hold.fulfillment_time), '%Y-%m-%d') %]</div>[% END %]
4253         </li>
4254     [% END %]
4255     </ol>
4256 </div>
4257 $$
4258     )
4259
4260 ;
4261
4262 INSERT INTO action_trigger.environment (
4263         event_def,
4264         path
4265     ) VALUES 
4266          ( 25, 'target_copy')
4267         ,( 25, 'usr' )
4268         ,( 26, 'target_copy' )
4269         ,( 26, 'usr' )
4270         ,( 27, 'current_copy' )
4271         ,( 27, 'usr' )
4272         ,( 28, 'current_copy' )
4273         ,( 28, 'usr' )
4274 ;
4275
4276 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4277         'money.format.payment_receipt.email',
4278         'mp', 
4279         oils_i18n_gettext(
4280             'money.format.payment_receipt.email',
4281             'An email has been requested for a payment receipt.',
4282             'ath',
4283             'description'
4284         ), 
4285         FALSE
4286     )
4287     ,(
4288         'money.format.payment_receipt.print',
4289         'mp', 
4290         oils_i18n_gettext(
4291             'money.format.payment_receipt.print',
4292             'A payment receipt needs to be formatted for printing.',
4293             'ath',
4294             'description'
4295         ), 
4296         FALSE
4297     )
4298 ;
4299
4300 INSERT INTO action_trigger.event_definition (
4301         id,
4302         active,
4303         owner,
4304         name,
4305         hook,
4306         validator,
4307         reactor,
4308         group_field,
4309         granularity,
4310         template
4311     ) VALUES (
4312         29,
4313         TRUE,
4314         1,
4315         'money.payment_receipt.email',
4316         'money.format.payment_receipt.email',
4317         'NOOP_True',
4318         'SendEmail',
4319         'xact.usr',
4320         NULL,
4321 $$
4322 [%- USE date -%]
4323 [%- SET user = target.0.xact.usr -%]
4324 To: [%- params.recipient_email || user.email %]
4325 From: [%- params.sender_email || default_sender %]
4326 Subject: Payment Receipt
4327
4328 [% date.format -%]
4329 [%- SET xact_mp_hash = {} -%]
4330 [%- FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions -%]
4331     [%- SET xact_id = mp.xact.id -%]
4332     [%- IF ! xact_mp_hash.defined( xact_id ) -%][%- xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } -%][%- END -%]
4333     [%- xact_mp_hash.$xact_id.payments.push(mp) -%]
4334 [%- END -%]
4335 [%- FOR xact_id IN xact_mp_hash.keys.sort -%]
4336     [%- SET xact = xact_mp_hash.$xact_id.xact %]
4337 Transaction ID: [% xact_id %]
4338     [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4339     [% ELSE %]Miscellaneous
4340     [% END %]
4341     Line item billings:
4342         [%- SET mb_type_hash = {} -%]
4343         [%- FOR mb IN xact.billings %][%# Group billings by their btype -%]
4344             [%- IF mb.voided == 'f' -%]
4345                 [%- SET mb_type = mb.btype.id -%]
4346                 [%- IF ! mb_type_hash.defined( mb_type ) -%][%- mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } -%][%- END -%]
4347                 [%- IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) -%][%- mb_type_hash.$mb_type.first_ts = mb.billing_ts -%][%- END -%]
4348                 [%- mb_type_hash.$mb_type.last_ts = mb.billing_ts -%]
4349                 [%- mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount -%]
4350                 [%- mb_type_hash.$mb_type.billings.push( mb ) -%]
4351             [%- END -%]
4352         [%- END -%]
4353         [%- FOR mb_type IN mb_type_hash.keys.sort -%]
4354             [%- IF mb_type == 1 %][%-# Consolidated view of overdue billings -%]
4355                 $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4356                     on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4357             [%- ELSE -%][%# all other billings show individually %]
4358                 [% FOR mb IN mb_type_hash.$mb_type.billings %]
4359                     $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4360                 [% END %]
4361             [% END %]
4362         [% END %]
4363     Line item payments:
4364         [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4365             Payment ID: [% mp.id %]
4366                 Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4367                     [% CASE "cash_payment" %]cash
4368                     [% CASE "check_payment" %]check
4369                     [% CASE "credit_card_payment" %]credit card (
4370                         [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4371                         [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4372                         [% cc_chunks.last -%]
4373                         exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4374                     )
4375                     [% CASE "credit_payment" %]credit
4376                     [% CASE "forgive_payment" %]forgiveness
4377                     [% CASE "goods_payment" %]goods
4378                     [% CASE "work_payment" %]work
4379                 [%- END %] on [% mp.payment_ts %] [% mp.note %]
4380         [% END %]
4381 [% END %]
4382 $$
4383     )
4384     ,(
4385         30,
4386         TRUE,
4387         1,
4388         'money.payment_receipt.print',
4389         'money.format.payment_receipt.print',
4390         'NOOP_True',
4391         'ProcessTemplate',
4392         'xact.usr',
4393         'print-on-demand',
4394 $$
4395 [%- USE date -%][%- SET user = target.0.xact.usr -%]
4396 <div style="li { padding: 8px; margin 5px; }">
4397     <div>[% date.format %]</div><br/>
4398     <ol>
4399     [% SET xact_mp_hash = {} %]
4400     [% FOR mp IN target %][%# Template is hooked around payments, but let us make the receipt focused on transactions %]
4401         [% SET xact_id = mp.xact.id %]
4402         [% IF ! xact_mp_hash.defined( xact_id ) %][% xact_mp_hash.$xact_id = { 'xact' => mp.xact, 'payments' => [] } %][% END %]
4403         [% xact_mp_hash.$xact_id.payments.push(mp) %]
4404     [% END %]
4405     [% FOR xact_id IN xact_mp_hash.keys.sort %]
4406         [% SET xact = xact_mp_hash.$xact_id.xact %]
4407         <li>Transaction ID: [% xact_id %]
4408             [% IF xact.circulation %][% helpers.get_copy_bib_basics(xact.circulation.target_copy).title %]
4409             [% ELSE %]Miscellaneous
4410             [% END %]
4411             Line item billings:<ol>
4412                 [% SET mb_type_hash = {} %]
4413                 [% FOR mb IN xact.billings %][%# Group billings by their btype %]
4414                     [% IF mb.voided == 'f' %]
4415                         [% SET mb_type = mb.btype.id %]
4416                         [% IF ! mb_type_hash.defined( mb_type ) %][% mb_type_hash.$mb_type = { 'sum' => 0.00, 'billings' => [] } %][% END %]
4417                         [% IF ! mb_type_hash.$mb_type.defined( 'first_ts' ) %][% mb_type_hash.$mb_type.first_ts = mb.billing_ts %][% END %]
4418                         [% mb_type_hash.$mb_type.last_ts = mb.billing_ts %]
4419                         [% mb_type_hash.$mb_type.sum = mb_type_hash.$mb_type.sum + mb.amount %]
4420                         [% mb_type_hash.$mb_type.billings.push( mb ) %]
4421                     [% END %]
4422                 [% END %]
4423                 [% FOR mb_type IN mb_type_hash.keys.sort %]
4424                     <li>[% IF mb_type == 1 %][%# Consolidated view of overdue billings %]
4425                         $[% mb_type_hash.$mb_type.sum %] for [% mb_type_hash.$mb_type.billings.0.btype.name %] 
4426                             on [% mb_type_hash.$mb_type.first_ts %] through [% mb_type_hash.$mb_type.last_ts %]
4427                     [% ELSE %][%# all other billings show individually %]
4428                         [% FOR mb IN mb_type_hash.$mb_type.billings %]
4429                             $[% mb.amount %] for [% mb.btype.name %] on [% mb.billing_ts %] [% mb.note %]
4430                         [% END %]
4431                     [% END %]</li>
4432                 [% END %]
4433             </ol>
4434             Line item payments:<ol>
4435                 [% FOR mp IN xact_mp_hash.$xact_id.payments %]
4436                     <li>Payment ID: [% mp.id %]
4437                         Paid [% mp.amount %] via [% SWITCH mp.payment_type -%]
4438                             [% CASE "cash_payment" %]cash
4439                             [% CASE "check_payment" %]check
4440                             [% CASE "credit_card_payment" %]credit card (
4441                                 [%- SET cc_chunks = mp.credit_card_payment.cc_number.replace(' ','').chunk(4); -%]
4442                                 [%- cc_chunks.slice(0, -1+cc_chunks.max).join.replace('\S','X') -%] 
4443                                 [% cc_chunks.last -%]
4444                                 exp [% mp.credit_card_payment.expire_month %]/[% mp.credit_card_payment.expire_year -%]
4445                             )
4446                             [% CASE "credit_payment" %]credit
4447                             [% CASE "forgive_payment" %]forgiveness
4448                             [% CASE "goods_payment" %]goods
4449                             [% CASE "work_payment" %]work
4450                         [%- END %] on [% mp.payment_ts %] [% mp.note %]
4451                     </li>
4452                 [% END %]
4453             </ol>
4454         </li>
4455     [% END %]
4456     </ol>
4457 </div>
4458 $$
4459     )
4460 ;
4461
4462 INSERT INTO action_trigger.environment (
4463         event_def,
4464         path
4465     ) VALUES -- for fleshing mp objects
4466          ( 29, 'xact')
4467         ,( 29, 'xact.usr')
4468         ,( 29, 'xact.grocery' )
4469         ,( 29, 'xact.circulation' )
4470         ,( 29, 'xact.summary' )
4471         ,( 30, 'xact')
4472         ,( 30, 'xact.usr')
4473         ,( 30, 'xact.grocery' )
4474         ,( 30, 'xact.circulation' )
4475         ,( 30, 'xact.summary' )
4476 ;
4477
4478 INSERT INTO action_trigger.cleanup ( module, description ) VALUES (
4479     'DeleteTempBiblioBucket',
4480     oils_i18n_gettext(
4481         'DeleteTempBiblioBucket',
4482         'Deletes a cbreb object used as a target if it has a btype of "temp"',
4483         'atclean',
4484         'description'
4485     )
4486 );
4487
4488 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
4489         'biblio.format.record_entry.email',
4490         'cbreb', 
4491         oils_i18n_gettext(
4492             'biblio.format.record_entry.email',
4493             'An email has been requested for one or more biblio record entries.',
4494             'ath',
4495             'description'
4496         ), 
4497         FALSE
4498     )
4499     ,(
4500         'biblio.format.record_entry.print',
4501         'cbreb', 
4502         oils_i18n_gettext(
4503             'biblio.format.record_entry.print',
4504             'One or more biblio record entries need to be formatted for printing.',
4505             'ath',
4506             'description'
4507         ), 
4508         FALSE
4509     )
4510 ;
4511
4512 INSERT INTO action_trigger.event_definition (
4513         id,
4514         active,
4515         owner,
4516         name,
4517         hook,
4518         validator,
4519         reactor,
4520         cleanup_success,
4521         cleanup_failure,
4522         group_field,
4523         granularity,
4524         template
4525     ) VALUES (
4526         31,
4527         TRUE,
4528         1,
4529         'biblio.record_entry.email',
4530         'biblio.format.record_entry.email',
4531         'NOOP_True',
4532         'SendEmail',
4533         'DeleteTempBiblioBucket',
4534         'DeleteTempBiblioBucket',
4535         'owner',
4536         NULL,
4537 $$
4538 [%- USE date -%]
4539 [%- SET user = target.0.owner -%]
4540 To: [%- params.recipient_email || user.email %]
4541 From: [%- params.sender_email || default_sender %]
4542 Subject: Bibliographic Records
4543
4544     [% FOR cbreb IN target %]
4545     [% FOR cbrebi IN cbreb.items %]
4546         Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]
4547         Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]
4548         Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]
4549         Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4550
4551     [% END %]
4552     [% END %]
4553 $$
4554     )
4555     ,(
4556         32,
4557         TRUE,
4558         1,
4559         'biblio.record_entry.print',
4560         'biblio.format.record_entry.print',
4561         'NOOP_True',
4562         'ProcessTemplate',
4563         'DeleteTempBiblioBucket',
4564         'DeleteTempBiblioBucket',
4565         'owner',
4566         'print-on-demand',
4567 $$
4568 [%- USE date -%]
4569 <div>
4570     <style> li { padding: 8px; margin 5px; }</style>
4571     <ol>
4572     [% FOR cbreb IN target %]
4573     [% FOR cbrebi IN cbreb.items %]
4574         <li>Bib ID# [% cbrebi.target_biblio_record_entry.id %] ISBN: [% crebi.target_biblio_record_entry.simple_record.isbn %]<br />
4575             Title: [% cbrebi.target_biblio_record_entry.simple_record.title %]<br />
4576             Author: [% cbrebi.target_biblio_record_entry.simple_record.author %]<br />
4577             Publication Year: [% cbrebi.target_biblio_record_entry.simple_record.pubdate %]
4578         </li>
4579     [% END %]
4580     [% END %]
4581     </ol>
4582 </div>
4583 $$
4584     )
4585 ;
4586
4587 INSERT INTO action_trigger.environment (
4588         event_def,
4589         path
4590     ) VALUES -- for fleshing cbreb objects
4591          ( 31, 'owner' )
4592         ,( 31, 'items' )
4593         ,( 31, 'items.target_biblio_record_entry' )
4594         ,( 31, 'items.target_biblio_record_entry.simple_record' )
4595         ,( 31, 'items.target_biblio_record_entry.call_numbers' )
4596         ,( 31, 'items.target_biblio_record_entry.fixed_fields' )
4597         ,( 31, 'items.target_biblio_record_entry.notes' )
4598         ,( 31, 'items.target_biblio_record_entry.full_record_entries' )
4599         ,( 32, 'owner' )
4600         ,( 32, 'items' )
4601         ,( 32, 'items.target_biblio_record_entry' )
4602         ,( 32, 'items.target_biblio_record_entry.simple_record' )
4603         ,( 32, 'items.target_biblio_record_entry.call_numbers' )
4604         ,( 32, 'items.target_biblio_record_entry.fixed_fields' )
4605         ,( 32, 'items.target_biblio_record_entry.notes' )
4606         ,( 32, 'items.target_biblio_record_entry.full_record_entries' )
4607 ;
4608
4609 INSERT INTO action_trigger.environment (
4610         event_def,
4611         path
4612     ) VALUES -- for fleshing mp objects
4613          ( 29, 'credit_card_payment')
4614         ,( 29, 'xact.billings')
4615         ,( 29, 'xact.billings.btype')
4616         ,( 30, 'credit_card_payment')
4617         ,( 30, 'xact.billings')
4618         ,( 30, 'xact.billings.btype')
4619 ;
4620
4621 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
4622     (   'circ.format.missing_pieces.slip.print',
4623         'circ', 
4624         oils_i18n_gettext(
4625             'circ.format.missing_pieces.slip.print',
4626             'A missing pieces slip needs to be formatted for printing.',
4627             'ath',
4628             'description'
4629         ), 
4630         FALSE
4631     )
4632     ,(  'circ.format.missing_pieces.letter.print',
4633         'circ', 
4634         oils_i18n_gettext(
4635             'circ.format.missing_pieces.letter.print',
4636             'A missing pieces patron letter needs to be formatted for printing.',
4637             'ath',
4638             'description'
4639         ), 
4640         FALSE
4641     )
4642 ;
4643
4644 INSERT INTO action_trigger.event_definition (
4645         id,
4646         active,
4647         owner,
4648         name,
4649         hook,
4650         validator,
4651         reactor,
4652         group_field,
4653         granularity,
4654         template
4655     ) VALUES (
4656         33,
4657         TRUE,
4658         1,
4659         'circ.missing_pieces.slip.print',
4660         'circ.format.missing_pieces.slip.print',
4661         'NOOP_True',
4662         'ProcessTemplate',
4663         'usr',
4664         'print-on-demand',
4665 $$
4666 [%- USE date -%]
4667 [%- SET user = target.0.usr -%]
4668 <div style="li { padding: 8px; margin 5px; }">
4669     <div>[% date.format %]</div><br/>
4670     Missing pieces for:
4671     <ol>
4672     [% FOR circ IN target %]
4673         <li>Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]<br />
4674             [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4675         </li>
4676     [% END %]
4677     </ol>
4678 </div>
4679 $$
4680     )
4681     ,(
4682         34,
4683         TRUE,
4684         1,
4685         'circ.missing_pieces.letter.print',
4686         'circ.format.missing_pieces.letter.print',
4687         'NOOP_True',
4688         'ProcessTemplate',
4689         'usr',
4690         'print-on-demand',
4691 $$
4692 [%- USE date -%]
4693 [%- SET user = target.0.usr -%]
4694 [% date.format %]
4695 Dear [% user.prefix %] [% user.first_given_name %] [% user.family_name %],
4696
4697 We are missing pieces for the following returned items:
4698 [% FOR circ IN target %]
4699 Barcode: [% circ.target_copy.barcode %] Transaction ID: [% circ.id %] Due: [% circ.due_date.format %]
4700 [% helpers.get_copy_bib_basics(circ.target_copy.id).title %]
4701 [% END %]
4702
4703 Please return these pieces as soon as possible.
4704
4705 Thanks!
4706
4707 Library Staff
4708 $$
4709     )
4710 ;
4711
4712 INSERT INTO action_trigger.environment (
4713         event_def,
4714         path
4715     ) VALUES -- for fleshing circ objects
4716          ( 33, 'usr')
4717         ,( 33, 'target_copy')
4718         ,( 33, 'target_copy.circ_lib')
4719         ,( 33, 'target_copy.circ_lib.mailing_address')
4720         ,( 33, 'target_copy.circ_lib.billing_address')
4721         ,( 33, 'target_copy.call_number')
4722         ,( 33, 'target_copy.call_number.owning_lib')
4723         ,( 33, 'target_copy.call_number.owning_lib.mailing_address')
4724         ,( 33, 'target_copy.call_number.owning_lib.billing_address')
4725         ,( 33, 'circ_lib')
4726         ,( 33, 'circ_lib.mailing_address')
4727         ,( 33, 'circ_lib.billing_address')
4728         ,( 34, 'usr')
4729         ,( 34, 'target_copy')
4730         ,( 34, 'target_copy.circ_lib')
4731         ,( 34, 'target_copy.circ_lib.mailing_address')
4732         ,( 34, 'target_copy.circ_lib.billing_address')
4733         ,( 34, 'target_copy.call_number')
4734         ,( 34, 'target_copy.call_number.owning_lib')
4735         ,( 34, 'target_copy.call_number.owning_lib.mailing_address')
4736         ,( 34, 'target_copy.call_number.owning_lib.billing_address')
4737         ,( 34, 'circ_lib')
4738         ,( 34, 'circ_lib.mailing_address')
4739         ,( 34, 'circ_lib.billing_address')
4740 ;
4741
4742 INSERT INTO action_trigger.hook (key,core_type,description,passive) 
4743     VALUES (   
4744         'ahr.format.pull_list',
4745         'ahr', 
4746         oils_i18n_gettext(
4747             'ahr.format.pull_list',
4748             'Format holds pull list for printing',
4749             'ath',
4750             'description'
4751         ), 
4752         FALSE
4753     );
4754
4755 INSERT INTO action_trigger.event_definition (
4756         id,
4757         active,
4758         owner,
4759         name,
4760         hook,
4761         validator,
4762         reactor,
4763         group_field,
4764         granularity,
4765         template
4766     ) VALUES (
4767         35,
4768         TRUE,
4769         1,
4770         'Holds Pull List',
4771         'ahr.format.pull_list',
4772         'NOOP_True',
4773         'ProcessTemplate',
4774         'pickup_lib',
4775         'print-on-demand',
4776 $$
4777 [%- USE date -%]
4778 <style>
4779     table { border-collapse: collapse; } 
4780     td { padding: 5px; border-bottom: 1px solid #888; } 
4781     th { font-weight: bold; }
4782 </style>
4783 [% 
4784     # Sort the holds into copy-location buckets
4785     # In the main print loop, sort each bucket by callnumber before printing
4786     SET holds_list = [];
4787     SET loc_data = [];
4788     SET current_location = target.0.current_copy.location.id;
4789     FOR hold IN target;
4790         IF current_location != hold.current_copy.location.id;
4791             SET current_location = hold.current_copy.location.id;
4792             holds_list.push(loc_data);
4793             SET loc_data = [];
4794         END;
4795         SET hold_data = {
4796             'hold' => hold,
4797             'callnumber' => hold.current_copy.call_number.label
4798         };
4799         loc_data.push(hold_data);
4800     END;
4801     holds_list.push(loc_data)
4802 %]
4803 <table>
4804     <thead>
4805         <tr>
4806             <th>Title</th>
4807             <th>Author</th>
4808             <th>Shelving Location</th>
4809             <th>Call Number</th>
4810             <th>Barcode</th>
4811             <th>Patron</th>
4812         </tr>
4813     </thead>
4814     <tbody>
4815     [% FOR loc_data IN holds_list  %]
4816         [% FOR hold_data IN loc_data.sort('callnumber') %]
4817             [% 
4818                 SET hold = hold_data.hold;
4819                 SET copy_data = helpers.get_copy_bib_basics(hold.current_copy.id);
4820             %]
4821             <tr>
4822                 <td>[% copy_data.title | truncate %]</td>
4823                 <td>[% copy_data.author | truncate %]</td>
4824                 <td>[% hold.current_copy.location.name %]</td>
4825                 <td>[% hold.current_copy.call_number.label %]</td>
4826                 <td>[% hold.current_copy.barcode %]</td>
4827                 <td>[% hold.usr.card.barcode %]</td>
4828             </tr>
4829         [% END %]
4830     [% END %]
4831     <tbody>
4832 </table>
4833 $$
4834 );
4835
4836 INSERT INTO action_trigger.environment (
4837         event_def,
4838         path
4839     ) VALUES
4840         (35, 'current_copy.location'),
4841         (35, 'current_copy.call_number'),
4842         (35, 'usr.card'),
4843         (35, 'pickup_lib')
4844 ;
4845
4846 INSERT INTO action_trigger.validator (module, description) VALUES ( 
4847     'HoldIsCancelled', 
4848     oils_i18n_gettext( 
4849         'HoldIsCancelled', 
4850         'Check whether a hold request is cancelled.', 
4851         'atval', 
4852         'description' 
4853     ) 
4854 );
4855
4856 -- Create the query schema, and the tables and views therein
4857
4858 DROP SCHEMA IF EXISTS sql CASCADE;
4859 DROP SCHEMA IF EXISTS query CASCADE;
4860
4861 CREATE SCHEMA query;
4862
4863 CREATE TABLE query.datatype (
4864         id              SERIAL            PRIMARY KEY,
4865         datatype_name   TEXT              NOT NULL UNIQUE,
4866         is_numeric      BOOL              NOT NULL DEFAULT FALSE,
4867         is_composite    BOOL              NOT NULL DEFAULT FALSE,
4868         CONSTRAINT qdt_comp_not_num CHECK
4869         ( is_numeric IS FALSE OR is_composite IS FALSE )
4870 );
4871
4872 -- Define the most common datatypes in query.datatype.  Note that none of
4873 -- these stock datatypes specifies a width or precision.
4874
4875 -- Also: set the sequence for query.datatype to 1000, leaving plenty of
4876 -- room for more stock datatypes if we ever want to add them.
4877
4878 SELECT setval( 'query.datatype_id_seq', 1000 );
4879
4880 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4881   VALUES (1, 'SMALLINT', true);
4882  
4883 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4884   VALUES (2, 'INTEGER', true);
4885  
4886 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4887   VALUES (3, 'BIGINT', true);
4888  
4889 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4890   VALUES (4, 'DECIMAL', true);
4891  
4892 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4893   VALUES (5, 'NUMERIC', true);
4894  
4895 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4896   VALUES (6, 'REAL', true);
4897  
4898 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4899   VALUES (7, 'DOUBLE PRECISION', true);
4900  
4901 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4902   VALUES (8, 'SERIAL', true);
4903  
4904 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4905   VALUES (9, 'BIGSERIAL', true);
4906  
4907 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4908   VALUES (10, 'MONEY', false);
4909  
4910 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4911   VALUES (11, 'VARCHAR', false);
4912  
4913 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4914   VALUES (12, 'CHAR', false);
4915  
4916 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4917   VALUES (13, 'TEXT', false);
4918  
4919 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4920   VALUES (14, '"char"', false);
4921  
4922 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4923   VALUES (15, 'NAME', false);
4924  
4925 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4926   VALUES (16, 'BYTEA', false);
4927  
4928 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4929   VALUES (17, 'TIMESTAMP WITHOUT TIME ZONE', false);
4930  
4931 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4932   VALUES (18, 'TIMESTAMP WITH TIME ZONE', false);
4933  
4934 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4935   VALUES (19, 'DATE', false);
4936  
4937 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4938   VALUES (20, 'TIME WITHOUT TIME ZONE', false);
4939  
4940 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4941   VALUES (21, 'TIME WITH TIME ZONE', false);
4942  
4943 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4944   VALUES (22, 'INTERVAL', false);
4945  
4946 INSERT INTO query.datatype (id, datatype_name, is_numeric )
4947   VALUES (23, 'BOOLEAN', false);
4948  
4949 CREATE TABLE query.subfield (
4950         id              SERIAL            PRIMARY KEY,
4951         composite_type  INT               NOT NULL
4952                                           REFERENCES query.datatype(id)
4953                                           ON DELETE CASCADE
4954                                           DEFERRABLE INITIALLY DEFERRED,
4955         seq_no          INT               NOT NULL
4956                                           CONSTRAINT qsf_pos_seq_no
4957                                           CHECK( seq_no > 0 ),
4958         subfield_type   INT               NOT NULL
4959                                           REFERENCES query.datatype(id)
4960                                           DEFERRABLE INITIALLY DEFERRED,
4961         CONSTRAINT qsf_datatype_seq_no UNIQUE (composite_type, seq_no)
4962 );
4963
4964 CREATE TABLE query.function_sig (
4965         id              SERIAL            PRIMARY KEY,
4966         function_name   TEXT              NOT NULL,
4967         return_type     INT               REFERENCES query.datatype(id)
4968                                           DEFERRABLE INITIALLY DEFERRED,
4969         is_aggregate    BOOL              NOT NULL DEFAULT FALSE,
4970         CONSTRAINT qfd_rtn_or_aggr CHECK
4971         ( return_type IS NULL OR is_aggregate = FALSE )
4972 );
4973
4974 CREATE INDEX query_function_sig_name_idx 
4975         ON query.function_sig (function_name);
4976
4977 CREATE TABLE query.function_param_def (
4978         id              SERIAL            PRIMARY KEY,
4979         function_id     INT               NOT NULL
4980                                           REFERENCES query.function_sig( id )
4981                                           ON DELETE CASCADE
4982                                           DEFERRABLE INITIALLY DEFERRED,
4983         seq_no          INT               NOT NULL
4984                                           CONSTRAINT qfpd_pos_seq_no CHECK
4985                                           ( seq_no > 0 ),
4986         datatype        INT               NOT NULL
4987                                           REFERENCES query.datatype( id )
4988                                           DEFERRABLE INITIALLY DEFERRED,
4989         CONSTRAINT qfpd_function_param_seq UNIQUE (function_id, seq_no)
4990 );
4991
4992 CREATE TABLE  query.stored_query (
4993         id            SERIAL         PRIMARY KEY,
4994         type          TEXT           NOT NULL CONSTRAINT query_type CHECK
4995                                      ( type IN ( 'SELECT', 'UNION', 'INTERSECT', 'EXCEPT' ) ),
4996         use_all       BOOLEAN        NOT NULL DEFAULT FALSE,
4997         use_distinct  BOOLEAN        NOT NULL DEFAULT FALSE,
4998         from_clause   INT            , --REFERENCES query.from_clause
4999                                      --DEFERRABLE INITIALLY DEFERRED,
5000         where_clause  INT            , --REFERENCES query.expression
5001                                      --DEFERRABLE INITIALLY DEFERRED,
5002         having_clause INT            , --REFERENCES query.expression
5003                                      --DEFERRABLE INITIALLY DEFERRED
5004         limit_count   INT            , --REFERENCES query.expression( id )
5005                                      --DEFERRABLE INITIALLY DEFERRED,
5006         offset_count  INT            --REFERENCES query.expression( id )
5007                                      --DEFERRABLE INITIALLY DEFERRED
5008 );
5009
5010 -- (Foreign keys to be defined later after other tables are created)
5011
5012 CREATE TABLE query.query_sequence (
5013         id              SERIAL            PRIMARY KEY,
5014         parent_query    INT               NOT NULL
5015                                           REFERENCES query.stored_query
5016                                                                           ON DELETE CASCADE
5017                                                                           DEFERRABLE INITIALLY DEFERRED,
5018         seq_no          INT               NOT NULL,
5019         child_query     INT               NOT NULL
5020                                           REFERENCES query.stored_query
5021                                                                           ON DELETE CASCADE
5022                                                                           DEFERRABLE INITIALLY DEFERRED,
5023         CONSTRAINT query_query_seq UNIQUE( parent_query, seq_no )
5024 );
5025
5026 CREATE TABLE query.bind_variable (
5027         name          TEXT             PRIMARY KEY,
5028         type          TEXT             NOT NULL
5029                                            CONSTRAINT bind_variable_type CHECK
5030                                            ( type in ( 'string', 'number', 'string_list', 'number_list' )),
5031         description   TEXT             NOT NULL,
5032         default_value TEXT,            -- to be encoded in JSON
5033         label         TEXT             NOT NULL
5034 );
5035
5036 CREATE TABLE query.expression (
5037         id            SERIAL        PRIMARY KEY,
5038         type          TEXT          NOT NULL CONSTRAINT expression_type CHECK
5039                                     ( type IN (
5040                                     'xbet',    -- between
5041                                     'xbind',   -- bind variable
5042                                     'xbool',   -- boolean
5043                                     'xcase',   -- case
5044                                     'xcast',   -- cast
5045                                     'xcol',    -- column
5046                                     'xex',     -- exists
5047                                     'xfunc',   -- function
5048                                     'xin',     -- in
5049                                     'xisnull', -- is null
5050                                     'xnull',   -- null
5051                                     'xnum',    -- number
5052                                     'xop',     -- operator
5053                                     'xser',    -- series
5054                                     'xstr',    -- string
5055                                     'xsubq'    -- subquery
5056                                                                 ) ),
5057         parenthesize  BOOL          NOT NULL DEFAULT FALSE,
5058         parent_expr   INT           REFERENCES query.expression
5059                                     ON DELETE CASCADE
5060                                     DEFERRABLE INITIALLY DEFERRED,
5061         seq_no        INT           NOT NULL DEFAULT 1,
5062         literal       TEXT,
5063         table_alias   TEXT,
5064         column_name   TEXT,
5065         left_operand  INT           REFERENCES query.expression
5066                                     DEFERRABLE INITIALLY DEFERRED,
5067         operator      TEXT,
5068         right_operand INT           REFERENCES query.expression
5069                                     DEFERRABLE INITIALLY DEFERRED,
5070         function_id   INT           REFERENCES query.function_sig
5071                                     DEFERRABLE INITIALLY DEFERRED,
5072         subquery      INT           REFERENCES query.stored_query
5073                                     DEFERRABLE INITIALLY DEFERRED,
5074         cast_type     INT           REFERENCES query.datatype
5075                                     DEFERRABLE INITIALLY DEFERRED,
5076         negate        BOOL          NOT NULL DEFAULT FALSE,
5077         bind_variable TEXT          REFERENCES query.bind_variable
5078                                         DEFERRABLE INITIALLY DEFERRED
5079 );
5080
5081 CREATE UNIQUE INDEX query_expr_parent_seq
5082         ON query.expression( parent_expr, seq_no )
5083         WHERE parent_expr IS NOT NULL;
5084
5085 -- Due to some circular references, the following foreign key definitions
5086 -- had to be deferred until query.expression existed:
5087
5088 ALTER TABLE query.stored_query
5089         ADD FOREIGN KEY ( where_clause )
5090         REFERENCES query.expression( id )
5091         DEFERRABLE INITIALLY DEFERRED;
5092
5093 ALTER TABLE query.stored_query
5094         ADD FOREIGN KEY ( having_clause )
5095         REFERENCES query.expression( id )
5096         DEFERRABLE INITIALLY DEFERRED;
5097
5098 ALTER TABLE query.stored_query
5099     ADD FOREIGN KEY ( limit_count )
5100     REFERENCES query.expression( id )
5101     DEFERRABLE INITIALLY DEFERRED;
5102
5103 ALTER TABLE query.stored_query
5104     ADD FOREIGN KEY ( offset_count )
5105     REFERENCES query.expression( id )
5106     DEFERRABLE INITIALLY DEFERRED;
5107
5108 CREATE TABLE query.case_branch (
5109         id            SERIAL        PRIMARY KEY,
5110         parent_expr   INT           NOT NULL REFERENCES query.expression
5111                                     ON DELETE CASCADE
5112                                     DEFERRABLE INITIALLY DEFERRED,
5113         seq_no        INT           NOT NULL,
5114         condition     INT           REFERENCES query.expression
5115                                     DEFERRABLE INITIALLY DEFERRED,
5116         result        INT           NOT NULL REFERENCES query.expression
5117                                     DEFERRABLE INITIALLY DEFERRED,
5118         CONSTRAINT case_branch_parent_seq UNIQUE (parent_expr, seq_no)
5119 );
5120
5121 CREATE TABLE query.from_relation (
5122         id               SERIAL        PRIMARY KEY,
5123         type             TEXT          NOT NULL CONSTRAINT relation_type CHECK (
5124                                            type IN ( 'RELATION', 'SUBQUERY', 'FUNCTION' ) ),
5125         table_name       TEXT,
5126         class_name       TEXT,
5127         subquery         INT           REFERENCES query.stored_query,
5128         function_call    INT           REFERENCES query.expression,
5129         table_alias      TEXT,
5130         parent_relation  INT           REFERENCES query.from_relation
5131                                        ON DELETE CASCADE
5132                                        DEFERRABLE INITIALLY DEFERRED,
5133         seq_no           INT           NOT NULL DEFAULT 1,
5134         join_type        TEXT          CONSTRAINT good_join_type CHECK (
5135                                            join_type IS NULL OR join_type IN
5136                                            ( 'INNER', 'LEFT', 'RIGHT', 'FULL' )
5137                                        ),
5138         on_clause        INT           REFERENCES query.expression
5139                                        DEFERRABLE INITIALLY DEFERRED,
5140         CONSTRAINT join_or_core CHECK (
5141         ( parent_relation IS NULL AND join_type IS NULL
5142           AND on_clause IS NULL )
5143         OR
5144         ( parent_relation IS NOT NULL AND join_type IS NOT NULL
5145           AND on_clause IS NOT NULL )
5146         )
5147 );
5148
5149 CREATE UNIQUE INDEX from_parent_seq
5150         ON query.from_relation( parent_relation, seq_no )
5151         WHERE parent_relation IS NOT NULL;
5152
5153 -- The following foreign key had to be deferred until
5154 -- query.from_relation existed
5155
5156 ALTER TABLE query.stored_query
5157         ADD FOREIGN KEY (from_clause)
5158         REFERENCES query.from_relation
5159         DEFERRABLE INITIALLY DEFERRED;
5160
5161 CREATE TABLE query.record_column (
5162         id            SERIAL            PRIMARY KEY,
5163         from_relation INT               NOT NULL REFERENCES query.from_relation
5164                                         ON DELETE CASCADE
5165                                         DEFERRABLE INITIALLY DEFERRED,
5166         seq_no        INT               NOT NULL,
5167         column_name   TEXT              NOT NULL,
5168         column_type   INT               NOT NULL REFERENCES query.datatype
5169                                         ON DELETE CASCADE
5170                                                                         DEFERRABLE INITIALLY DEFERRED,
5171         CONSTRAINT column_sequence UNIQUE (from_relation, seq_no)
5172 );
5173
5174 CREATE TABLE query.select_item (
5175         id               SERIAL         PRIMARY KEY,
5176         stored_query     INT            NOT NULL REFERENCES query.stored_query
5177                                         ON DELETE CASCADE
5178                                         DEFERRABLE INITIALLY DEFERRED,
5179         seq_no           INT            NOT NULL,
5180         expression       INT            NOT NULL REFERENCES query.expression
5181                                         DEFERRABLE INITIALLY DEFERRED,
5182         column_alias     TEXT,
5183         grouped_by       BOOL           NOT NULL DEFAULT FALSE,
5184         CONSTRAINT select_sequence UNIQUE( stored_query, seq_no )
5185 );
5186
5187 CREATE TABLE query.order_by_item (
5188         id               SERIAL         PRIMARY KEY,
5189         stored_query     INT            NOT NULL REFERENCES query.stored_query
5190                                         ON DELETE CASCADE
5191                                         DEFERRABLE INITIALLY DEFERRED,
5192         seq_no           INT            NOT NULL,
5193         expression       INT            NOT NULL REFERENCES query.expression
5194                                         ON DELETE CASCADE
5195                                         DEFERRABLE INITIALLY DEFERRED,
5196         CONSTRAINT order_by_sequence UNIQUE( stored_query, seq_no )
5197 );
5198
5199 ------------------------------------------------------------
5200 -- Create updatable views for different kinds of expressions
5201 ------------------------------------------------------------
5202
5203 -- Create updatable view for BETWEEN expressions
5204
5205 CREATE OR REPLACE VIEW query.expr_xbet AS
5206     SELECT
5207                 id,
5208                 parenthesize,
5209                 parent_expr,
5210                 seq_no,
5211                 left_operand,
5212                 negate
5213     FROM
5214         query.expression
5215     WHERE
5216         type = 'xbet';
5217
5218 CREATE OR REPLACE RULE query_expr_xbet_insert_rule AS
5219     ON INSERT TO query.expr_xbet
5220     DO INSTEAD
5221     INSERT INTO query.expression (
5222                 id,
5223                 type,
5224                 parenthesize,
5225                 parent_expr,
5226                 seq_no,
5227                 left_operand,
5228                 negate
5229     ) VALUES (
5230         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5231         'xbet',
5232         COALESCE(NEW.parenthesize, FALSE),
5233         NEW.parent_expr,
5234         COALESCE(NEW.seq_no, 1),
5235                 NEW.left_operand,
5236                 COALESCE(NEW.negate, false)
5237     );
5238
5239 CREATE OR REPLACE RULE query_expr_xbet_update_rule AS
5240     ON UPDATE TO query.expr_xbet
5241     DO INSTEAD
5242     UPDATE query.expression SET
5243         id = NEW.id,
5244         parenthesize = NEW.parenthesize,
5245         parent_expr = NEW.parent_expr,
5246         seq_no = NEW.seq_no,
5247                 left_operand = NEW.left_operand,
5248                 negate = NEW.negate
5249     WHERE
5250         id = OLD.id;
5251
5252 CREATE OR REPLACE RULE query_expr_xbet_delete_rule AS
5253     ON DELETE TO query.expr_xbet
5254     DO INSTEAD
5255     DELETE FROM query.expression WHERE id = OLD.id;
5256
5257 -- Create updatable view for bind variable expressions
5258
5259 CREATE OR REPLACE VIEW query.expr_xbind AS
5260     SELECT
5261                 id,
5262                 parenthesize,
5263                 parent_expr,
5264                 seq_no,
5265                 bind_variable
5266     FROM
5267         query.expression
5268     WHERE
5269         type = 'xbind';
5270
5271 CREATE OR REPLACE RULE query_expr_xbind_insert_rule AS
5272     ON INSERT TO query.expr_xbind
5273     DO INSTEAD
5274     INSERT INTO query.expression (
5275                 id,
5276                 type,
5277                 parenthesize,
5278                 parent_expr,
5279                 seq_no,
5280                 bind_variable
5281     ) VALUES (
5282         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5283         'xbind',
5284         COALESCE(NEW.parenthesize, FALSE),
5285         NEW.parent_expr,
5286         COALESCE(NEW.seq_no, 1),
5287                 NEW.bind_variable
5288     );
5289
5290 CREATE OR REPLACE RULE query_expr_xbind_update_rule AS
5291     ON UPDATE TO query.expr_xbind
5292     DO INSTEAD
5293     UPDATE query.expression SET
5294         id = NEW.id,
5295         parenthesize = NEW.parenthesize,
5296         parent_expr = NEW.parent_expr,
5297         seq_no = NEW.seq_no,
5298                 bind_variable = NEW.bind_variable
5299     WHERE
5300         id = OLD.id;
5301
5302 CREATE OR REPLACE RULE query_expr_xbind_delete_rule AS
5303     ON DELETE TO query.expr_xbind
5304     DO INSTEAD
5305     DELETE FROM query.expression WHERE id = OLD.id;
5306
5307 -- Create updatable view for boolean expressions
5308
5309 CREATE OR REPLACE VIEW query.expr_xbool AS
5310     SELECT
5311                 id,
5312                 parenthesize,
5313                 parent_expr,
5314                 seq_no,
5315                 literal,
5316                 negate
5317     FROM
5318         query.expression
5319     WHERE
5320         type = 'xbool';
5321
5322 CREATE OR REPLACE RULE query_expr_xbool_insert_rule AS
5323     ON INSERT TO query.expr_xbool
5324     DO INSTEAD
5325     INSERT INTO query.expression (
5326                 id,
5327                 type,
5328                 parenthesize,
5329                 parent_expr,
5330                 seq_no,
5331                 literal,
5332                 negate
5333     ) VALUES (
5334         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5335         'xbool',
5336         COALESCE(NEW.parenthesize, FALSE),
5337         NEW.parent_expr,
5338         COALESCE(NEW.seq_no, 1),
5339         NEW.literal,
5340                 COALESCE(NEW.negate, false)
5341     );
5342
5343 CREATE OR REPLACE RULE query_expr_xbool_update_rule AS
5344     ON UPDATE TO query.expr_xbool
5345     DO INSTEAD
5346     UPDATE query.expression SET
5347         id = NEW.id,
5348         parenthesize = NEW.parenthesize,
5349         parent_expr = NEW.parent_expr,
5350         seq_no = NEW.seq_no,
5351         literal = NEW.literal,
5352                 negate = NEW.negate
5353     WHERE
5354         id = OLD.id;
5355
5356 CREATE OR REPLACE RULE query_expr_xbool_delete_rule AS
5357     ON DELETE TO query.expr_xbool
5358     DO INSTEAD
5359     DELETE FROM query.expression WHERE id = OLD.id;
5360
5361 -- Create updatable view for CASE expressions
5362
5363 CREATE OR REPLACE VIEW query.expr_xcase AS
5364     SELECT
5365                 id,
5366                 parenthesize,
5367                 parent_expr,
5368                 seq_no,
5369                 left_operand,
5370                 negate
5371     FROM
5372         query.expression
5373     WHERE
5374         type = 'xcase';
5375
5376 CREATE OR REPLACE RULE query_expr_xcase_insert_rule AS
5377     ON INSERT TO query.expr_xcase
5378     DO INSTEAD
5379     INSERT INTO query.expression (
5380                 id,
5381                 type,
5382                 parenthesize,
5383                 parent_expr,
5384                 seq_no,
5385                 left_operand,
5386                 negate
5387     ) VALUES (
5388         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5389         'xcase',
5390         COALESCE(NEW.parenthesize, FALSE),
5391         NEW.parent_expr,
5392         COALESCE(NEW.seq_no, 1),
5393                 NEW.left_operand,
5394                 COALESCE(NEW.negate, false)
5395     );
5396
5397 CREATE OR REPLACE RULE query_expr_xcase_update_rule AS
5398     ON UPDATE TO query.expr_xcase
5399     DO INSTEAD
5400     UPDATE query.expression SET
5401         id = NEW.id,
5402         parenthesize = NEW.parenthesize,
5403         parent_expr = NEW.parent_expr,
5404         seq_no = NEW.seq_no,
5405                 left_operand = NEW.left_operand,
5406                 negate = NEW.negate
5407     WHERE
5408         id = OLD.id;
5409
5410 CREATE OR REPLACE RULE query_expr_xcase_delete_rule AS
5411     ON DELETE TO query.expr_xcase
5412     DO INSTEAD
5413     DELETE FROM query.expression WHERE id = OLD.id;
5414
5415 -- Create updatable view for cast expressions
5416
5417 CREATE OR REPLACE VIEW query.expr_xcast AS
5418     SELECT
5419                 id,
5420                 parenthesize,
5421                 parent_expr,
5422                 seq_no,
5423                 left_operand,
5424                 cast_type,
5425                 negate
5426     FROM
5427         query.expression
5428     WHERE
5429         type = 'xcast';
5430
5431 CREATE OR REPLACE RULE query_expr_xcast_insert_rule AS
5432     ON INSERT TO query.expr_xcast
5433     DO INSTEAD
5434     INSERT INTO query.expression (
5435         id,
5436         type,
5437         parenthesize,
5438         parent_expr,
5439         seq_no,
5440         left_operand,
5441         cast_type,
5442         negate
5443     ) VALUES (
5444         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5445         'xcast',
5446         COALESCE(NEW.parenthesize, FALSE),
5447         NEW.parent_expr,
5448         COALESCE(NEW.seq_no, 1),
5449         NEW.left_operand,
5450         NEW.cast_type,
5451         COALESCE(NEW.negate, false)
5452     );
5453
5454 CREATE OR REPLACE RULE query_expr_xcast_update_rule AS
5455     ON UPDATE TO query.expr_xcast
5456     DO INSTEAD
5457     UPDATE query.expression SET
5458         id = NEW.id,
5459         parenthesize = NEW.parenthesize,
5460         parent_expr = NEW.parent_expr,
5461         seq_no = NEW.seq_no,
5462                 left_operand = NEW.left_operand,
5463                 cast_type = NEW.cast_type,
5464                 negate = NEW.negate
5465     WHERE
5466         id = OLD.id;
5467
5468 CREATE OR REPLACE RULE query_expr_xcast_delete_rule AS
5469     ON DELETE TO query.expr_xcast
5470     DO INSTEAD
5471     DELETE FROM query.expression WHERE id = OLD.id;
5472
5473 -- Create updatable view for column expressions
5474
5475 CREATE OR REPLACE VIEW query.expr_xcol AS
5476     SELECT
5477                 id,
5478                 parenthesize,
5479                 parent_expr,
5480                 seq_no,
5481                 table_alias,
5482                 column_name,
5483                 negate
5484     FROM
5485         query.expression
5486     WHERE
5487         type = 'xcol';
5488
5489 CREATE OR REPLACE RULE query_expr_xcol_insert_rule AS
5490     ON INSERT TO query.expr_xcol
5491     DO INSTEAD
5492     INSERT INTO query.expression (
5493                 id,
5494                 type,
5495                 parenthesize,
5496                 parent_expr,
5497                 seq_no,
5498                 table_alias,
5499                 column_name,
5500                 negate
5501     ) VALUES (
5502         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5503         'xcol',
5504         COALESCE(NEW.parenthesize, FALSE),
5505         NEW.parent_expr,
5506         COALESCE(NEW.seq_no, 1),
5507                 NEW.table_alias,
5508                 NEW.column_name,
5509                 COALESCE(NEW.negate, false)
5510     );
5511
5512 CREATE OR REPLACE RULE query_expr_xcol_update_rule AS
5513     ON UPDATE TO query.expr_xcol
5514     DO INSTEAD
5515     UPDATE query.expression SET
5516         id = NEW.id,
5517         parenthesize = NEW.parenthesize,
5518         parent_expr = NEW.parent_expr,
5519         seq_no = NEW.seq_no,
5520                 table_alias = NEW.table_alias,
5521                 column_name = NEW.column_name,
5522                 negate = NEW.negate
5523     WHERE
5524         id = OLD.id;
5525
5526 CREATE OR REPLACE RULE query_expr_xcol_delete_rule AS
5527     ON DELETE TO query.expr_xcol
5528     DO INSTEAD
5529     DELETE FROM query.expression WHERE id = OLD.id;
5530
5531 -- Create updatable view for EXISTS expressions
5532
5533 CREATE OR REPLACE VIEW query.expr_xex AS
5534     SELECT
5535                 id,
5536                 parenthesize,
5537                 parent_expr,
5538                 seq_no,
5539                 subquery,
5540                 negate
5541     FROM
5542         query.expression
5543     WHERE
5544         type = 'xex';
5545
5546 CREATE OR REPLACE RULE query_expr_xex_insert_rule AS
5547     ON INSERT TO query.expr_xex
5548     DO INSTEAD
5549     INSERT INTO query.expression (
5550                 id,
5551                 type,
5552                 parenthesize,
5553                 parent_expr,
5554                 seq_no,
5555                 subquery,
5556                 negate
5557     ) VALUES (
5558         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5559         'xex',
5560         COALESCE(NEW.parenthesize, FALSE),
5561         NEW.parent_expr,
5562         COALESCE(NEW.seq_no, 1),
5563                 NEW.subquery,
5564                 COALESCE(NEW.negate, false)
5565     );
5566
5567 CREATE OR REPLACE RULE query_expr_xex_update_rule AS
5568     ON UPDATE TO query.expr_xex
5569     DO INSTEAD
5570     UPDATE query.expression SET
5571         id = NEW.id,
5572         parenthesize = NEW.parenthesize,
5573         parent_expr = NEW.parent_expr,
5574         seq_no = NEW.seq_no,
5575                 subquery = NEW.subquery,
5576                 negate = NEW.negate
5577     WHERE
5578         id = OLD.id;
5579
5580 CREATE OR REPLACE RULE query_expr_xex_delete_rule AS
5581     ON DELETE TO query.expr_xex
5582     DO INSTEAD
5583     DELETE FROM query.expression WHERE id = OLD.id;
5584
5585 -- Create updatable view for function call expressions
5586
5587 CREATE OR REPLACE VIEW query.expr_xfunc AS
5588     SELECT
5589         id,
5590         parenthesize,
5591         parent_expr,
5592         seq_no,
5593         column_name,
5594         function_id,
5595         negate
5596     FROM
5597         query.expression
5598     WHERE
5599         type = 'xfunc';
5600
5601 CREATE OR REPLACE RULE query_expr_xfunc_insert_rule AS
5602     ON INSERT TO query.expr_xfunc
5603     DO INSTEAD
5604     INSERT INTO query.expression (
5605         id,
5606         type,
5607         parenthesize,
5608         parent_expr,
5609         seq_no,
5610         column_name,
5611         function_id,
5612         negate
5613     ) VALUES (
5614         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5615         'xfunc',
5616         COALESCE(NEW.parenthesize, FALSE),
5617         NEW.parent_expr,
5618         COALESCE(NEW.seq_no, 1),
5619         NEW.column_name,
5620         NEW.function_id,
5621         COALESCE(NEW.negate, false)
5622     );
5623
5624 CREATE OR REPLACE RULE query_expr_xfunc_update_rule AS
5625     ON UPDATE TO query.expr_xfunc
5626     DO INSTEAD
5627     UPDATE query.expression SET
5628         id = NEW.id,
5629         parenthesize = NEW.parenthesize,
5630         parent_expr = NEW.parent_expr,
5631         seq_no = NEW.seq_no,
5632         column_name = NEW.column_name,
5633         function_id = NEW.function_id,
5634         negate = NEW.negate
5635     WHERE
5636         id = OLD.id;
5637
5638 CREATE OR REPLACE RULE query_expr_xfunc_delete_rule AS
5639     ON DELETE TO query.expr_xfunc
5640     DO INSTEAD
5641     DELETE FROM query.expression WHERE id = OLD.id;
5642
5643 -- Create updatable view for IN expressions
5644
5645 CREATE OR REPLACE VIEW query.expr_xin AS
5646     SELECT
5647                 id,
5648                 parenthesize,
5649                 parent_expr,
5650                 seq_no,
5651                 left_operand,
5652                 subquery,
5653                 negate
5654     FROM
5655         query.expression
5656     WHERE
5657         type = 'xin';
5658
5659 CREATE OR REPLACE RULE query_expr_xin_insert_rule AS
5660     ON INSERT TO query.expr_xin
5661     DO INSTEAD
5662     INSERT INTO query.expression (
5663                 id,
5664                 type,
5665                 parenthesize,
5666                 parent_expr,
5667                 seq_no,
5668                 left_operand,
5669                 subquery,
5670                 negate
5671     ) VALUES (
5672         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5673         'xin',
5674         COALESCE(NEW.parenthesize, FALSE),
5675         NEW.parent_expr,
5676         COALESCE(NEW.seq_no, 1),
5677                 NEW.left_operand,
5678                 NEW.subquery,
5679                 COALESCE(NEW.negate, false)
5680     );
5681
5682 CREATE OR REPLACE RULE query_expr_xin_update_rule AS
5683     ON UPDATE TO query.expr_xin
5684     DO INSTEAD
5685     UPDATE query.expression SET
5686         id = NEW.id,
5687         parenthesize = NEW.parenthesize,
5688         parent_expr = NEW.parent_expr,
5689         seq_no = NEW.seq_no,
5690                 left_operand = NEW.left_operand,
5691                 subquery = NEW.subquery,
5692                 negate = NEW.negate
5693     WHERE
5694         id = OLD.id;
5695
5696 CREATE OR REPLACE RULE query_expr_xin_delete_rule AS
5697     ON DELETE TO query.expr_xin
5698     DO INSTEAD
5699     DELETE FROM query.expression WHERE id = OLD.id;
5700
5701 -- Create updatable view for IS NULL expressions
5702
5703 CREATE OR REPLACE VIEW query.expr_xisnull AS
5704     SELECT
5705                 id,
5706                 parenthesize,
5707                 parent_expr,
5708                 seq_no,
5709                 left_operand,
5710                 negate
5711     FROM
5712         query.expression
5713     WHERE
5714         type = 'xisnull';
5715
5716 CREATE OR REPLACE RULE query_expr_xisnull_insert_rule AS
5717     ON INSERT TO query.expr_xisnull
5718     DO INSTEAD
5719     INSERT INTO query.expression (
5720                 id,
5721                 type,
5722                 parenthesize,
5723                 parent_expr,
5724                 seq_no,
5725                 left_operand,
5726                 negate
5727     ) VALUES (
5728         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5729         'xisnull',
5730         COALESCE(NEW.parenthesize, FALSE),
5731         NEW.parent_expr,
5732         COALESCE(NEW.seq_no, 1),
5733                 NEW.left_operand,
5734                 COALESCE(NEW.negate, false)
5735     );
5736
5737 CREATE OR REPLACE RULE query_expr_xisnull_update_rule AS
5738     ON UPDATE TO query.expr_xisnull
5739     DO INSTEAD
5740     UPDATE query.expression SET
5741         id = NEW.id,
5742         parenthesize = NEW.parenthesize,
5743         parent_expr = NEW.parent_expr,
5744         seq_no = NEW.seq_no,
5745                 left_operand = NEW.left_operand,
5746                 negate = NEW.negate
5747     WHERE
5748         id = OLD.id;
5749
5750 CREATE OR REPLACE RULE query_expr_xisnull_delete_rule AS
5751     ON DELETE TO query.expr_xisnull
5752     DO INSTEAD
5753     DELETE FROM query.expression WHERE id = OLD.id;
5754
5755 -- Create updatable view for NULL expressions
5756
5757 CREATE OR REPLACE VIEW query.expr_xnull AS
5758     SELECT
5759                 id,
5760                 parenthesize,
5761                 parent_expr,
5762                 seq_no,
5763                 negate
5764     FROM
5765         query.expression
5766     WHERE
5767         type = 'xnull';
5768
5769 CREATE OR REPLACE RULE query_expr_xnull_insert_rule AS
5770     ON INSERT TO query.expr_xnull
5771     DO INSTEAD
5772     INSERT INTO query.expression (
5773                 id,
5774                 type,
5775                 parenthesize,
5776                 parent_expr,
5777                 seq_no,
5778                 negate
5779     ) VALUES (
5780         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5781         'xnull',
5782         COALESCE(NEW.parenthesize, FALSE),
5783         NEW.parent_expr,
5784         COALESCE(NEW.seq_no, 1),
5785                 COALESCE(NEW.negate, false)
5786     );
5787
5788 CREATE OR REPLACE RULE query_expr_xnull_update_rule AS
5789     ON UPDATE TO query.expr_xnull
5790     DO INSTEAD
5791     UPDATE query.expression SET
5792         id = NEW.id,
5793         parenthesize = NEW.parenthesize,
5794         parent_expr = NEW.parent_expr,
5795         seq_no = NEW.seq_no,
5796                 negate = NEW.negate
5797     WHERE
5798         id = OLD.id;
5799
5800 CREATE OR REPLACE RULE query_expr_xnull_delete_rule AS
5801     ON DELETE TO query.expr_xnull
5802     DO INSTEAD
5803     DELETE FROM query.expression WHERE id = OLD.id;
5804
5805 -- Create updatable view for numeric literal expressions
5806
5807 CREATE OR REPLACE VIEW query.expr_xnum AS
5808     SELECT
5809                 id,
5810                 parenthesize,
5811                 parent_expr,
5812                 seq_no,
5813                 literal
5814     FROM
5815         query.expression
5816     WHERE
5817         type = 'xnum';
5818
5819 CREATE OR REPLACE RULE query_expr_xnum_insert_rule AS
5820     ON INSERT TO query.expr_xnum
5821     DO INSTEAD
5822     INSERT INTO query.expression (
5823                 id,
5824                 type,
5825                 parenthesize,
5826                 parent_expr,
5827                 seq_no,
5828                 literal
5829     ) VALUES (
5830         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5831         'xnum',
5832         COALESCE(NEW.parenthesize, FALSE),
5833         NEW.parent_expr,
5834         COALESCE(NEW.seq_no, 1),
5835         NEW.literal
5836     );
5837
5838 CREATE OR REPLACE RULE query_expr_xnum_update_rule AS
5839     ON UPDATE TO query.expr_xnum
5840     DO INSTEAD
5841     UPDATE query.expression SET
5842         id = NEW.id,
5843         parenthesize = NEW.parenthesize,
5844         parent_expr = NEW.parent_expr,
5845         seq_no = NEW.seq_no,
5846         literal = NEW.literal
5847     WHERE
5848         id = OLD.id;
5849
5850 CREATE OR REPLACE RULE query_expr_xnum_delete_rule AS
5851     ON DELETE TO query.expr_xnum
5852     DO INSTEAD
5853     DELETE FROM query.expression WHERE id = OLD.id;
5854
5855 -- Create updatable view for operator expressions
5856
5857 CREATE OR REPLACE VIEW query.expr_xop AS
5858     SELECT
5859                 id,
5860                 parenthesize,
5861                 parent_expr,
5862                 seq_no,
5863                 left_operand,
5864                 operator,
5865                 right_operand,
5866                 negate
5867     FROM
5868         query.expression
5869     WHERE
5870         type = 'xop';
5871
5872 CREATE OR REPLACE RULE query_expr_xop_insert_rule AS
5873     ON INSERT TO query.expr_xop
5874     DO INSTEAD
5875     INSERT INTO query.expression (
5876                 id,
5877                 type,
5878                 parenthesize,
5879                 parent_expr,
5880                 seq_no,
5881                 left_operand,
5882                 operator,
5883                 right_operand,
5884                 negate
5885     ) VALUES (
5886         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5887         'xop',
5888         COALESCE(NEW.parenthesize, FALSE),
5889         NEW.parent_expr,
5890         COALESCE(NEW.seq_no, 1),
5891                 NEW.left_operand,
5892                 NEW.operator,
5893                 NEW.right_operand,
5894                 COALESCE(NEW.negate, false)
5895     );
5896
5897 CREATE OR REPLACE RULE query_expr_xop_update_rule AS
5898     ON UPDATE TO query.expr_xop
5899     DO INSTEAD
5900     UPDATE query.expression SET
5901         id = NEW.id,
5902         parenthesize = NEW.parenthesize,
5903         parent_expr = NEW.parent_expr,
5904         seq_no = NEW.seq_no,
5905                 left_operand = NEW.left_operand,
5906                 operator = NEW.operator,
5907                 right_operand = NEW.right_operand,
5908                 negate = NEW.negate
5909     WHERE
5910         id = OLD.id;
5911
5912 CREATE OR REPLACE RULE query_expr_xop_delete_rule AS
5913     ON DELETE TO query.expr_xop
5914     DO INSTEAD
5915     DELETE FROM query.expression WHERE id = OLD.id;
5916
5917 -- Create updatable view for series expressions
5918 -- i.e. series of expressions separated by operators
5919
5920 CREATE OR REPLACE VIEW query.expr_xser AS
5921     SELECT
5922                 id,
5923                 parenthesize,
5924                 parent_expr,
5925                 seq_no,
5926                 operator,
5927                 negate
5928     FROM
5929         query.expression
5930     WHERE
5931         type = 'xser';
5932
5933 CREATE OR REPLACE RULE query_expr_xser_insert_rule AS
5934     ON INSERT TO query.expr_xser
5935     DO INSTEAD
5936     INSERT INTO query.expression (
5937                 id,
5938                 type,
5939                 parenthesize,
5940                 parent_expr,
5941                 seq_no,
5942                 operator,
5943                 negate
5944     ) VALUES (
5945         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5946         'xser',
5947         COALESCE(NEW.parenthesize, FALSE),
5948         NEW.parent_expr,
5949         COALESCE(NEW.seq_no, 1),
5950                 NEW.operator,
5951                 COALESCE(NEW.negate, false)
5952     );
5953
5954 CREATE OR REPLACE RULE query_expr_xser_update_rule AS
5955     ON UPDATE TO query.expr_xser
5956     DO INSTEAD
5957     UPDATE query.expression SET
5958         id = NEW.id,
5959         parenthesize = NEW.parenthesize,
5960         parent_expr = NEW.parent_expr,
5961         seq_no = NEW.seq_no,
5962                 operator = NEW.operator,
5963                 negate = NEW.negate
5964     WHERE
5965         id = OLD.id;
5966
5967 CREATE OR REPLACE RULE query_expr_xser_delete_rule AS
5968     ON DELETE TO query.expr_xser
5969     DO INSTEAD
5970     DELETE FROM query.expression WHERE id = OLD.id;
5971
5972 -- Create updatable view for string literal expressions
5973
5974 CREATE OR REPLACE VIEW query.expr_xstr AS
5975     SELECT
5976         id,
5977         parenthesize,
5978         parent_expr,
5979         seq_no,
5980         literal
5981     FROM
5982         query.expression
5983     WHERE
5984         type = 'xstr';
5985
5986 CREATE OR REPLACE RULE query_expr_string_insert_rule AS
5987     ON INSERT TO query.expr_xstr
5988     DO INSTEAD
5989     INSERT INTO query.expression (
5990         id,
5991         type,
5992         parenthesize,
5993         parent_expr,
5994         seq_no,
5995         literal
5996     ) VALUES (
5997         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
5998         'xstr',
5999         COALESCE(NEW.parenthesize, FALSE),
6000         NEW.parent_expr,
6001         COALESCE(NEW.seq_no, 1),
6002         NEW.literal
6003     );
6004
6005 CREATE OR REPLACE RULE query_expr_string_update_rule AS
6006     ON UPDATE TO query.expr_xstr
6007     DO INSTEAD
6008     UPDATE query.expression SET
6009         id = NEW.id,
6010         parenthesize = NEW.parenthesize,
6011         parent_expr = NEW.parent_expr,
6012         seq_no = NEW.seq_no,
6013         literal = NEW.literal
6014     WHERE
6015         id = OLD.id;
6016
6017 CREATE OR REPLACE RULE query_expr_string_delete_rule AS
6018     ON DELETE TO query.expr_xstr
6019     DO INSTEAD
6020     DELETE FROM query.expression WHERE id = OLD.id;
6021
6022 -- Create updatable view for subquery expressions
6023
6024 CREATE OR REPLACE VIEW query.expr_xsubq AS
6025     SELECT
6026                 id,
6027                 parenthesize,
6028                 parent_expr,
6029                 seq_no,
6030                 subquery,
6031                 negate
6032     FROM
6033         query.expression
6034     WHERE
6035         type = 'xsubq';
6036
6037 CREATE OR REPLACE RULE query_expr_xsubq_insert_rule AS
6038     ON INSERT TO query.expr_xsubq
6039     DO INSTEAD
6040     INSERT INTO query.expression (
6041                 id,
6042                 type,
6043                 parenthesize,
6044                 parent_expr,
6045                 seq_no,
6046                 subquery,
6047                 negate
6048     ) VALUES (
6049         COALESCE(NEW.id, NEXTVAL('query.expression_id_seq'::REGCLASS)),
6050         'xsubq',
6051         COALESCE(NEW.parenthesize, FALSE),
6052         NEW.parent_expr,
6053         COALESCE(NEW.seq_no, 1),
6054                 NEW.subquery,
6055                 COALESCE(NEW.negate, false)
6056     );
6057
6058 CREATE OR REPLACE RULE query_expr_xsubq_update_rule AS
6059     ON UPDATE TO query.expr_xsubq
6060     DO INSTEAD
6061     UPDATE query.expression SET
6062         id = NEW.id,
6063         parenthesize = NEW.parenthesize,
6064         parent_expr = NEW.parent_expr,
6065         seq_no = NEW.seq_no,
6066                 subquery = NEW.subquery,
6067                 negate = NEW.negate
6068     WHERE
6069         id = OLD.id;
6070
6071 CREATE OR REPLACE RULE query_expr_xsubq_delete_rule AS
6072     ON DELETE TO query.expr_xsubq
6073     DO INSTEAD
6074     DELETE FROM query.expression WHERE id = OLD.id;
6075
6076 CREATE TABLE action.fieldset (
6077     id              SERIAL          PRIMARY KEY,
6078     owner           INT             NOT NULL REFERENCES actor.usr (id)
6079                                     DEFERRABLE INITIALLY DEFERRED,
6080     owning_lib      INT             NOT NULL REFERENCES actor.org_unit (id)
6081                                     DEFERRABLE INITIALLY DEFERRED,
6082     status          TEXT            NOT NULL
6083                                     CONSTRAINT valid_status CHECK ( status in
6084                                     ( 'PENDING', 'APPLIED', 'ERROR' )),
6085     creation_time   TIMESTAMPTZ     NOT NULL DEFAULT NOW(),
6086     scheduled_time  TIMESTAMPTZ,
6087     applied_time    TIMESTAMPTZ,
6088     classname       TEXT            NOT NULL, -- an IDL class name
6089     name            TEXT            NOT NULL,
6090     stored_query    INT             REFERENCES query.stored_query (id)
6091                                     DEFERRABLE INITIALLY DEFERRED,
6092     pkey_value      TEXT,
6093     CONSTRAINT lib_name_unique UNIQUE (owning_lib, name),
6094     CONSTRAINT fieldset_one_or_the_other CHECK (
6095         (stored_query IS NOT NULL AND pkey_value IS NULL) OR
6096         (pkey_value IS NOT NULL AND stored_query IS NULL)
6097     )
6098     -- the CHECK constraint means we can update the fields for a single
6099     -- row without all the extra overhead involved in a query
6100 );
6101
6102 CREATE INDEX action_fieldset_sched_time_idx ON action.fieldset( scheduled_time );
6103 CREATE INDEX action_owner_idx               ON action.fieldset( owner );
6104
6105 CREATE TABLE action.fieldset_col_val (
6106     id              SERIAL  PRIMARY KEY,
6107     fieldset        INT     NOT NULL REFERENCES action.fieldset
6108                                          ON DELETE CASCADE
6109                                          DEFERRABLE INITIALLY DEFERRED,
6110     col             TEXT    NOT NULL,  -- "field" from the idl ... the column on the table
6111     val             TEXT,              -- value for the column ... NULL means, well, NULL
6112     CONSTRAINT fieldset_col_once_per_set UNIQUE (fieldset, col)
6113 );
6114
6115 CREATE OR REPLACE FUNCTION action.apply_fieldset(
6116         fieldset_id IN INT,        -- id from action.fieldset
6117         table_name  IN TEXT,       -- table to be updated
6118         pkey_name   IN TEXT,       -- name of primary key column in that table
6119         query       IN TEXT        -- query constructed by qstore (for query-based
6120                                    --    fieldsets only; otherwise null
6121 )
6122 RETURNS TEXT AS $$
6123 DECLARE
6124         statement TEXT;
6125         fs_status TEXT;
6126         fs_pkey_value TEXT;
6127         fs_query TEXT;
6128         sep CHAR;
6129         status_code TEXT;
6130         msg TEXT;
6131         update_count INT;
6132         cv RECORD;
6133 BEGIN
6134         -- Sanity checks
6135         IF fieldset_id IS NULL THEN
6136                 RETURN 'Fieldset ID parameter is NULL';
6137         END IF;
6138         IF table_name IS NULL THEN
6139                 RETURN 'Table name parameter is NULL';
6140         END IF;
6141         IF pkey_name IS NULL THEN
6142                 RETURN 'Primary key name parameter is NULL';
6143         END IF;
6144         --
6145         statement := 'UPDATE ' || table_name || ' SET';
6146         --
6147         SELECT
6148                 status,
6149                 quote_literal( pkey_value )
6150         INTO
6151                 fs_status,
6152                 fs_pkey_value
6153         FROM
6154                 action.fieldset
6155         WHERE
6156                 id = fieldset_id;
6157         --
6158         IF fs_status IS NULL THEN
6159                 RETURN 'No fieldset found for id = ' || fieldset_id;
6160         ELSIF fs_status = 'APPLIED' THEN
6161                 RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
6162         END IF;
6163         --
6164         sep := '';
6165         FOR cv IN
6166                 SELECT  col,
6167                                 val
6168                 FROM    action.fieldset_col_val
6169                 WHERE   fieldset = fieldset_id
6170         LOOP
6171                 statement := statement || sep || ' ' || cv.col
6172                                          || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
6173                 sep := ',';
6174         END LOOP;
6175         --
6176         IF sep = '' THEN
6177                 RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
6178         END IF;
6179         --
6180         -- Add the WHERE clause.  This differs according to whether it's a
6181         -- single-row fieldset or a query-based fieldset.
6182         --
6183         IF query IS NULL        AND fs_pkey_value IS NULL THEN
6184                 RETURN 'Incomplete fieldset: neither a primary key nor a query available';
6185         ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
6186             fs_query := rtrim( query, ';' );
6187             statement := statement || ' WHERE ' || pkey_name || ' IN ( '
6188                          || fs_query || ' );';
6189         ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
6190                 statement := statement || ' WHERE ' || pkey_name || ' = '
6191                                      || fs_pkey_value || ';';
6192         ELSE  -- both are not null
6193                 RETURN 'Ambiguous fieldset: both a primary key and a query provided';
6194         END IF;
6195         --
6196         -- Execute the update
6197         --
6198         BEGIN
6199                 EXECUTE statement;
6200                 GET DIAGNOSTICS update_count = ROW_COUNT;
6201                 --
6202                 IF UPDATE_COUNT > 0 THEN
6203                         status_code := 'APPLIED';
6204                         msg := NULL;
6205                 ELSE
6206                         status_code := 'ERROR';
6207                         msg := 'No eligible rows found for fieldset ' || fieldset_id;
6208         END IF;
6209         EXCEPTION WHEN OTHERS THEN
6210                 status_code := 'ERROR';
6211                 msg := 'Unable to apply fieldset ' || fieldset_id
6212                            || ': ' || sqlerrm;
6213         END;
6214         --
6215         -- Update fieldset status
6216         --
6217         UPDATE action.fieldset
6218         SET status       = status_code,
6219             applied_time = now()
6220         WHERE id = fieldset_id;
6221         --
6222         RETURN msg;
6223 END;
6224 $$ LANGUAGE plpgsql;
6225
6226 COMMENT ON FUNCTION action.apply_fieldset( INT, TEXT, TEXT, TEXT ) IS $$
6227 /**
6228  * Applies a specified fieldset, using a supplied table name and primary
6229  * key name.  The query parameter should be non-null only for
6230  * query-based fieldsets.
6231  *
6232  * Returns NULL if successful, or an error message if not.
6233  */
6234 $$;
6235
6236 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
6237
6238 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
6239     SELECT  u.hold,
6240             c.circ_lib,
6241             count(*)
6242       FROM  action.unfulfilled_hold_list u
6243             JOIN asset.copy c ON (c.id = u.current_copy)
6244       GROUP BY 1,2;
6245
6246 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
6247     SELECT  hold,
6248             min(count)
6249       FROM  action.unfulfilled_hold_loops
6250       GROUP BY 1;
6251
6252 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
6253     SELECT  DISTINCT l.*
6254       FROM  action.unfulfilled_hold_loops l
6255             JOIN action.unfulfilled_hold_min_loop m USING (hold)
6256       WHERE l.count = m.min;
6257
6258 ALTER TABLE asset.copy
6259 ADD COLUMN dummy_isbn TEXT;
6260
6261 ALTER TABLE auditor.asset_copy_history
6262 ADD COLUMN dummy_isbn TEXT;
6263
6264 -- Add new column status_changed_date to asset.copy, with trigger to maintain it
6265 -- Add corresponding new column to auditor.asset_copy_history
6266
6267 ALTER TABLE asset.copy
6268         ADD COLUMN status_changed_time TIMESTAMPTZ;
6269
6270 ALTER TABLE auditor.asset_copy_history
6271         ADD COLUMN status_changed_time TIMESTAMPTZ;
6272
6273 CREATE OR REPLACE FUNCTION asset.acp_status_changed()
6274 RETURNS TRIGGER AS $$
6275 BEGIN
6276     IF NEW.status <> OLD.status THEN
6277         NEW.status_changed_time := now();
6278     END IF;
6279     RETURN NEW;
6280 END;
6281 $$ LANGUAGE plpgsql;
6282
6283 CREATE TRIGGER acp_status_changed_trig
6284         BEFORE UPDATE ON asset.copy
6285         FOR EACH ROW EXECUTE PROCEDURE asset.acp_status_changed();
6286
6287 ALTER TABLE asset.copy
6288 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6289
6290 ALTER TABLE auditor.asset_copy_history
6291 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
6292
6293 ALTER TABLE asset.copy ADD COLUMN floating BOOL NOT NULL DEFAULT FALSE;
6294 ALTER TABLE auditor.asset_copy_history ADD COLUMN floating BOOL;
6295
6296 DROP INDEX IF EXISTS asset.copy_barcode_key;
6297 CREATE UNIQUE INDEX copy_barcode_key ON asset.copy (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
6298
6299 -- Note: later we create a trigger a_opac_vis_mat_view_tgr
6300 -- AFTER INSERT OR UPDATE ON asset.copy
6301
6302 ALTER TABLE asset.copy ADD COLUMN cost NUMERIC(8,2);
6303 ALTER TABLE auditor.asset_copy_history ADD COLUMN cost NUMERIC(8,2);
6304
6305 -- Moke mostly parallel changes to action.circulation
6306 -- and action.aged_circulation
6307
6308 ALTER TABLE action.circulation
6309 ADD COLUMN workstation INT
6310     REFERENCES actor.workstation
6311         ON DELETE SET NULL
6312         DEFERRABLE INITIALLY DEFERRED;
6313
6314 ALTER TABLE action.aged_circulation
6315 ADD COLUMN workstation INT;
6316
6317 ALTER TABLE action.circulation
6318 ADD COLUMN parent_circ BIGINT
6319         REFERENCES action.circulation(id)
6320         DEFERRABLE INITIALLY DEFERRED;
6321
6322 CREATE UNIQUE INDEX circ_parent_idx
6323 ON action.circulation( parent_circ )
6324 WHERE parent_circ IS NOT NULL;
6325
6326 ALTER TABLE action.aged_circulation
6327 ADD COLUMN parent_circ BIGINT;
6328
6329 ALTER TABLE action.circulation
6330 ADD COLUMN checkin_workstation INT
6331         REFERENCES actor.workstation(id)
6332         ON DELETE SET NULL
6333         DEFERRABLE INITIALLY DEFERRED;
6334
6335 ALTER TABLE action.aged_circulation
6336 ADD COLUMN checkin_workstation INT;
6337
6338 ALTER TABLE action.circulation
6339 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6340
6341 ALTER TABLE action.aged_circulation
6342 ADD COLUMN checkin_scan_time TIMESTAMPTZ;
6343
6344 CREATE INDEX action_circulation_target_copy_idx
6345 ON action.circulation (target_copy);
6346
6347 CREATE INDEX action_aged_circulation_target_copy_idx
6348 ON action.aged_circulation (target_copy);
6349
6350 ALTER TABLE action.circulation
6351 DROP CONSTRAINT circulation_stop_fines_check;
6352
6353 ALTER TABLE action.circulation
6354         ADD CONSTRAINT circulation_stop_fines_check
6355         CHECK (stop_fines IN (
6356         'CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE','CLAIMSNEVERCHECKEDOUT'));
6357
6358 -- Hard due-date functionality
6359 CREATE TABLE config.hard_due_date (
6360         id          SERIAL      PRIMARY KEY,
6361         name        TEXT        NOT NULL UNIQUE CHECK ( name ~ E'^\\w+$' ),
6362         ceiling_date    TIMESTAMPTZ NOT NULL,
6363         forceto     BOOL        NOT NULL,
6364         owner       INT         NOT NULL
6365 );
6366
6367 CREATE TABLE config.hard_due_date_values (
6368     id                  SERIAL      PRIMARY KEY,
6369     hard_due_date       INT         NOT NULL REFERENCES config.hard_due_date (id)
6370                                     DEFERRABLE INITIALLY DEFERRED,
6371     ceiling_date        TIMESTAMPTZ NOT NULL,
6372     active_date         TIMESTAMPTZ NOT NULL
6373 );
6374
6375 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN hard_due_date INT REFERENCES config.hard_due_date (id);
6376
6377 CREATE OR REPLACE FUNCTION config.update_hard_due_dates () RETURNS INT AS $func$
6378 DECLARE
6379     temp_value  config.hard_due_date_values%ROWTYPE;
6380     updated     INT := 0;
6381 BEGIN
6382     FOR temp_value IN
6383       SELECT  DISTINCT ON (hard_due_date) *
6384         FROM  config.hard_due_date_values
6385         WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
6386         ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
6387    LOOP
6388         UPDATE  config.hard_due_date
6389           SET   ceiling_date = temp_value.ceiling_date
6390           WHERE id = temp_value.hard_due_date
6391                 AND ceiling_date <> temp_value.ceiling_date; -- Time is equal if we've already updated the chdd
6392
6393         IF FOUND THEN
6394             updated := updated + 1;
6395         END IF;
6396     END LOOP;
6397
6398     RETURN updated;
6399 END;
6400 $func$ LANGUAGE plpgsql;
6401
6402 -- Correct some long-standing misspellings involving variations of "recur"
6403
6404 ALTER TABLE action.circulation RENAME COLUMN recuring_fine TO recurring_fine;
6405 ALTER TABLE action.circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6406
6407 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine TO recurring_fine;
6408 ALTER TABLE action.aged_circulation RENAME COLUMN recuring_fine_rule TO recurring_fine_rule;
6409
6410 ALTER TABLE config.rule_recuring_fine RENAME TO rule_recurring_fine;
6411 ALTER TABLE config.rule_recuring_fine_id_seq RENAME TO rule_recurring_fine_id_seq;
6412
6413 ALTER TABLE config.rule_recurring_fine RENAME COLUMN recurance_interval TO recurrence_interval;
6414
6415 -- Might as well keep the comment in sync as well
6416 COMMENT ON TABLE config.rule_recurring_fine IS $$
6417 /*
6418  * Copyright (C) 2005  Georgia Public Library Service 
6419  * Mike Rylander <mrylander@gmail.com>
6420  *
6421  * Circulation Recurring Fine rules
6422  *
6423  * Each circulation is given a recurring fine amount based on one of
6424  * these rules.  The recurrence_interval should not be any shorter
6425  * than the interval between runs of the fine_processor.pl script
6426  * (which is run from CRON), or you could miss fines.
6427  * 
6428  *
6429  * ****
6430  *
6431  * This program is free software; you can redistribute it and/or
6432  * modify it under the terms of the GNU General Public License
6433  * as published by the Free Software Foundation; either version 2
6434  * of the License, or (at your option) any later version.
6435  *
6436  * This program is distributed in the hope that it will be useful,
6437  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6438  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6439  * GNU General Public License for more details.
6440  */
6441 $$;
6442
6443 -- Extend the name change to some related views:
6444
6445 DROP VIEW IF EXISTS reporter.overdue_circs;
6446
6447 CREATE OR REPLACE VIEW reporter.overdue_circs AS
6448 SELECT  *
6449   FROM  action.circulation
6450     WHERE checkin_time is null
6451                 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
6452                                 AND due_date < now();
6453
6454 DROP VIEW IF EXISTS stats.fleshed_circulation;
6455
6456 DROP VIEW IF EXISTS stats.fleshed_copy;
6457
6458 CREATE VIEW stats.fleshed_copy AS
6459         SELECT  cp.*,
6460         CAST(cp.create_date AS DATE) AS create_date_day,
6461         CAST(cp.edit_date AS DATE) AS edit_date_day,
6462         DATE_TRUNC('hour', cp.create_date) AS create_date_hour,
6463         DATE_TRUNC('hour', cp.edit_date) AS edit_date_hour,
6464                 cn.label AS call_number_label,
6465                 cn.owning_lib,
6466                 rd.item_lang,
6467                 rd.item_type,
6468                 rd.item_form
6469         FROM    asset.copy cp
6470                 JOIN asset.call_number cn ON (cp.call_number = cn.id)
6471                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
6472
6473 CREATE VIEW stats.fleshed_circulation AS
6474         SELECT  c.*,
6475                 CAST(c.xact_start AS DATE) AS start_date_day,
6476                 CAST(c.xact_finish AS DATE) AS finish_date_day,
6477                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
6478                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
6479                 cp.call_number_label,
6480                 cp.owning_lib,
6481                 cp.item_lang,
6482                 cp.item_type,
6483                 cp.item_form
6484         FROM    action.circulation c
6485                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
6486
6487 -- Drop a view temporarily in order to alter action.all_circulation, upon
6488 -- which it is dependent.  We will recreate the view later.
6489
6490 DROP VIEW IF EXISTS extend_reporter.full_circ_count;
6491
6492 -- You would think that CREATE OR REPLACE would be enough, but in testing
6493 -- PostgreSQL complained about renaming the columns in the view. So we
6494 -- drop the view first.
6495 DROP VIEW IF EXISTS action.all_circulation;
6496
6497 CREATE OR REPLACE VIEW action.all_circulation AS
6498     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6499         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6500         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6501         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6502         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6503         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6504       FROM  action.aged_circulation
6505             UNION ALL
6506     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,
6507         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,
6508         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
6509         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
6510         circ.fine_interval, circ.recurring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
6511         circ.recurring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
6512         circ.parent_circ
6513       FROM  action.circulation circ
6514         JOIN asset.copy cp ON (circ.target_copy = cp.id)
6515         JOIN asset.call_number cn ON (cp.call_number = cn.id)
6516         JOIN actor.usr p ON (circ.usr = p.id)
6517         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
6518         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
6519
6520 -- Recreate the temporarily dropped view, having altered the action.all_circulation view:
6521
6522 CREATE OR REPLACE VIEW extend_reporter.full_circ_count AS
6523  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
6524    FROM asset."copy" cp
6525    LEFT JOIN extend_reporter.legacy_circ_count c USING (id)
6526    LEFT JOIN "action".circulation circ ON circ.target_copy = cp.id
6527    LEFT JOIN "action".aged_circulation acirc ON acirc.target_copy = cp.id
6528   GROUP BY cp.id;
6529
6530 CREATE UNIQUE INDEX only_one_concurrent_checkout_per_copy ON action.circulation(target_copy) WHERE checkin_time IS NULL;
6531
6532 ALTER TABLE action.circulation DROP CONSTRAINT action_circulation_target_copy_fkey;
6533
6534 -- Rebuild dependent views
6535
6536 DROP VIEW IF EXISTS action.billable_circulations;
6537
6538 CREATE OR REPLACE VIEW action.billable_circulations AS
6539     SELECT  *
6540       FROM  action.circulation
6541       WHERE xact_finish IS NULL;
6542
6543 DROP VIEW IF EXISTS action.open_circulation;
6544
6545 CREATE OR REPLACE VIEW action.open_circulation AS
6546     SELECT  *
6547       FROM  action.circulation
6548       WHERE checkin_time IS NULL
6549       ORDER BY due_date;
6550
6551 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
6552 DECLARE
6553 found char := 'N';
6554 BEGIN
6555
6556     -- If there are any renewals for this circulation, don't archive or delete
6557     -- it yet.   We'll do so later, when we archive and delete the renewals.
6558
6559     SELECT 'Y' INTO found
6560     FROM action.circulation
6561     WHERE parent_circ = OLD.id
6562     LIMIT 1;
6563
6564     IF found = 'Y' THEN
6565         RETURN NULL;  -- don't delete
6566         END IF;
6567
6568     -- Archive a copy of the old row to action.aged_circulation
6569
6570     INSERT INTO action.aged_circulation
6571         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6572         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6573         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6574         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6575         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6576         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
6577       SELECT
6578         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
6579         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
6580         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
6581         stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
6582         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
6583         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
6584         FROM action.all_circulation WHERE id = OLD.id;
6585
6586     RETURN OLD;
6587 END;
6588 $$ LANGUAGE 'plpgsql';
6589
6590 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND name = 'title';
6591
6592 UPDATE config.z3950_attr SET truncation = 1 WHERE source = 'biblios' AND truncation = 0;
6593
6594 -- Adding circ.holds.target_skip_me OU setting logic to the pre-matchpoint tests
6595
6596 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$
6597 DECLARE
6598     current_requestor_group    permission.grp_tree%ROWTYPE;
6599     requestor_object    actor.usr%ROWTYPE;
6600     user_object        actor.usr%ROWTYPE;
6601     item_object        asset.copy%ROWTYPE;
6602     item_cn_object        asset.call_number%ROWTYPE;
6603     rec_descriptor        metabib.rec_descriptor%ROWTYPE;
6604     current_mp_weight    FLOAT;
6605     matchpoint_weight    FLOAT;
6606     tmp_weight        FLOAT;
6607     current_mp        config.hold_matrix_matchpoint%ROWTYPE;
6608     matchpoint        config.hold_matrix_matchpoint%ROWTYPE;
6609 BEGIN
6610     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6611     SELECT INTO requestor_object * FROM actor.usr WHERE id = match_requestor;
6612     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6613     SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
6614     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r WHERE r.record = item_cn_object.record;
6615
6616     PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;
6617
6618     IF NOT FOUND THEN
6619         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = requestor_object.profile;
6620     ELSE
6621         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = user_object.profile;
6622     END IF;
6623
6624     LOOP 
6625         -- for each potential matchpoint for this ou and group ...
6626         FOR current_mp IN
6627             SELECT    m.*
6628               FROM    config.hold_matrix_matchpoint m
6629               WHERE    m.requestor_grp = current_requestor_group.id AND m.active
6630               ORDER BY    CASE WHEN m.circ_modifier    IS NOT NULL THEN 16 ELSE 0 END +
6631                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 16 ELSE 0 END +
6632                     CASE WHEN m.marc_type        IS NOT NULL THEN 8 ELSE 0 END +
6633                     CASE WHEN m.marc_form        IS NOT NULL THEN 4 ELSE 0 END +
6634                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 2 ELSE 0 END +
6635                     CASE WHEN m.ref_flag        IS NOT NULL THEN 1 ELSE 0 END DESC LOOP
6636
6637             current_mp_weight := 5.0;
6638
6639             IF current_mp.circ_modifier IS NOT NULL THEN
6640                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
6641             END IF;
6642
6643             IF current_mp.marc_type IS NOT NULL THEN
6644                 IF item_object.circ_as_type IS NOT NULL THEN
6645                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
6646                 ELSE
6647                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
6648                 END IF;
6649             END IF;
6650
6651             IF current_mp.marc_form IS NOT NULL THEN
6652                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
6653             END IF;
6654
6655             IF current_mp.marc_vr_format IS NOT NULL THEN
6656                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
6657             END IF;
6658
6659             IF current_mp.juvenile_flag IS NOT NULL THEN
6660                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
6661             END IF;
6662
6663             IF current_mp.ref_flag IS NOT NULL THEN
6664                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
6665             END IF;
6666
6667
6668             -- caclulate the rule match weight
6669             IF current_mp.item_owning_ou IS NOT NULL THEN
6670                 CONTINUE WHEN current_mp.item_owning_ou NOT IN (SELECT (actor.org_unit_ancestors(item_cn_object.owning_lib)).id);
6671                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_owning_ou, item_cn_object.owning_lib)::FLOAT + 1.0)::FLOAT;
6672                 current_mp_weight := current_mp_weight - tmp_weight;
6673             END IF; 
6674
6675             IF current_mp.item_circ_ou IS NOT NULL THEN
6676                 CONTINUE WHEN current_mp.item_circ_ou NOT IN (SELECT (actor.org_unit_ancestors(item_object.circ_lib)).id);
6677                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.item_circ_ou, item_object.circ_lib)::FLOAT + 1.0)::FLOAT;
6678                 current_mp_weight := current_mp_weight - tmp_weight;
6679             END IF; 
6680
6681             IF current_mp.pickup_ou IS NOT NULL THEN
6682                 CONTINUE WHEN current_mp.pickup_ou NOT IN (SELECT (actor.org_unit_ancestors(pickup_ou)).id);
6683                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.pickup_ou, pickup_ou)::FLOAT + 1.0)::FLOAT;
6684                 current_mp_weight := current_mp_weight - tmp_weight;
6685             END IF; 
6686
6687             IF current_mp.request_ou IS NOT NULL THEN
6688                 CONTINUE WHEN current_mp.request_ou NOT IN (SELECT (actor.org_unit_ancestors(request_ou)).id);
6689                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.request_ou, request_ou)::FLOAT + 1.0)::FLOAT;
6690                 current_mp_weight := current_mp_weight - tmp_weight;
6691             END IF; 
6692
6693             IF current_mp.user_home_ou IS NOT NULL THEN
6694                 CONTINUE WHEN current_mp.user_home_ou NOT IN (SELECT (actor.org_unit_ancestors(user_object.home_ou)).id);
6695                 SELECT INTO tmp_weight 1.0 / (actor.org_unit_proximity(current_mp.user_home_ou, user_object.home_ou)::FLOAT + 1.0)::FLOAT;
6696                 current_mp_weight := current_mp_weight - tmp_weight;
6697             END IF; 
6698
6699             -- set the matchpoint if we found the best one
6700             IF matchpoint_weight IS NULL OR matchpoint_weight > current_mp_weight THEN
6701                 matchpoint = current_mp;
6702                 matchpoint_weight = current_mp_weight;
6703             END IF;
6704
6705         END LOOP;
6706
6707         EXIT WHEN current_requestor_group.parent IS NULL OR matchpoint.id IS NOT NULL;
6708
6709         SELECT INTO current_requestor_group * FROM permission.grp_tree WHERE id = current_requestor_group.parent;
6710     END LOOP;
6711
6712     RETURN matchpoint.id;
6713 END;
6714 $func$ LANGUAGE plpgsql;
6715
6716 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$
6717 DECLARE
6718     matchpoint_id        INT;
6719     user_object        actor.usr%ROWTYPE;
6720     age_protect_object    config.rule_age_hold_protect%ROWTYPE;
6721     standing_penalty    config.standing_penalty%ROWTYPE;
6722     transit_range_ou_type    actor.org_unit_type%ROWTYPE;
6723     transit_source        actor.org_unit%ROWTYPE;
6724     item_object        asset.copy%ROWTYPE;
6725     ou_skip              actor.org_unit_setting%ROWTYPE;
6726     result            action.matrix_test_result;
6727     hold_test        config.hold_matrix_matchpoint%ROWTYPE;
6728     hold_count        INT;
6729     hold_transit_prox    INT;
6730     frozen_hold_count    INT;
6731     context_org_list    INT[];
6732     done            BOOL := FALSE;
6733 BEGIN
6734     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
6735     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( pickup_ou );
6736
6737     result.success := TRUE;
6738
6739     -- Fail if we couldn't find a user
6740     IF user_object.id IS NULL THEN
6741         result.fail_part := 'no_user';
6742         result.success := FALSE;
6743         done := TRUE;
6744         RETURN NEXT result;
6745         RETURN;
6746     END IF;
6747
6748     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
6749
6750     -- Fail if we couldn't find a copy
6751     IF item_object.id IS NULL THEN
6752         result.fail_part := 'no_item';
6753         result.success := FALSE;
6754         done := TRUE;
6755         RETURN NEXT result;
6756         RETURN;
6757     END IF;
6758
6759     SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(pickup_ou, request_ou, match_item, match_user, match_requestor);
6760     result.matchpoint := matchpoint_id;
6761
6762     SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;
6763
6764     -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
6765     IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
6766         result.fail_part := 'circ.holds.target_skip_me';
6767         result.success := FALSE;
6768         done := TRUE;
6769         RETURN NEXT result;
6770         RETURN;
6771     END IF;
6772
6773     -- Fail if user is barred
6774     IF user_object.barred IS TRUE THEN
6775         result.fail_part := 'actor.usr.barred';
6776         result.success := FALSE;
6777         done := TRUE;
6778         RETURN NEXT result;
6779         RETURN;
6780     END IF;
6781
6782     -- Fail if we couldn't find any matchpoint (requires a default)
6783     IF matchpoint_id IS NULL THEN
6784         result.fail_part := 'no_matchpoint';
6785         result.success := FALSE;
6786         done := TRUE;
6787         RETURN NEXT result;
6788         RETURN;
6789     END IF;
6790
6791     SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;
6792
6793     IF hold_test.holdable IS FALSE THEN
6794         result.fail_part := 'config.hold_matrix_test.holdable';
6795         result.success := FALSE;
6796         done := TRUE;
6797         RETURN NEXT result;
6798     END IF;
6799
6800     IF hold_test.transit_range IS NOT NULL THEN
6801         SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
6802         IF hold_test.distance_is_from_owner THEN
6803             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;
6804         ELSE
6805             SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
6806         END IF;
6807
6808         PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = pickup_ou;
6809
6810         IF NOT FOUND THEN
6811             result.fail_part := 'transit_range';
6812             result.success := FALSE;
6813             done := TRUE;
6814             RETURN NEXT result;
6815         END IF;
6816     END IF;
6817  
6818     IF NOT retargetting THEN
6819         FOR standing_penalty IN
6820             SELECT  DISTINCT csp.*
6821               FROM  actor.usr_standing_penalty usp
6822                     JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6823               WHERE usr = match_user
6824                     AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6825                     AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6826                     AND csp.block_list LIKE '%HOLD%' LOOP
6827     
6828             result.fail_part := standing_penalty.name;
6829             result.success := FALSE;
6830             done := TRUE;
6831             RETURN NEXT result;
6832         END LOOP;
6833     
6834         IF hold_test.stop_blocked_user IS TRUE THEN
6835             FOR standing_penalty IN
6836                 SELECT  DISTINCT csp.*
6837                   FROM  actor.usr_standing_penalty usp
6838                         JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
6839                   WHERE usr = match_user
6840                         AND usp.org_unit IN ( SELECT * FROM explode_array(context_org_list) )
6841                         AND (usp.stop_date IS NULL or usp.stop_date > NOW())
6842                         AND csp.block_list LIKE '%CIRC%' LOOP
6843         
6844                 result.fail_part := standing_penalty.name;
6845                 result.success := FALSE;
6846                 done := TRUE;
6847                 RETURN NEXT result;
6848             END LOOP;
6849         END IF;
6850     
6851         IF hold_test.max_holds IS NOT NULL THEN
6852             SELECT    INTO hold_count COUNT(*)
6853               FROM    action.hold_request
6854               WHERE    usr = match_user
6855                 AND fulfillment_time IS NULL
6856                 AND cancel_time IS NULL
6857                 AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;
6858     
6859             IF hold_count >= hold_test.max_holds THEN
6860                 result.fail_part := 'config.hold_matrix_test.max_holds';
6861                 result.success := FALSE;
6862                 done := TRUE;
6863                 RETURN NEXT result;
6864             END IF;
6865         END IF;
6866     END IF;
6867
6868     IF item_object.age_protect IS NOT NULL THEN
6869         SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
6870
6871         IF item_object.create_date + age_protect_object.age > NOW() THEN
6872             IF hold_test.distance_is_from_owner THEN
6873                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_cn_object.owning_lib AND to_org = pickup_ou;
6874             ELSE
6875                 SELECT INTO hold_transit_prox prox FROM actor.org_unit_proximity WHERE from_org = item_object.circ_lib AND to_org = pickup_ou;
6876             END IF;
6877
6878             IF hold_transit_prox > age_protect_object.prox THEN
6879                 result.fail_part := 'config.rule_age_hold_protect.prox';
6880                 result.success := FALSE;
6881                 done := TRUE;
6882                 RETURN NEXT result;
6883             END IF;
6884         END IF;
6885     END IF;
6886
6887     IF NOT done THEN
6888         RETURN NEXT result;
6889     END IF;
6890
6891     RETURN;
6892 END;
6893 $func$ LANGUAGE plpgsql;
6894
6895 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$
6896     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE);
6897 $func$ LANGUAGE SQL;
6898
6899 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$
6900     SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );
6901 $func$ LANGUAGE SQL;
6902
6903 -- New post-delete trigger to propagate deletions to parent(s)
6904
6905 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
6906 BEGIN
6907
6908     -- Having deleted a renewal, we can delete the original circulation (or a previous
6909     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
6910     -- deletion of any prior parents, etc. recursively.
6911
6912     IF OLD.parent_circ IS NOT NULL THEN
6913         DELETE FROM action.circulation
6914         WHERE id = OLD.parent_circ;
6915     END IF;
6916
6917     RETURN OLD;
6918 END;
6919 $$ LANGUAGE 'plpgsql';
6920
6921 CREATE TRIGGER age_parent_circ AFTER DELETE ON action.circulation
6922 FOR EACH ROW EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
6923
6924 -- This only gets inserted if there are no other id > 100 billing types
6925 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;
6926 SELECT SETVAL('config.billing_type_id_seq'::TEXT, 101) FROM config.billing_type_id_seq WHERE last_value < 101;
6927
6928 -- Populate xact_type column in the materialized version of billable_xact_summary
6929
6930 CREATE OR REPLACE FUNCTION money.mat_summary_create () RETURNS TRIGGER AS $$
6931 BEGIN
6932         INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
6933                 VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
6934         RETURN NEW;
6935 END;
6936 $$ LANGUAGE PLPGSQL;
6937  
6938 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON action.circulation;
6939 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('circulation');
6940  
6941 DROP TRIGGER IF EXISTS mat_summary_create_tgr ON money.grocery;
6942 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON money.grocery FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('grocery');
6943
6944 CREATE RULE money_payment_view_update AS ON UPDATE TO money.payment_view DO INSTEAD 
6945     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;
6946
6947 -- Generate the equivalent of compound subject entries from the existing rows
6948 -- so that we don't have to laboriously reindex them
6949
6950 --INSERT INTO config.metabib_field (field_class, name, format, xpath ) VALUES
6951 --    ( 'subject', 'complete', 'mods32', $$//mods32:mods/mods32:subject//text()$$ );
6952 --
6953 --CREATE INDEX metabib_subject_field_entry_source_idx ON metabib.subject_field_entry (source);
6954 --
6955 --INSERT INTO metabib.subject_field_entry (source, field, value)
6956 --    SELECT source, (
6957 --            SELECT id 
6958 --            FROM config.metabib_field
6959 --            WHERE field_class = 'subject' AND name = 'complete'
6960 --        ), 
6961 --        ARRAY_TO_STRING ( 
6962 --            ARRAY (
6963 --                SELECT value 
6964 --                FROM metabib.subject_field_entry msfe
6965 --                WHERE msfe.source = groupee.source
6966 --                ORDER BY source 
6967 --            ), ' ' 
6968 --        ) AS grouped
6969 --    FROM ( 
6970 --        SELECT source
6971 --        FROM metabib.subject_field_entry
6972 --        GROUP BY source
6973 --    ) AS groupee;
6974
6975 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_del () RETURNS TRIGGER AS $$
6976 DECLARE
6977         prev_billing    money.billing%ROWTYPE;
6978         old_billing     money.billing%ROWTYPE;
6979 BEGIN
6980         SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
6981         SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
6982
6983         IF OLD.id = old_billing.id THEN
6984                 UPDATE  money.materialized_billable_xact_summary
6985                   SET   last_billing_ts = prev_billing.billing_ts,
6986                         last_billing_note = prev_billing.note,
6987                         last_billing_type = prev_billing.billing_type
6988                   WHERE id = OLD.xact;
6989         END IF;
6990
6991         IF NOT OLD.voided THEN
6992                 UPDATE  money.materialized_billable_xact_summary
6993                   SET   total_owed = total_owed - OLD.amount,
6994                         balance_owed = balance_owed + OLD.amount
6995                   WHERE id = OLD.xact;
6996         END IF;
6997
6998         RETURN OLD;
6999 END;
7000 $$ LANGUAGE PLPGSQL;
7001
7002 -- ARG! need to rid ourselves of the broken table definition ... this mechanism is not ideal, sorry.
7003 DROP TABLE IF EXISTS config.index_normalizer CASCADE;
7004
7005 CREATE OR REPLACE FUNCTION public.naco_normalize( TEXT, TEXT ) RETURNS TEXT AS $func$
7006
7007     use strict;
7008     use Unicode::Normalize;
7009     use Encode;
7010
7011     my $str = decode_utf8(shift);
7012     my $sf = shift;
7013
7014     # Apply NACO normalization to input string; based on
7015     # http://www.loc.gov/catdir/pcc/naco/SCA_PccNormalization_Final_revised.pdf
7016     #
7017     # Note that unlike a strict reading of the NACO normalization rules,
7018     # output is returned as lowercase instead of uppercase for compatibility
7019     # with previous versions of the Evergreen naco_normalize routine.
7020
7021     # Convert to upper-case first; even though final output will be lowercase, doing this will
7022     # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
7023     # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
7024     $str = uc $str;
7025
7026     # remove non-filing strings
7027     $str =~ s/\x{0098}.*?\x{009C}//g;
7028
7029     $str = NFKD($str);
7030
7031     # additional substitutions - 3.6.
7032     $str =~ s/\x{00C6}/AE/g;
7033     $str =~ s/\x{00DE}/TH/g;
7034     $str =~ s/\x{0152}/OE/g;
7035     $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}]['/DDOLl/d;
7036
7037     # transformations based on Unicode category codes
7038     $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;
7039
7040         if ($sf && $sf =~ /^a/o) {
7041                 my $commapos = index($str, ',');
7042                 if ($commapos > -1) {
7043                         if ($commapos != length($str) - 1) {
7044                 $str =~ s/,/\x07/; # preserve first comma
7045                         }
7046                 }
7047         }
7048
7049     # since we've stripped out the control characters, we can now
7050     # use a few as placeholders temporarily
7051     $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
7052     $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;
7053     $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;
7054
7055     # decimal digits
7056     $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/;
7057
7058     # intentionally skipping step 8 of the NACO algorithm; if the string
7059     # gets normalized away, that's fine.
7060
7061     # leading and trailing spaces
7062     $str =~ s/\s+/ /g;
7063     $str =~ s/^\s+//;
7064     $str =~ s/\s+$//g;
7065
7066     return lc $str;
7067 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7068
7069 -- Some handy functions, based on existing ones, to provide optional ingest normalization
7070
7071 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7072         SELECT SUBSTRING($1,$2);
7073 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7074
7075 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7076         SELECT SUBSTRING($1,1,$2);
7077 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7078
7079 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
7080         SELECT public.naco_normalize($1,'a');
7081 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7082
7083 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
7084         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
7085 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7086
7087 -- And ... a table in which to register them
7088
7089 CREATE TABLE config.index_normalizer (
7090         id              SERIAL  PRIMARY KEY,
7091         name            TEXT    UNIQUE NOT NULL,
7092         description     TEXT,
7093         func            TEXT    NOT NULL,
7094         param_count     INT     NOT NULL DEFAULT 0
7095 );
7096
7097 CREATE TABLE config.metabib_field_index_norm_map (
7098         id      SERIAL  PRIMARY KEY,
7099         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7100         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7101         params  TEXT,
7102         pos     INT     NOT NULL DEFAULT 0
7103 );
7104
7105 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7106         'NACO Normalize',
7107         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7108         'naco_normalize',
7109         0
7110 );
7111
7112 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7113         'Normalize date range',
7114         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7115         'split_date_range',
7116         1
7117 );
7118
7119 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7120         'NACO Normalize -- retain first comma',
7121         '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.',
7122         'naco_normalize_keep_comma',
7123         0
7124 );
7125
7126 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7127         'Strip Diacritics',
7128         'Convert text to NFD form and remove non-spacing combining marks.',
7129         'remove_diacritics',
7130         0
7131 );
7132
7133 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7134         'Up-case',
7135         'Convert text upper case.',
7136         'uppercase',
7137         0
7138 );
7139
7140 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7141         'Down-case',
7142         'Convert text lower case.',
7143         'lowercase',
7144         0
7145 );
7146
7147 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7148         'Extract Dewey-like number',
7149         'Extract a string of numeric characters ther resembles a DDC number.',
7150         'call_number_dewey',
7151         0
7152 );
7153
7154 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7155         'Left truncation',
7156         'Discard the specified number of characters from the left side of the string.',
7157         'left_trunc',
7158         1
7159 );
7160
7161 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7162         'Right truncation',
7163         'Include only the specified number of characters from the left side of the string.',
7164         'right_trunc',
7165         1
7166 );
7167
7168 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7169         'First word',
7170         'Include only the first space-separated word of a string.',
7171         'first_word',
7172         0
7173 );
7174
7175 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7176         SELECT  m.id,
7177                 i.id
7178           FROM  config.metabib_field m,
7179                 config.index_normalizer i
7180           WHERE i.func IN ('naco_normalize','split_date_range');
7181
7182 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7183 DECLARE
7184     normalizer      RECORD;
7185     value           TEXT := '';
7186 BEGIN
7187
7188     value := NEW.value;
7189
7190     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7191         FOR normalizer IN
7192             SELECT  n.func AS func,
7193                     n.param_count AS param_count,
7194                     m.params AS params
7195               FROM  config.index_normalizer n
7196                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7197               WHERE field = NEW.field AND m.pos < 0
7198               ORDER BY m.pos LOOP
7199                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7200                     quote_literal( value ) ||
7201                     CASE
7202                         WHEN normalizer.param_count > 0
7203                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7204                             ELSE ''
7205                         END ||
7206                     ')' INTO value;
7207
7208         END LOOP;
7209
7210         NEW.value := value;
7211     END IF;
7212
7213     IF NEW.index_vector = ''::tsvector THEN
7214         RETURN NEW;
7215     END IF;
7216
7217     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7218         FOR normalizer IN
7219             SELECT  n.func AS func,
7220                     n.param_count AS param_count,
7221                     m.params AS params
7222               FROM  config.index_normalizer n
7223                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7224               WHERE field = NEW.field AND m.pos >= 0
7225               ORDER BY m.pos LOOP
7226                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7227                     quote_literal( value ) ||
7228                     CASE
7229                         WHEN normalizer.param_count > 0
7230                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7231                             ELSE ''
7232                         END ||
7233                     ')' INTO value;
7234
7235         END LOOP;
7236     END IF;
7237
7238     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7239         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7240     ELSE
7241         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7242     END IF;
7243
7244     RETURN NEW;
7245 END;
7246 $$ LANGUAGE PLPGSQL;
7247
7248 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7249
7250 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7251
7252 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7253     SELECT  ARRAY_TO_STRING(
7254                 oils_xpath(
7255                     $1 ||
7256                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7257                     $2,
7258                     $4
7259                 ),
7260                 $3
7261             );
7262 $func$ LANGUAGE SQL IMMUTABLE;
7263
7264 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7265     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7266 $func$ LANGUAGE SQL IMMUTABLE;
7267
7268 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7269     SELECT oils_xpath_string( $1, $2, '', $3 );
7270 $func$ LANGUAGE SQL IMMUTABLE;
7271
7272 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7273     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7274 $func$ LANGUAGE SQL IMMUTABLE;
7275
7276 CREATE TYPE metabib.field_entry_template AS (
7277         field_class     TEXT,
7278         field           INT,
7279         source          BIGINT,
7280         value           TEXT
7281 );
7282
7283 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7284   use strict;
7285
7286   use XML::LibXSLT;
7287   use XML::LibXML;
7288
7289   my $doc = shift;
7290   my $xslt = shift;
7291
7292   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7293   # methods of parsing XML documents and stylesheets, in the hopes of broader
7294   # compatibility with distributions
7295   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7296
7297   # Cache the XML parser, if we do not already have one
7298   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7299     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7300
7301   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7302
7303   # Cache the XSLT processor, if we do not already have one
7304   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7305     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7306
7307   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7308     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7309
7310   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7311     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7312
7313   return $stylesheet->output_string(
7314     $stylesheet->transform(
7315       $parser->parse_string($doc)
7316     )
7317   );
7318
7319 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7320
7321 -- Add two columns so that the following function will compile.
7322 -- Eventually the label column will be NOT NULL, but not yet.
7323 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7324 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7325
7326 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7327 DECLARE
7328     bib     biblio.record_entry%ROWTYPE;
7329     idx     config.metabib_field%ROWTYPE;
7330     xfrm        config.xml_transform%ROWTYPE;
7331     prev_xfrm   TEXT;
7332     transformed_xml TEXT;
7333     xml_node    TEXT;
7334     xml_node_list   TEXT[];
7335     facet_text  TEXT;
7336     raw_text    TEXT;
7337     curr_text   TEXT;
7338     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7339     output_row  metabib.field_entry_template%ROWTYPE;
7340 BEGIN
7341
7342     -- Get the record
7343     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7344
7345     -- Loop over the indexing entries
7346     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7347
7348         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7349
7350         -- See if we can skip the XSLT ... it's expensive
7351         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7352             -- Can't skip the transform
7353             IF xfrm.xslt <> '---' THEN
7354                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7355             ELSE
7356                 transformed_xml := bib.marc;
7357             END IF;
7358
7359             prev_xfrm := xfrm.name;
7360         END IF;
7361
7362         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7363
7364         raw_text := NULL;
7365         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7366             CONTINUE WHEN xml_node !~ E'^\\s*<';
7367
7368             curr_text := ARRAY_TO_STRING(
7369                 oils_xpath( '//text()',
7370                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7371                         REGEXP_REPLACE( -- This escapes embeded <s
7372                             xml_node,
7373                             $re$(>[^<]+)(<)([^>]+<)$re$,
7374                             E'\\1&lt;\\3',
7375                             'g'
7376                         ),
7377                         '&(?!amp;)',
7378                         '&amp;',
7379                         'g'
7380                     )
7381                 ),
7382                 ' '
7383             );
7384
7385             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7386
7387             IF raw_text IS NOT NULL THEN
7388                 raw_text := raw_text || joiner;
7389             END IF;
7390
7391             raw_text := COALESCE(raw_text,'') || curr_text;
7392
7393             -- insert raw node text for faceting
7394             IF idx.facet_field THEN
7395
7396                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7397                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7398                 ELSE
7399                     facet_text := curr_text;
7400                 END IF;
7401
7402                 output_row.field_class = idx.field_class;
7403                 output_row.field = -1 * idx.id;
7404                 output_row.source = rid;
7405                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7406
7407                 RETURN NEXT output_row;
7408             END IF;
7409
7410         END LOOP;
7411
7412         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7413
7414         -- insert combined node text for searching
7415         IF idx.search_field THEN
7416             output_row.field_class = idx.field_class;
7417             output_row.field = idx.id;
7418             output_row.source = rid;
7419             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7420
7421             RETURN NEXT output_row;
7422         END IF;
7423
7424     END LOOP;
7425
7426 END;
7427 $func$ LANGUAGE PLPGSQL;
7428
7429 -- default to a space joiner
7430 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7431         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7432 $func$ LANGUAGE SQL;
7433
7434 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7435
7436 use MARC::Record;
7437 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7438
7439 my $xml = shift;
7440 my $r = MARC::Record->new_from_xml( $xml );
7441
7442 return_next( { tag => 'LDR', value => $r->leader } );
7443
7444 for my $f ( $r->fields ) {
7445     if ($f->is_control_field) {
7446         return_next({ tag => $f->tag, value => $f->data });
7447     } else {
7448         for my $s ($f->subfields) {
7449             return_next({
7450                 tag      => $f->tag,
7451                 ind1     => $f->indicator(1),
7452                 ind2     => $f->indicator(2),
7453                 subfield => $s->[0],
7454                 value    => $s->[1]
7455             });
7456
7457             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7458                 my $trim = $f->indicator(2) || 0;
7459                 return_next({
7460                     tag      => 'tnf',
7461                     ind1     => $f->indicator(1),
7462                     ind2     => $f->indicator(2),
7463                     subfield => 'a',
7464                     value    => substr( $s->[1], $trim )
7465                 });
7466             }
7467         }
7468     }
7469 }
7470
7471 return undef;
7472
7473 $func$ LANGUAGE PLPERLU;
7474
7475 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7476 DECLARE
7477     bib biblio.record_entry%ROWTYPE;
7478     output  metabib.full_rec%ROWTYPE;
7479     field   RECORD;
7480 BEGIN
7481     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7482
7483     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7484         output.record := rid;
7485         output.ind1 := field.ind1;
7486         output.ind2 := field.ind2;
7487         output.tag := field.tag;
7488         output.subfield := field.subfield;
7489         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7490             output.value := naco_normalize(field.value, field.subfield);
7491         ELSE
7492             output.value := field.value;
7493         END IF;
7494
7495         CONTINUE WHEN output.value IS NULL;
7496
7497         RETURN NEXT output;
7498     END LOOP;
7499 END;
7500 $func$ LANGUAGE PLPGSQL;
7501
7502 -- functions to create auditor objects
7503
7504 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7505 BEGIN
7506     EXECUTE $$
7507         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7508     $$;
7509         RETURN TRUE;
7510 END;
7511 $creator$ LANGUAGE 'plpgsql';
7512
7513 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7514 BEGIN
7515     EXECUTE $$
7516         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7517             audit_id    BIGINT                          PRIMARY KEY,
7518             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7519             audit_action        TEXT                            NOT NULL,
7520             LIKE $$ || sch || $$.$$ || tbl || $$
7521         );
7522     $$;
7523         RETURN TRUE;
7524 END;
7525 $creator$ LANGUAGE 'plpgsql';
7526
7527 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7528 BEGIN
7529     EXECUTE $$
7530         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7531         RETURNS TRIGGER AS $func$
7532         BEGIN
7533             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7534                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7535                     now(),
7536                     SUBSTR(TG_OP,1,1),
7537                     OLD.*;
7538             RETURN NULL;
7539         END;
7540         $func$ LANGUAGE 'plpgsql';
7541     $$;
7542         RETURN TRUE;
7543 END;
7544 $creator$ LANGUAGE 'plpgsql';
7545
7546 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7547 BEGIN
7548     EXECUTE $$
7549         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7550             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7551             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7552     $$;
7553         RETURN TRUE;
7554 END;
7555 $creator$ LANGUAGE 'plpgsql';
7556
7557 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7558 BEGIN
7559     EXECUTE $$
7560         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7561             SELECT      -1, now() as audit_time, '-' as audit_action, *
7562               FROM      $$ || sch || $$.$$ || tbl || $$
7563                 UNION ALL
7564             SELECT      *
7565               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7566     $$;
7567         RETURN TRUE;
7568 END;
7569 $creator$ LANGUAGE 'plpgsql';
7570
7571 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7572
7573 -- The main event
7574
7575 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7576 BEGIN
7577     PERFORM auditor.create_auditor_seq(sch, tbl);
7578     PERFORM auditor.create_auditor_history(sch, tbl);
7579     PERFORM auditor.create_auditor_func(sch, tbl);
7580     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7581     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7582     RETURN TRUE;
7583 END;
7584 $creator$ LANGUAGE 'plpgsql';
7585
7586 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7587
7588 ALTER TABLE action.hold_request
7589 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7590
7591 ALTER TABLE action.hold_request
7592 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7593
7594 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7595
7596 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7597
7598 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7599
7600 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7601
7602 -- Add claims_never_checked_out_count to actor.usr, related history
7603
7604 ALTER TABLE actor.usr ADD COLUMN
7605         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7606
7607 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7608         claims_never_checked_out_count INT;
7609
7610 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7611
7612 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7613
7614 -----------
7615
7616 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7617 BEGIN
7618         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7619                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7620                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7621                 END IF;
7622                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7623                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7624                 END IF;
7625                 IF NEW.stop_fines = 'LOST' THEN
7626                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7627                 END IF;
7628         END IF;
7629         RETURN NEW;
7630 END;
7631 $$ LANGUAGE 'plpgsql';
7632
7633 -- Create new table acq.fund_allocation_percent
7634 -- Populate it from acq.fund_allocation
7635 -- Convert all percentages to amounts in acq.fund_allocation
7636
7637 CREATE TABLE acq.fund_allocation_percent
7638 (
7639     id                   SERIAL            PRIMARY KEY,
7640     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7641                                                DEFERRABLE INITIALLY DEFERRED,
7642     org                  INT               NOT NULL REFERENCES actor.org_unit
7643                                                DEFERRABLE INITIALLY DEFERRED,
7644     fund_code            TEXT,
7645     percent              NUMERIC           NOT NULL,
7646     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7647                                                DEFERRABLE INITIALLY DEFERRED,
7648     note                 TEXT,
7649     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7650     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7651     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7652 );
7653
7654 -- Trigger function to validate combination of org_unit and fund_code
7655
7656 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7657 RETURNS TRIGGER AS $$
7658 --
7659 DECLARE
7660 --
7661 dummy int := 0;
7662 --
7663 BEGIN
7664     SELECT
7665         1
7666     INTO
7667         dummy
7668     FROM
7669         acq.fund
7670     WHERE
7671         org = NEW.org
7672         AND code = NEW.fund_code
7673         LIMIT 1;
7674     --
7675     IF dummy = 1 then
7676         RETURN NEW;
7677     ELSE
7678         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7679     END IF;
7680 END;
7681 $$ LANGUAGE plpgsql;
7682
7683 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7684     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7685     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7686
7687 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7688 RETURNS TRIGGER AS $$
7689 DECLARE
7690 --
7691 total_percent numeric;
7692 --
7693 BEGIN
7694     SELECT
7695         sum( percent )
7696     INTO
7697         total_percent
7698     FROM
7699         acq.fund_allocation_percent AS fap
7700     WHERE
7701         fap.funding_source = NEW.funding_source;
7702     --
7703     IF total_percent > 100 THEN
7704         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7705             NEW.funding_source;
7706     ELSE
7707         RETURN NEW;
7708     END IF;
7709 END;
7710 $$ LANGUAGE plpgsql;
7711
7712 CREATE TRIGGER acqfap_limit_100_trig
7713     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7714     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7715
7716 -- Populate new table from acq.fund_allocation
7717
7718 INSERT INTO acq.fund_allocation_percent
7719 (
7720     funding_source,
7721     org,
7722     fund_code,
7723     percent,
7724     allocator,
7725     note,
7726     create_time
7727 )
7728     SELECT
7729         fa.funding_source,
7730         fund.org,
7731         fund.code,
7732         fa.percent,
7733         fa.allocator,
7734         fa.note,
7735         fa.create_time
7736     FROM
7737         acq.fund_allocation AS fa
7738             INNER JOIN acq.fund AS fund
7739                 ON ( fa.fund = fund.id )
7740     WHERE
7741         fa.percent is not null
7742     ORDER BY
7743         fund.org;
7744
7745 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7746
7747 -- Algorithm to apply to each funding source:
7748
7749 -- 1. Add up the credits.
7750 -- 2. Add up the percentages.
7751 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7752 --    fractional cents from the result.  This is the total amount to be allocated.
7753 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7754 --    fractional cents to get a preliminary amount.
7755 -- 5. Add up the preliminary amounts for all the allocations.
7756 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7757 --    number of residual cents (resulting from having dropped fractional cents) that
7758 --    must be distributed across the funds in order to make the total of the amounts
7759 --    match the total allocation.
7760 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7761 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7762 --    for each successive fund, until all the residual cents have been exhausted.
7763
7764 -- Result: the sum of the individual allocations now equals the total to be allocated,
7765 -- to the penny.  The individual amounts match the percentages as closely as possible,
7766 -- given the constraint that the total must match.
7767
7768 CREATE OR REPLACE FUNCTION acq.apply_percents()
7769 RETURNS VOID AS $$
7770 declare
7771 --
7772 tot              RECORD;
7773 fund             RECORD;
7774 tot_cents        INTEGER;
7775 src              INTEGER;
7776 id               INTEGER[];
7777 curr_id          INTEGER;
7778 pennies          NUMERIC[];
7779 curr_amount      NUMERIC;
7780 i                INTEGER;
7781 total_of_floors  INTEGER;
7782 total_percent    NUMERIC;
7783 total_allocation INTEGER;
7784 residue          INTEGER;
7785 --
7786 begin
7787         RAISE NOTICE 'Applying percents';
7788         FOR tot IN
7789                 SELECT
7790                         fsrc.funding_source,
7791                         sum( fsrc.amount ) AS total
7792                 FROM
7793                         acq.funding_source_credit AS fsrc
7794                 WHERE fsrc.funding_source IN
7795                         ( SELECT DISTINCT fa.funding_source
7796                           FROM acq.fund_allocation AS fa
7797                           WHERE fa.percent IS NOT NULL )
7798                 GROUP BY
7799                         fsrc.funding_source
7800         LOOP
7801                 tot_cents = floor( tot.total * 100 );
7802                 src = tot.funding_source;
7803                 RAISE NOTICE 'Funding source % total %',
7804                         src, tot_cents;
7805                 i := 0;
7806                 total_of_floors := 0;
7807                 total_percent := 0;
7808                 --
7809                 FOR fund in
7810                         SELECT
7811                                 fa.id,
7812                                 fa.percent,
7813                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7814                         FROM
7815                                 acq.fund_allocation AS fa
7816                         WHERE
7817                                 fa.funding_source = src
7818                                 AND fa.percent IS NOT NULL
7819                         ORDER BY
7820                                 mod( fa.percent * tot_cents / 100, 1 ),
7821                                 fa.fund,
7822                                 fa.id
7823                 LOOP
7824                         RAISE NOTICE '   %: %',
7825                                 fund.id,
7826                                 fund.floor_pennies;
7827                         i := i + 1;
7828                         id[i] = fund.id;
7829                         pennies[i] = fund.floor_pennies;
7830                         total_percent := total_percent + fund.percent;
7831                         total_of_floors := total_of_floors + pennies[i];
7832                 END LOOP;
7833                 total_allocation := floor( total_percent * tot_cents /100 );
7834                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7835                 residue := total_allocation - total_of_floors;
7836                 RAISE NOTICE 'Residue: %', residue;
7837                 --
7838                 -- Post the calculated amounts, revising as needed to
7839                 -- distribute the rounding error
7840                 --
7841                 WHILE i > 0 LOOP
7842                         IF residue > 0 THEN
7843                                 pennies[i] = pennies[i] + 1;
7844                                 residue := residue - 1;
7845                         END IF;
7846                         --
7847                         -- Post amount
7848                         --
7849                         curr_id     := id[i];
7850                         curr_amount := trunc( pennies[i] / 100, 2 );
7851                         --
7852                         UPDATE
7853                                 acq.fund_allocation AS fa
7854                         SET
7855                                 amount = curr_amount,
7856                                 percent = NULL
7857                         WHERE
7858                                 fa.id = curr_id;
7859                         --
7860                         RAISE NOTICE '   ID % and amount %',
7861                                 curr_id,
7862                                 curr_amount;
7863                         i = i - 1;
7864                 END LOOP;
7865         END LOOP;
7866 end;
7867 $$ LANGUAGE 'plpgsql';
7868
7869 -- Run the temporary function
7870
7871 select * from acq.apply_percents();
7872
7873 -- Drop the temporary function now that we're done with it
7874
7875 DROP FUNCTION IF EXISTS acq.apply_percents();
7876
7877 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7878
7879 -- If the following step fails, it's probably because there are still some non-null percent values in
7880 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7881 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7882 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7883 -- slipped in afterwards.
7884
7885 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7886 -- procedure acq.apply_percents() as defined above.
7887
7888 ALTER TABLE acq.fund_allocation
7889 ALTER COLUMN amount SET NOT NULL;
7890
7891 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7892     SELECT  fund,
7893             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7894     FROM acq.fund_allocation a
7895          JOIN acq.fund f ON (a.fund = f.id)
7896          JOIN acq.funding_source s ON (a.funding_source = s.id)
7897     GROUP BY 1;
7898
7899 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7900     SELECT  funding_source,
7901             SUM(a.amount)::NUMERIC(100,2) AS amount
7902     FROM  acq.fund_allocation a
7903     GROUP BY 1;
7904
7905 ALTER TABLE acq.fund_allocation
7906 DROP COLUMN percent;
7907
7908 CREATE TABLE asset.copy_location_order
7909 (
7910         id              SERIAL           PRIMARY KEY,
7911         location        INT              NOT NULL
7912                                              REFERENCES asset.copy_location
7913                                              ON DELETE CASCADE
7914                                              DEFERRABLE INITIALLY DEFERRED,
7915         org             INT              NOT NULL
7916                                              REFERENCES actor.org_unit
7917                                              ON DELETE CASCADE
7918                                              DEFERRABLE INITIALLY DEFERRED,
7919         position        INT              NOT NULL DEFAULT 0,
7920         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7921 );
7922
7923 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7924
7925 -- If you ran this before its most recent incarnation:
7926 -- delete from config.upgrade_log where version = '0328';
7927 -- alter table money.credit_card_payment drop column cc_name;
7928
7929 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7930 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7931
7932 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$
7933 DECLARE
7934     current_group    permission.grp_tree%ROWTYPE;
7935     user_object    actor.usr%ROWTYPE;
7936     item_object    asset.copy%ROWTYPE;
7937     cn_object    asset.call_number%ROWTYPE;
7938     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7939     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7940     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7941 BEGIN
7942     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7943     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7944     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7945     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7946     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7947
7948     LOOP 
7949         -- for each potential matchpoint for this ou and group ...
7950         FOR current_mp IN
7951             SELECT  m.*
7952               FROM  config.circ_matrix_matchpoint m
7953                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7954                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7955               WHERE m.grp = current_group.id
7956                     AND m.active
7957                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7958                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7959               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7960                     CASE WHEN m.copy_owning_lib IS NOT NULL
7961                         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 )
7962                         ELSE 0
7963                     END +
7964                     CASE WHEN m.copy_circ_lib IS NOT NULL
7965                         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 )
7966                         ELSE 0
7967                     END +
7968                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7969                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7970                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7971                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7972                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7973                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7974                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7975                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7976                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7977
7978             IF current_mp.is_renewal IS NOT NULL THEN
7979                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7980             END IF;
7981
7982             IF current_mp.circ_modifier IS NOT NULL THEN
7983                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7984             END IF;
7985
7986             IF current_mp.marc_type IS NOT NULL THEN
7987                 IF item_object.circ_as_type IS NOT NULL THEN
7988                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7989                 ELSE
7990                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7991                 END IF;
7992             END IF;
7993
7994             IF current_mp.marc_form IS NOT NULL THEN
7995                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7996             END IF;
7997
7998             IF current_mp.marc_vr_format IS NOT NULL THEN
7999                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
8000             END IF;
8001
8002             IF current_mp.ref_flag IS NOT NULL THEN
8003                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
8004             END IF;
8005
8006             IF current_mp.juvenile_flag IS NOT NULL THEN
8007                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
8008             END IF;
8009
8010             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
8011                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
8012             END IF;
8013
8014             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
8015                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
8016             END IF;
8017
8018
8019             -- everything was undefined or matched
8020             matchpoint = current_mp;
8021
8022             EXIT WHEN matchpoint.id IS NOT NULL;
8023         END LOOP;
8024
8025         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
8026
8027         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
8028     END LOOP;
8029
8030     RETURN matchpoint;
8031 END;
8032 $func$ LANGUAGE plpgsql;
8033
8034 CREATE TYPE action.hold_stats AS (
8035     hold_count              INT,
8036     copy_count              INT,
8037     available_count         INT,
8038     total_copy_ratio        FLOAT,
8039     available_copy_ratio    FLOAT
8040 );
8041
8042 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
8043 DECLARE
8044     output          action.hold_stats%ROWTYPE;
8045     hold_count      INT := 0;
8046     copy_count      INT := 0;
8047     available_count INT := 0;
8048     hold_map_data   RECORD;
8049 BEGIN
8050
8051     output.hold_count := 0;
8052     output.copy_count := 0;
8053     output.available_count := 0;
8054
8055     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
8056       FROM  action.hold_copy_map m
8057             JOIN action.hold_request h ON (m.hold = h.id)
8058       WHERE m.target_copy = copy_id
8059             AND NOT h.frozen;
8060
8061     output.hold_count := hold_count;
8062
8063     IF output.hold_count > 0 THEN
8064         FOR hold_map_data IN
8065             SELECT  DISTINCT m.target_copy,
8066                     acp.status
8067               FROM  action.hold_copy_map m
8068                     JOIN asset.copy acp ON (m.target_copy = acp.id)
8069                     JOIN action.hold_request h ON (m.hold = h.id)
8070               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
8071         LOOP
8072             output.copy_count := output.copy_count + 1;
8073             IF hold_map_data.status IN (0,7,12) THEN
8074                 output.available_count := output.available_count + 1;
8075             END IF;
8076         END LOOP;
8077         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
8078         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
8079
8080     END IF;
8081
8082     RETURN output;
8083
8084 END;
8085 $func$ LANGUAGE PLPGSQL;
8086
8087 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
8088 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
8089
8090 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
8091
8092 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8093 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8094
8095 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8096     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8097     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8098     copy_owning_lib
8099 );
8100
8101 -- Return the correct fail_part when the item can't be found
8102 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$
8103 DECLARE
8104     user_object        actor.usr%ROWTYPE;
8105     standing_penalty    config.standing_penalty%ROWTYPE;
8106     item_object        asset.copy%ROWTYPE;
8107     item_status_object    config.copy_status%ROWTYPE;
8108     item_location_object    asset.copy_location%ROWTYPE;
8109     result            action.matrix_test_result;
8110     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8111     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8112     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8113     hold_ratio          action.hold_stats%ROWTYPE;
8114     penalty_type         TEXT;
8115     tmp_grp         INT;
8116     items_out        INT;
8117     context_org_list        INT[];
8118     done            BOOL := FALSE;
8119 BEGIN
8120     result.success := TRUE;
8121
8122     -- Fail if the user is BARRED
8123     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8124
8125     -- Fail if we couldn't find the user 
8126     IF user_object.id IS NULL THEN
8127         result.fail_part := 'no_user';
8128         result.success := FALSE;
8129         done := TRUE;
8130         RETURN NEXT result;
8131         RETURN;
8132     END IF;
8133
8134     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8135
8136     -- Fail if we couldn't find the item 
8137     IF item_object.id IS NULL THEN
8138         result.fail_part := 'no_item';
8139         result.success := FALSE;
8140         done := TRUE;
8141         RETURN NEXT result;
8142         RETURN;
8143     END IF;
8144
8145     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8146     result.matchpoint := circ_test.id;
8147
8148     -- Fail if we couldn't find a matchpoint
8149     IF result.matchpoint IS NULL THEN
8150         result.fail_part := 'no_matchpoint';
8151         result.success := FALSE;
8152         done := TRUE;
8153         RETURN NEXT result;
8154     END IF;
8155
8156     IF user_object.barred IS TRUE THEN
8157         result.fail_part := 'actor.usr.barred';
8158         result.success := FALSE;
8159         done := TRUE;
8160         RETURN NEXT result;
8161     END IF;
8162
8163     -- Fail if the item can't circulate
8164     IF item_object.circulate IS FALSE THEN
8165         result.fail_part := 'asset.copy.circulate';
8166         result.success := FALSE;
8167         done := TRUE;
8168         RETURN NEXT result;
8169     END IF;
8170
8171     -- Fail if the item isn't in a circulateable status on a non-renewal
8172     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8173         result.fail_part := 'asset.copy.status';
8174         result.success := FALSE;
8175         done := TRUE;
8176         RETURN NEXT result;
8177     ELSIF renewal AND item_object.status <> 1 THEN
8178         result.fail_part := 'asset.copy.status';
8179         result.success := FALSE;
8180         done := TRUE;
8181         RETURN NEXT result;
8182     END IF;
8183
8184     -- Fail if the item can't circulate because of the shelving location
8185     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8186     IF item_location_object.circulate IS FALSE THEN
8187         result.fail_part := 'asset.copy_location.circulate';
8188         result.success := FALSE;
8189         done := TRUE;
8190         RETURN NEXT result;
8191     END IF;
8192
8193     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8194
8195     -- Fail if the test is set to hard non-circulating
8196     IF circ_test.circulate IS FALSE THEN
8197         result.fail_part := 'config.circ_matrix_test.circulate';
8198         result.success := FALSE;
8199         done := TRUE;
8200         RETURN NEXT result;
8201     END IF;
8202
8203     -- Fail if the total copy-hold ratio is too low
8204     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8205         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8206         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8207             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8208             result.success := FALSE;
8209             done := TRUE;
8210             RETURN NEXT result;
8211         END IF;
8212     END IF;
8213
8214     -- Fail if the available copy-hold ratio is too low
8215     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8216         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8217         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8218             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8219             result.success := FALSE;
8220             done := TRUE;
8221             RETURN NEXT result;
8222         END IF;
8223     END IF;
8224
8225     IF renewal THEN
8226         penalty_type = '%RENEW%';
8227     ELSE
8228         penalty_type = '%CIRC%';
8229     END IF;
8230
8231     FOR standing_penalty IN
8232         SELECT  DISTINCT csp.*
8233           FROM  actor.usr_standing_penalty usp
8234                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8235           WHERE usr = match_user
8236                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8237                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8238                 AND csp.block_list LIKE penalty_type LOOP
8239
8240         result.fail_part := standing_penalty.name;
8241         result.success := FALSE;
8242         done := TRUE;
8243         RETURN NEXT result;
8244     END LOOP;
8245
8246     -- Fail if the user has too many items with specific circ_modifiers checked out
8247     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8248         SELECT  INTO items_out COUNT(*)
8249           FROM  action.circulation circ
8250             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8251           WHERE circ.usr = match_user
8252                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8253             AND circ.checkin_time IS NULL
8254             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8255             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);
8256         IF items_out >= out_by_circ_mod.items_out THEN
8257             result.fail_part := 'config.circ_matrix_circ_mod_test';
8258             result.success := FALSE;
8259             done := TRUE;
8260             RETURN NEXT result;
8261         END IF;
8262     END LOOP;
8263
8264     -- If we passed everything, return the successful matchpoint id
8265     IF NOT done THEN
8266         RETURN NEXT result;
8267     END IF;
8268
8269     RETURN;
8270 END;
8271 $func$ LANGUAGE plpgsql;
8272
8273 CREATE TABLE config.remote_account (
8274     id          SERIAL  PRIMARY KEY,
8275     label       TEXT    NOT NULL,
8276     host        TEXT    NOT NULL,   -- name or IP, :port optional
8277     username    TEXT,               -- optional, since we could default to $USER
8278     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8279     account     TEXT,               -- aka profile or FTP "account" command
8280     path        TEXT,               -- aka directory
8281     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8282     last_activity TIMESTAMP WITH TIME ZONE
8283 );
8284
8285 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8286     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8287     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8288         vendcode    TEXT,
8289         vendacct    TEXT
8290
8291 ) INHERITS (config.remote_account);
8292
8293 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8294
8295 CREATE TABLE acq.claim_type (
8296         id             SERIAL           PRIMARY KEY,
8297         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8298                                                  DEFERRABLE INITIALLY DEFERRED,
8299         code           TEXT             NOT NULL,
8300         description    TEXT             NOT NULL,
8301         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8302 );
8303
8304 CREATE TABLE acq.claim (
8305         id             SERIAL           PRIMARY KEY,
8306         type           INT              NOT NULL REFERENCES acq.claim_type
8307                                                  DEFERRABLE INITIALLY DEFERRED,
8308         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8309                                                  DEFERRABLE INITIALLY DEFERRED
8310 );
8311
8312 CREATE TABLE acq.claim_policy (
8313         id              SERIAL       PRIMARY KEY,
8314         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8315                                      DEFERRABLE INITIALLY DEFERRED,
8316         name            TEXT         NOT NULL,
8317         description     TEXT         NOT NULL,
8318         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8319 );
8320
8321 -- Add a san column for EDI. 
8322 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8323
8324 ALTER TABLE acq.provider ADD COLUMN san INT;
8325
8326 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8327
8328 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8329 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8330
8331 ALTER TABLE acq.provider
8332         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8333
8334 ALTER TABLE acq.provider
8335         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8336
8337 ALTER TABLE acq.provider
8338         ADD COLUMN url TEXT;
8339
8340 ALTER TABLE acq.provider
8341         ADD COLUMN email TEXT;
8342
8343 ALTER TABLE acq.provider
8344         ADD COLUMN phone TEXT;
8345
8346 ALTER TABLE acq.provider
8347         ADD COLUMN fax_phone TEXT;
8348
8349 ALTER TABLE acq.provider
8350         ADD COLUMN default_claim_policy INT
8351                 REFERENCES acq.claim_policy
8352                 DEFERRABLE INITIALLY DEFERRED;
8353
8354 ALTER TABLE action.transit_copy
8355 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8356                                                          DEFERRABLE INITIALLY DEFERRED;
8357
8358 DROP SCHEMA IF EXISTS booking CASCADE;
8359
8360 CREATE SCHEMA booking;
8361
8362 CREATE TABLE booking.resource_type (
8363         id             SERIAL          PRIMARY KEY,
8364         name           TEXT            NOT NULL,
8365         fine_interval  INTERVAL,
8366         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8367         owner          INT             NOT NULL
8368                                        REFERENCES actor.org_unit( id )
8369                                        DEFERRABLE INITIALLY DEFERRED,
8370         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8371         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8372     record         BIGINT          REFERENCES biblio.record_entry (id)
8373                                        DEFERRABLE INITIALLY DEFERRED,
8374     max_fine       NUMERIC(8,2),
8375     elbow_room     INTERVAL,
8376     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8377 );
8378
8379 CREATE TABLE booking.resource (
8380         id             SERIAL           PRIMARY KEY,
8381         owner          INT              NOT NULL
8382                                         REFERENCES actor.org_unit(id)
8383                                         DEFERRABLE INITIALLY DEFERRED,
8384         type           INT              NOT NULL
8385                                         REFERENCES booking.resource_type(id)
8386                                         DEFERRABLE INITIALLY DEFERRED,
8387         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8388         barcode        TEXT             NOT NULL,
8389         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8390         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8391         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8392         CONSTRAINT br_unique UNIQUE (owner, barcode)
8393 );
8394
8395 -- For non-catalog items: hijack barcode for name/description
8396
8397 CREATE TABLE booking.resource_attr (
8398         id              SERIAL          PRIMARY KEY,
8399         owner           INT             NOT NULL
8400                                         REFERENCES actor.org_unit(id)
8401                                         DEFERRABLE INITIALLY DEFERRED,
8402         name            TEXT            NOT NULL,
8403         resource_type   INT             NOT NULL
8404                                         REFERENCES booking.resource_type(id)
8405                                         ON DELETE CASCADE
8406                                         DEFERRABLE INITIALLY DEFERRED,
8407         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8408         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8409 );
8410
8411 CREATE TABLE booking.resource_attr_value (
8412         id               SERIAL         PRIMARY KEY,
8413         owner            INT            NOT NULL
8414                                         REFERENCES actor.org_unit(id)
8415                                         DEFERRABLE INITIALLY DEFERRED,
8416         attr             INT            NOT NULL
8417                                         REFERENCES booking.resource_attr(id)
8418                                         DEFERRABLE INITIALLY DEFERRED,
8419         valid_value      TEXT           NOT NULL,
8420         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8421 );
8422
8423 CREATE TABLE booking.resource_attr_map (
8424         id               SERIAL         PRIMARY KEY,
8425         resource         INT            NOT NULL
8426                                         REFERENCES booking.resource(id)
8427                                         ON DELETE CASCADE
8428                                         DEFERRABLE INITIALLY DEFERRED,
8429         resource_attr    INT            NOT NULL
8430                                         REFERENCES booking.resource_attr(id)
8431                                         ON DELETE CASCADE
8432                                         DEFERRABLE INITIALLY DEFERRED,
8433         value            INT            NOT NULL
8434                                         REFERENCES booking.resource_attr_value(id)
8435                                         DEFERRABLE INITIALLY DEFERRED,
8436         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8437 );
8438
8439 CREATE TABLE booking.reservation (
8440         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8441         start_time       TIMESTAMPTZ,
8442         end_time         TIMESTAMPTZ,
8443         capture_time     TIMESTAMPTZ,
8444         cancel_time      TIMESTAMPTZ,
8445         pickup_time      TIMESTAMPTZ,
8446         return_time      TIMESTAMPTZ,
8447         booking_interval INTERVAL,
8448         fine_interval    INTERVAL,
8449         fine_amount      DECIMAL(8,2),
8450         target_resource_type  INT       NOT NULL
8451                                         REFERENCES booking.resource_type(id)
8452                                         ON DELETE CASCADE
8453                                         DEFERRABLE INITIALLY DEFERRED,
8454         target_resource  INT            REFERENCES booking.resource(id)
8455                                         ON DELETE CASCADE
8456                                         DEFERRABLE INITIALLY DEFERRED,
8457         current_resource INT            REFERENCES booking.resource(id)
8458                                         ON DELETE CASCADE
8459                                         DEFERRABLE INITIALLY DEFERRED,
8460         request_lib      INT            NOT NULL
8461                                         REFERENCES actor.org_unit(id)
8462                                         DEFERRABLE INITIALLY DEFERRED,
8463         pickup_lib       INT            REFERENCES actor.org_unit(id)
8464                                         DEFERRABLE INITIALLY DEFERRED,
8465         capture_staff    INT            REFERENCES actor.usr(id)
8466                                         DEFERRABLE INITIALLY DEFERRED,
8467     max_fine         NUMERIC(8,2)
8468 ) INHERITS (money.billable_xact);
8469
8470 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8471
8472 ALTER TABLE booking.reservation
8473         ADD CONSTRAINT booking_reservation_usr_fkey
8474         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8475         DEFERRABLE INITIALLY DEFERRED;
8476
8477 CREATE TABLE booking.reservation_attr_value_map (
8478         id               SERIAL         PRIMARY KEY,
8479         reservation      INT            NOT NULL
8480                                         REFERENCES booking.reservation(id)
8481                                         ON DELETE CASCADE
8482                                         DEFERRABLE INITIALLY DEFERRED,
8483         attr_value       INT            NOT NULL
8484                                         REFERENCES booking.resource_attr_value(id)
8485                                         ON DELETE CASCADE
8486                                         DEFERRABLE INITIALLY DEFERRED,
8487         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8488 );
8489
8490 -- represents a circ chain summary
8491 CREATE TYPE action.circ_chain_summary AS (
8492     num_circs INTEGER,
8493     start_time TIMESTAMP WITH TIME ZONE,
8494     checkout_workstation TEXT,
8495     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8496     last_stop_fines TEXT,
8497     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8498     last_renewal_workstation TEXT, -- NULL if no renewals
8499     last_checkin_workstation TEXT,
8500     last_checkin_time TIMESTAMP WITH TIME ZONE,
8501     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8502 );
8503
8504 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8505 DECLARE
8506     tmp_circ action.circulation%ROWTYPE;
8507     circ_0 action.circulation%ROWTYPE;
8508 BEGIN
8509
8510     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8511
8512     IF tmp_circ IS NULL THEN
8513         RETURN NEXT tmp_circ;
8514     END IF;
8515     circ_0 := tmp_circ;
8516
8517     -- find the front of the chain
8518     WHILE TRUE LOOP
8519         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8520         IF tmp_circ IS NULL THEN
8521             EXIT;
8522         END IF;
8523         circ_0 := tmp_circ;
8524     END LOOP;
8525
8526     -- now send the circs to the caller, oldest to newest
8527     tmp_circ := circ_0;
8528     WHILE TRUE LOOP
8529         IF tmp_circ IS NULL THEN
8530             EXIT;
8531         END IF;
8532         RETURN NEXT tmp_circ;
8533         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8534     END LOOP;
8535
8536 END;
8537 $$ LANGUAGE 'plpgsql';
8538
8539 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8540
8541 DECLARE
8542
8543     -- first circ in the chain
8544     circ_0 action.circulation%ROWTYPE;
8545
8546     -- last circ in the chain
8547     circ_n action.circulation%ROWTYPE;
8548
8549     -- circ chain under construction
8550     chain action.circ_chain_summary;
8551     tmp_circ action.circulation%ROWTYPE;
8552
8553 BEGIN
8554     
8555     chain.num_circs := 0;
8556     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8557
8558         IF chain.num_circs = 0 THEN
8559             circ_0 := tmp_circ;
8560         END IF;
8561
8562         chain.num_circs := chain.num_circs + 1;
8563         circ_n := tmp_circ;
8564     END LOOP;
8565
8566     chain.start_time := circ_0.xact_start;
8567     chain.last_stop_fines := circ_n.stop_fines;
8568     chain.last_stop_fines_time := circ_n.stop_fines_time;
8569     chain.last_checkin_time := circ_n.checkin_time;
8570     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8571     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8572     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8573
8574     IF chain.num_circs > 1 THEN
8575         chain.last_renewal_time := circ_n.xact_start;
8576         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8577     END IF;
8578
8579     RETURN chain;
8580
8581 END;
8582 $$ LANGUAGE 'plpgsql';
8583
8584 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8585 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8586 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8587
8588 ALTER TABLE config.standing_penalty
8589         ADD COLUMN org_depth   INTEGER;
8590
8591 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8592 DECLARE
8593     user_object         actor.usr%ROWTYPE;
8594     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8595     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8596     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8597     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8598     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8599     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8600     tmp_grp             INT;
8601     items_overdue       INT;
8602     items_out           INT;
8603     context_org_list    INT[];
8604     current_fines        NUMERIC(8,2) := 0.0;
8605     tmp_fines            NUMERIC(8,2);
8606     tmp_groc            RECORD;
8607     tmp_circ            RECORD;
8608     tmp_org             actor.org_unit%ROWTYPE;
8609     tmp_penalty         config.standing_penalty%ROWTYPE;
8610     tmp_depth           INTEGER;
8611 BEGIN
8612     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8613
8614     -- Max fines
8615     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8616
8617     -- Fail if the user has a high fine balance
8618     LOOP
8619         tmp_grp := user_object.profile;
8620         LOOP
8621             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8622
8623             IF max_fines.threshold IS NULL THEN
8624                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8625             ELSE
8626                 EXIT;
8627             END IF;
8628
8629             IF tmp_grp IS NULL THEN
8630                 EXIT;
8631             END IF;
8632         END LOOP;
8633
8634         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8635             EXIT;
8636         END IF;
8637
8638         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8639
8640     END LOOP;
8641
8642     IF max_fines.threshold IS NOT NULL THEN
8643
8644         RETURN QUERY
8645             SELECT  *
8646               FROM  actor.usr_standing_penalty
8647               WHERE usr = match_user
8648                     AND org_unit = max_fines.org_unit
8649                     AND (stop_date IS NULL or stop_date > NOW())
8650                     AND standing_penalty = 1;
8651
8652         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8653
8654         SELECT  SUM(f.balance_owed) INTO current_fines
8655           FROM  money.materialized_billable_xact_summary f
8656                 JOIN (
8657                     SELECT  r.id
8658                       FROM  booking.reservation r
8659                       WHERE r.usr = match_user
8660                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8661                             AND xact_finish IS NULL
8662                                 UNION ALL
8663                     SELECT  g.id
8664                       FROM  money.grocery g
8665                       WHERE g.usr = match_user
8666                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8667                             AND xact_finish IS NULL
8668                                 UNION ALL
8669                     SELECT  circ.id
8670                       FROM  action.circulation circ
8671                       WHERE circ.usr = match_user
8672                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8673                             AND xact_finish IS NULL ) l USING (id);
8674
8675         IF current_fines >= max_fines.threshold THEN
8676             new_sp_row.usr := match_user;
8677             new_sp_row.org_unit := max_fines.org_unit;
8678             new_sp_row.standing_penalty := 1;
8679             RETURN NEXT new_sp_row;
8680         END IF;
8681     END IF;
8682
8683     -- Start over for max overdue
8684     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8685
8686     -- Fail if the user has too many overdue items
8687     LOOP
8688         tmp_grp := user_object.profile;
8689         LOOP
8690
8691             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8692
8693             IF max_overdue.threshold IS NULL THEN
8694                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8695             ELSE
8696                 EXIT;
8697             END IF;
8698
8699             IF tmp_grp IS NULL THEN
8700                 EXIT;
8701             END IF;
8702         END LOOP;
8703
8704         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8705             EXIT;
8706         END IF;
8707
8708         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8709
8710     END LOOP;
8711
8712     IF max_overdue.threshold IS NOT NULL THEN
8713
8714         RETURN QUERY
8715             SELECT  *
8716               FROM  actor.usr_standing_penalty
8717               WHERE usr = match_user
8718                     AND org_unit = max_overdue.org_unit
8719                     AND (stop_date IS NULL or stop_date > NOW())
8720                     AND standing_penalty = 2;
8721
8722         SELECT  INTO items_overdue COUNT(*)
8723           FROM  action.circulation circ
8724                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8725           WHERE circ.usr = match_user
8726             AND circ.checkin_time IS NULL
8727             AND circ.due_date < NOW()
8728             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8729
8730         IF items_overdue >= max_overdue.threshold::INT THEN
8731             new_sp_row.usr := match_user;
8732             new_sp_row.org_unit := max_overdue.org_unit;
8733             new_sp_row.standing_penalty := 2;
8734             RETURN NEXT new_sp_row;
8735         END IF;
8736     END IF;
8737
8738     -- Start over for max out
8739     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8740
8741     -- Fail if the user has too many checked out items
8742     LOOP
8743         tmp_grp := user_object.profile;
8744         LOOP
8745             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8746
8747             IF max_items_out.threshold IS NULL THEN
8748                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8749             ELSE
8750                 EXIT;
8751             END IF;
8752
8753             IF tmp_grp IS NULL THEN
8754                 EXIT;
8755             END IF;
8756         END LOOP;
8757
8758         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8759             EXIT;
8760         END IF;
8761
8762         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8763
8764     END LOOP;
8765
8766
8767     -- Fail if the user has too many items checked out
8768     IF max_items_out.threshold IS NOT NULL THEN
8769
8770         RETURN QUERY
8771             SELECT  *
8772               FROM  actor.usr_standing_penalty
8773               WHERE usr = match_user
8774                     AND org_unit = max_items_out.org_unit
8775                     AND (stop_date IS NULL or stop_date > NOW())
8776                     AND standing_penalty = 3;
8777
8778         SELECT  INTO items_out COUNT(*)
8779           FROM  action.circulation circ
8780                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8781           WHERE circ.usr = match_user
8782                 AND circ.checkin_time IS NULL
8783                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8784
8785            IF items_out >= max_items_out.threshold::INT THEN
8786             new_sp_row.usr := match_user;
8787             new_sp_row.org_unit := max_items_out.org_unit;
8788             new_sp_row.standing_penalty := 3;
8789             RETURN NEXT new_sp_row;
8790            END IF;
8791     END IF;
8792
8793     -- Start over for collections warning
8794     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8795
8796     -- Fail if the user has a collections-level fine balance
8797     LOOP
8798         tmp_grp := user_object.profile;
8799         LOOP
8800             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8801
8802             IF max_fines.threshold IS NULL THEN
8803                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8804             ELSE
8805                 EXIT;
8806             END IF;
8807
8808             IF tmp_grp IS NULL THEN
8809                 EXIT;
8810             END IF;
8811         END LOOP;
8812
8813         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8814             EXIT;
8815         END IF;
8816
8817         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8818
8819     END LOOP;
8820
8821     IF max_fines.threshold IS NOT NULL THEN
8822
8823         RETURN QUERY
8824             SELECT  *
8825               FROM  actor.usr_standing_penalty
8826               WHERE usr = match_user
8827                     AND org_unit = max_fines.org_unit
8828                     AND (stop_date IS NULL or stop_date > NOW())
8829                     AND standing_penalty = 4;
8830
8831         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8832
8833         SELECT  SUM(f.balance_owed) INTO current_fines
8834           FROM  money.materialized_billable_xact_summary f
8835                 JOIN (
8836                     SELECT  r.id
8837                       FROM  booking.reservation r
8838                       WHERE r.usr = match_user
8839                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8840                             AND r.xact_finish IS NULL
8841                                 UNION ALL
8842                     SELECT  g.id
8843                       FROM  money.grocery g
8844                       WHERE g.usr = match_user
8845                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8846                             AND g.xact_finish IS NULL
8847                                 UNION ALL
8848                     SELECT  circ.id
8849                       FROM  action.circulation circ
8850                       WHERE circ.usr = match_user
8851                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8852                             AND circ.xact_finish IS NULL ) l USING (id);
8853
8854         IF current_fines >= max_fines.threshold THEN
8855             new_sp_row.usr := match_user;
8856             new_sp_row.org_unit := max_fines.org_unit;
8857             new_sp_row.standing_penalty := 4;
8858             RETURN NEXT new_sp_row;
8859         END IF;
8860     END IF;
8861
8862     -- Start over for in collections
8863     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8864
8865     -- Remove the in-collections penalty if the user has paid down enough
8866     -- This penalty is different, because this code is not responsible for creating 
8867     -- new in-collections penalties, only for removing them
8868     LOOP
8869         tmp_grp := user_object.profile;
8870         LOOP
8871             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8872
8873             IF max_fines.threshold IS NULL THEN
8874                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8875             ELSE
8876                 EXIT;
8877             END IF;
8878
8879             IF tmp_grp IS NULL THEN
8880                 EXIT;
8881             END IF;
8882         END LOOP;
8883
8884         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8885             EXIT;
8886         END IF;
8887
8888         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8889
8890     END LOOP;
8891
8892     IF max_fines.threshold IS NOT NULL THEN
8893
8894         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8895
8896         -- first, see if the user had paid down to the threshold
8897         SELECT  SUM(f.balance_owed) INTO current_fines
8898           FROM  money.materialized_billable_xact_summary f
8899                 JOIN (
8900                     SELECT  r.id
8901                       FROM  booking.reservation r
8902                       WHERE r.usr = match_user
8903                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8904                             AND r.xact_finish IS NULL
8905                                 UNION ALL
8906                     SELECT  g.id
8907                       FROM  money.grocery g
8908                       WHERE g.usr = match_user
8909                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8910                             AND g.xact_finish IS NULL
8911                                 UNION ALL
8912                     SELECT  circ.id
8913                       FROM  action.circulation circ
8914                       WHERE circ.usr = match_user
8915                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8916                             AND circ.xact_finish IS NULL ) l USING (id);
8917
8918         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8919             -- patron has paid down enough
8920
8921             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8922
8923             IF tmp_penalty.org_depth IS NOT NULL THEN
8924
8925                 -- since this code is not responsible for applying the penalty, it can't 
8926                 -- guarantee the current context org will match the org at which the penalty 
8927                 --- was applied.  search up the org tree until we hit the configured penalty depth
8928                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8929                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8930
8931                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8932
8933                     RETURN QUERY
8934                         SELECT  *
8935                           FROM  actor.usr_standing_penalty
8936                           WHERE usr = match_user
8937                                 AND org_unit = tmp_org.id
8938                                 AND (stop_date IS NULL or stop_date > NOW())
8939                                 AND standing_penalty = 30;
8940
8941                     IF tmp_org.parent_ou IS NULL THEN
8942                         EXIT;
8943                     END IF;
8944
8945                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8946                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8947                 END LOOP;
8948
8949             ELSE
8950
8951                 -- no penalty depth is defined, look for exact matches
8952
8953                 RETURN QUERY
8954                     SELECT  *
8955                       FROM  actor.usr_standing_penalty
8956                       WHERE usr = match_user
8957                             AND org_unit = max_fines.org_unit
8958                             AND (stop_date IS NULL or stop_date > NOW())
8959                             AND standing_penalty = 30;
8960             END IF;
8961     
8962         END IF;
8963
8964     END IF;
8965
8966     RETURN;
8967 END;
8968 $func$ LANGUAGE plpgsql;
8969
8970 -- Create a default row in acq.fiscal_calendar
8971 -- Add a column in actor.org_unit to point to it
8972
8973 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8974
8975 ALTER TABLE actor.org_unit
8976 ADD COLUMN fiscal_calendar INT NOT NULL
8977         REFERENCES acq.fiscal_calendar( id )
8978         DEFERRABLE INITIALLY DEFERRED
8979         DEFAULT 1;
8980
8981 ALTER TABLE auditor.actor_org_unit_history
8982         ADD COLUMN fiscal_calendar INT;
8983
8984 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8985
8986 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8987
8988 ALTER TABLE acq.funding_source_credit
8989 ADD COLUMN deadline_date TIMESTAMPTZ;
8990
8991 ALTER TABLE acq.funding_source_credit
8992 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8993
8994 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8995
8996 CREATE TABLE acq.fund_transfer (
8997         id               SERIAL         PRIMARY KEY,
8998         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8999                                         DEFERRABLE INITIALLY DEFERRED,
9000         src_amount       NUMERIC        NOT NULL,
9001         dest_fund        INT            REFERENCES acq.fund( id )
9002                                         DEFERRABLE INITIALLY DEFERRED,
9003         dest_amount      NUMERIC,
9004         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
9005         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
9006                                         DEFERRABLE INITIALLY DEFERRED,
9007         note             TEXT,
9008     funding_source_credit INTEGER   NOT NULL
9009                                         REFERENCES acq.funding_source_credit(id)
9010                                         DEFERRABLE INITIALLY DEFERRED
9011 );
9012
9013 CREATE INDEX acqftr_usr_idx
9014 ON acq.fund_transfer( transfer_user );
9015
9016 COMMENT ON TABLE acq.fund_transfer IS $$
9017 /*
9018  * Copyright (C) 2009  Georgia Public Library Service
9019  * Scott McKellar <scott@esilibrary.com>
9020  *
9021  * Fund Transfer
9022  *
9023  * Each row represents the transfer of money from a source fund
9024  * to a destination fund.  There should be corresponding entries
9025  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
9026  * to record how much money moved from which fund to which other
9027  * fund.
9028  * 
9029  * The presence of two amount fields, rather than one, reflects
9030  * the possibility that the two funds are denominated in different
9031  * currencies.  If they use the same currency type, the two
9032  * amounts should be the same.
9033  *
9034  * ****
9035  *
9036  * This program is free software; you can redistribute it and/or
9037  * modify it under the terms of the GNU General Public License
9038  * as published by the Free Software Foundation; either version 2
9039  * of the License, or (at your option) any later version.
9040  *
9041  * This program is distributed in the hope that it will be useful,
9042  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9043  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9044  * GNU General Public License for more details.
9045  */
9046 $$;
9047
9048 CREATE TABLE acq.claim_event_type (
9049         id             SERIAL           PRIMARY KEY,
9050         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
9051                                                  DEFERRABLE INITIALLY DEFERRED,
9052         code           TEXT             NOT NULL,
9053         description    TEXT             NOT NULL,
9054         library_initiated BOOL          NOT NULL DEFAULT FALSE,
9055         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
9056 );
9057
9058 CREATE TABLE acq.claim_event (
9059         id             BIGSERIAL        PRIMARY KEY,
9060         type           INT              NOT NULL REFERENCES acq.claim_event_type
9061                                                  DEFERRABLE INITIALLY DEFERRED,
9062         claim          SERIAL           NOT NULL REFERENCES acq.claim
9063                                                  DEFERRABLE INITIALLY DEFERRED,
9064         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
9065         creator        INT              NOT NULL REFERENCES actor.usr
9066                                                  DEFERRABLE INITIALLY DEFERRED,
9067         note           TEXT
9068 );
9069
9070 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
9071
9072 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
9073         src_usr  IN INTEGER,
9074         dest_usr IN INTEGER
9075 ) RETURNS VOID AS $$
9076 DECLARE
9077         suffix TEXT;
9078         renamable_row RECORD;
9079 BEGIN
9080
9081         UPDATE actor.usr SET
9082                 active = FALSE,
9083                 card = NULL,
9084                 mailing_address = NULL,
9085                 billing_address = NULL
9086         WHERE id = src_usr;
9087
9088         -- acq.*
9089         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9090         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9091         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9092         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9093         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9094         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9095         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9096
9097         -- Update with a rename to avoid collisions
9098         FOR renamable_row in
9099                 SELECT id, name
9100                 FROM   acq.picklist
9101                 WHERE  owner = src_usr
9102         LOOP
9103                 suffix := ' (' || src_usr || ')';
9104                 LOOP
9105                         BEGIN
9106                                 UPDATE  acq.picklist
9107                                 SET     owner = dest_usr, name = name || suffix
9108                                 WHERE   id = renamable_row.id;
9109                         EXCEPTION WHEN unique_violation THEN
9110                                 suffix := suffix || ' ';
9111                                 CONTINUE;
9112                         END;
9113                         EXIT;
9114                 END LOOP;
9115         END LOOP;
9116
9117         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9118         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9119         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9120         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9121         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9122         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9123         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9124         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9125
9126         -- action.*
9127         DELETE FROM action.circulation WHERE usr = src_usr;
9128         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9129         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9130         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9131         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9132         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9133         DELETE FROM action.hold_request WHERE usr = src_usr;
9134         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9135         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9136         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9137         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9138         DELETE FROM action.survey_response WHERE usr = src_usr;
9139         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9140
9141         -- actor.*
9142         DELETE FROM actor.card WHERE usr = src_usr;
9143         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9144
9145         -- The following update is intended to avoid transient violations of a foreign
9146         -- key constraint, whereby actor.usr_address references itself.  It may not be
9147         -- necessary, but it does no harm.
9148         UPDATE actor.usr_address SET replaces = NULL
9149                 WHERE usr = src_usr AND replaces IS NOT NULL;
9150         DELETE FROM actor.usr_address WHERE usr = src_usr;
9151         DELETE FROM actor.usr_note WHERE usr = src_usr;
9152         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9153         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9154         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9155         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9156         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9157         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9158
9159         -- asset.*
9160         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9161         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9162         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9163         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9164         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9165         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9166
9167         -- auditor.*
9168         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9169         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9170         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9171         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9172         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9173         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9174         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9175         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9176
9177         -- biblio.*
9178         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9179         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9180         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9181         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9182
9183         -- container.*
9184         -- Update buckets with a rename to avoid collisions
9185         FOR renamable_row in
9186                 SELECT id, name
9187                 FROM   container.biblio_record_entry_bucket
9188                 WHERE  owner = src_usr
9189         LOOP
9190                 suffix := ' (' || src_usr || ')';
9191                 LOOP
9192                         BEGIN
9193                                 UPDATE  container.biblio_record_entry_bucket
9194                                 SET     owner = dest_usr, name = name || suffix
9195                                 WHERE   id = renamable_row.id;
9196                         EXCEPTION WHEN unique_violation THEN
9197                                 suffix := suffix || ' ';
9198                                 CONTINUE;
9199                         END;
9200                         EXIT;
9201                 END LOOP;
9202         END LOOP;
9203
9204         FOR renamable_row in
9205                 SELECT id, name
9206                 FROM   container.call_number_bucket
9207                 WHERE  owner = src_usr
9208         LOOP
9209                 suffix := ' (' || src_usr || ')';
9210                 LOOP
9211                         BEGIN
9212                                 UPDATE  container.call_number_bucket
9213                                 SET     owner = dest_usr, name = name || suffix
9214                                 WHERE   id = renamable_row.id;
9215                         EXCEPTION WHEN unique_violation THEN
9216                                 suffix := suffix || ' ';
9217                                 CONTINUE;
9218                         END;
9219                         EXIT;
9220                 END LOOP;
9221         END LOOP;
9222
9223         FOR renamable_row in
9224                 SELECT id, name
9225                 FROM   container.copy_bucket
9226                 WHERE  owner = src_usr
9227         LOOP
9228                 suffix := ' (' || src_usr || ')';
9229                 LOOP
9230                         BEGIN
9231                                 UPDATE  container.copy_bucket
9232                                 SET     owner = dest_usr, name = name || suffix
9233                                 WHERE   id = renamable_row.id;
9234                         EXCEPTION WHEN unique_violation THEN
9235                                 suffix := suffix || ' ';
9236                                 CONTINUE;
9237                         END;
9238                         EXIT;
9239                 END LOOP;
9240         END LOOP;
9241
9242         FOR renamable_row in
9243                 SELECT id, name
9244                 FROM   container.user_bucket
9245                 WHERE  owner = src_usr
9246         LOOP
9247                 suffix := ' (' || src_usr || ')';
9248                 LOOP
9249                         BEGIN
9250                                 UPDATE  container.user_bucket
9251                                 SET     owner = dest_usr, name = name || suffix
9252                                 WHERE   id = renamable_row.id;
9253                         EXCEPTION WHEN unique_violation THEN
9254                                 suffix := suffix || ' ';
9255                                 CONTINUE;
9256                         END;
9257                         EXIT;
9258                 END LOOP;
9259         END LOOP;
9260
9261         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9262
9263         -- money.*
9264         DELETE FROM money.billable_xact WHERE usr = src_usr;
9265         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9266         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9267
9268         -- permission.*
9269         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9270         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9271         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9272         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9273
9274         -- reporter.*
9275         -- Update with a rename to avoid collisions
9276         BEGIN
9277                 FOR renamable_row in
9278                         SELECT id, name
9279                         FROM   reporter.output_folder
9280                         WHERE  owner = src_usr
9281                 LOOP
9282                         suffix := ' (' || src_usr || ')';
9283                         LOOP
9284                                 BEGIN
9285                                         UPDATE  reporter.output_folder
9286                                         SET     owner = dest_usr, name = name || suffix
9287                                         WHERE   id = renamable_row.id;
9288                                 EXCEPTION WHEN unique_violation THEN
9289                                         suffix := suffix || ' ';
9290                                         CONTINUE;
9291                                 END;
9292                                 EXIT;
9293                         END LOOP;
9294                 END LOOP;
9295         EXCEPTION WHEN undefined_table THEN
9296                 -- do nothing
9297         END;
9298
9299         BEGIN
9300                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9301         EXCEPTION WHEN undefined_table THEN
9302                 -- do nothing
9303         END;
9304
9305         -- Update with a rename to avoid collisions
9306         BEGIN
9307                 FOR renamable_row in
9308                         SELECT id, name
9309                         FROM   reporter.report_folder
9310                         WHERE  owner = src_usr
9311                 LOOP
9312                         suffix := ' (' || src_usr || ')';
9313                         LOOP
9314                                 BEGIN
9315                                         UPDATE  reporter.report_folder
9316                                         SET     owner = dest_usr, name = name || suffix
9317                                         WHERE   id = renamable_row.id;
9318                                 EXCEPTION WHEN unique_violation THEN
9319                                         suffix := suffix || ' ';
9320                                         CONTINUE;
9321                                 END;
9322                                 EXIT;
9323                         END LOOP;
9324                 END LOOP;
9325         EXCEPTION WHEN undefined_table THEN
9326                 -- do nothing
9327         END;
9328
9329         BEGIN
9330                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9331         EXCEPTION WHEN undefined_table THEN
9332                 -- do nothing
9333         END;
9334
9335         BEGIN
9336                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9337         EXCEPTION WHEN undefined_table THEN
9338                 -- do nothing
9339         END;
9340
9341         -- Update with a rename to avoid collisions
9342         BEGIN
9343                 FOR renamable_row in
9344                         SELECT id, name
9345                         FROM   reporter.template_folder
9346                         WHERE  owner = src_usr
9347                 LOOP
9348                         suffix := ' (' || src_usr || ')';
9349                         LOOP
9350                                 BEGIN
9351                                         UPDATE  reporter.template_folder
9352                                         SET     owner = dest_usr, name = name || suffix
9353                                         WHERE   id = renamable_row.id;
9354                                 EXCEPTION WHEN unique_violation THEN
9355                                         suffix := suffix || ' ';
9356                                         CONTINUE;
9357                                 END;
9358                                 EXIT;
9359                         END LOOP;
9360                 END LOOP;
9361         EXCEPTION WHEN undefined_table THEN
9362         -- do nothing
9363         END;
9364
9365         -- vandelay.*
9366         -- Update with a rename to avoid collisions
9367         FOR renamable_row in
9368                 SELECT id, name
9369                 FROM   vandelay.queue
9370                 WHERE  owner = src_usr
9371         LOOP
9372                 suffix := ' (' || src_usr || ')';
9373                 LOOP
9374                         BEGIN
9375                                 UPDATE  vandelay.queue
9376                                 SET     owner = dest_usr, name = name || suffix
9377                                 WHERE   id = renamable_row.id;
9378                         EXCEPTION WHEN unique_violation THEN
9379                                 suffix := suffix || ' ';
9380                                 CONTINUE;
9381                         END;
9382                         EXIT;
9383                 END LOOP;
9384         END LOOP;
9385
9386 END;
9387 $$ LANGUAGE plpgsql;
9388
9389 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9390 /**
9391  * Finds rows dependent on a given row in actor.usr and either deletes them
9392  * or reassigns them to a different user.
9393  */
9394 $$;
9395
9396 CREATE OR REPLACE FUNCTION actor.usr_delete(
9397         src_usr  IN INTEGER,
9398         dest_usr IN INTEGER
9399 ) RETURNS VOID AS $$
9400 DECLARE
9401         old_profile actor.usr.profile%type;
9402         old_home_ou actor.usr.home_ou%type;
9403         new_profile actor.usr.profile%type;
9404         new_home_ou actor.usr.home_ou%type;
9405         new_name    text;
9406         new_dob     actor.usr.dob%type;
9407 BEGIN
9408         SELECT
9409                 id || '-PURGED-' || now(),
9410                 profile,
9411                 home_ou,
9412                 dob
9413         INTO
9414                 new_name,
9415                 old_profile,
9416                 old_home_ou,
9417                 new_dob
9418         FROM
9419                 actor.usr
9420         WHERE
9421                 id = src_usr;
9422         --
9423         -- Quit if no such user
9424         --
9425         IF old_profile IS NULL THEN
9426                 RETURN;
9427         END IF;
9428         --
9429         perform actor.usr_purge_data( src_usr, dest_usr );
9430         --
9431         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9432         -- could assume that there is only one root.  Theoretically, someday, maybe,
9433         -- there could be multiple roots, so we take extra trouble to get the right ones.
9434         --
9435         SELECT
9436                 id
9437         INTO
9438                 new_profile
9439         FROM
9440                 permission.grp_ancestors( old_profile )
9441         WHERE
9442                 parent is null;
9443         --
9444         SELECT
9445                 id
9446         INTO
9447                 new_home_ou
9448         FROM
9449                 actor.org_unit_ancestors( old_home_ou )
9450         WHERE
9451                 parent_ou is null;
9452         --
9453         -- Truncate date of birth
9454         --
9455         IF new_dob IS NOT NULL THEN
9456                 new_dob := date_trunc( 'year', new_dob );
9457         END IF;
9458         --
9459         UPDATE
9460                 actor.usr
9461                 SET
9462                         card = NULL,
9463                         profile = new_profile,
9464                         usrname = new_name,
9465                         email = NULL,
9466                         passwd = random()::text,
9467                         standing = DEFAULT,
9468                         ident_type = 
9469                         (
9470                                 SELECT MIN( id )
9471                                 FROM config.identification_type
9472                         ),
9473                         ident_value = NULL,
9474                         ident_type2 = NULL,
9475                         ident_value2 = NULL,
9476                         net_access_level = DEFAULT,
9477                         photo_url = NULL,
9478                         prefix = NULL,
9479                         first_given_name = new_name,
9480                         second_given_name = NULL,
9481                         family_name = new_name,
9482                         suffix = NULL,
9483                         alias = NULL,
9484                         day_phone = NULL,
9485                         evening_phone = NULL,
9486                         other_phone = NULL,
9487                         mailing_address = NULL,
9488                         billing_address = NULL,
9489                         home_ou = new_home_ou,
9490                         dob = new_dob,
9491                         active = FALSE,
9492                         master_account = DEFAULT, 
9493                         super_user = DEFAULT,
9494                         barred = FALSE,
9495                         deleted = TRUE,
9496                         juvenile = DEFAULT,
9497                         usrgroup = 0,
9498                         claims_returned_count = DEFAULT,
9499                         credit_forward_balance = DEFAULT,
9500                         last_xact_id = DEFAULT,
9501                         alert_message = NULL,
9502                         create_date = now(),
9503                         expire_date = now()
9504         WHERE
9505                 id = src_usr;
9506 END;
9507 $$ LANGUAGE plpgsql;
9508
9509 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9510 /**
9511  * Logically deletes a user.  Removes personally identifiable information,
9512  * and purges associated data in other tables.
9513  */
9514 $$;
9515
9516 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9517
9518 ALTER TABLE acq.fund
9519 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9520
9521 ALTER TABLE acq.fund
9522         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9523
9524 -- A fund can't roll over if it doesn't propagate from one year to the next
9525
9526 ALTER TABLE acq.fund
9527         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9528         ( propagate OR NOT rollover );
9529
9530 ALTER TABLE acq.fund
9531         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9532
9533 ALTER TABLE acq.fund
9534     ADD COLUMN balance_warning_percent INT;
9535
9536 ALTER TABLE acq.fund
9537     ADD COLUMN balance_stop_percent INT;
9538
9539 CREATE VIEW acq.ordered_funding_source_credit AS
9540         SELECT
9541                 CASE WHEN deadline_date IS NULL THEN
9542                         2
9543                 ELSE
9544                         1
9545                 END AS sort_priority,
9546                 CASE WHEN deadline_date IS NULL THEN
9547                         effective_date
9548                 ELSE
9549                         deadline_date
9550                 END AS sort_date,
9551                 id,
9552                 funding_source,
9553                 amount,
9554                 note
9555         FROM
9556                 acq.funding_source_credit;
9557
9558 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9559 /*
9560  * Copyright (C) 2009  Georgia Public Library Service
9561  * Scott McKellar <scott@gmail.com>
9562  *
9563  * The acq.ordered_funding_source_credit view is a prioritized
9564  * ordering of funding source credits.  When ordered by the first
9565  * three columns, this view defines the order in which the various
9566  * credits are to be tapped for spending, subject to the allocations
9567  * in the acq.fund_allocation table.
9568  *
9569  * The first column reflects the principle that we should spend
9570  * money with deadlines before spending money without deadlines.
9571  *
9572  * The second column reflects the principle that we should spend the
9573  * oldest money first.  For money with deadlines, that means that we
9574  * spend first from the credit with the earliest deadline.  For
9575  * money without deadlines, we spend first from the credit with the
9576  * earliest effective date.  
9577  *
9578  * The third column is a tie breaker to ensure a consistent
9579  * ordering.
9580  *
9581  * ****
9582  *
9583  * This program is free software; you can redistribute it and/or
9584  * modify it under the terms of the GNU General Public License
9585  * as published by the Free Software Foundation; either version 2
9586  * of the License, or (at your option) any later version.
9587  *
9588  * This program is distributed in the hope that it will be useful,
9589  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9590  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9591  * GNU General Public License for more details.
9592  */
9593 $$;
9594
9595 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9596     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9597       FROM  money.materialized_billable_xact_summary m
9598             LEFT JOIN action.circulation c ON (c.id = m.id)
9599             LEFT JOIN money.grocery g ON (g.id = m.id)
9600             LEFT JOIN booking.reservation r ON (r.id = m.id);
9601
9602 CREATE TABLE config.marc21_rec_type_map (
9603     code        TEXT    PRIMARY KEY,
9604     type_val    TEXT    NOT NULL,
9605     blvl_val    TEXT    NOT NULL
9606 );
9607
9608 CREATE TABLE config.marc21_ff_pos_map (
9609     id          SERIAL  PRIMARY KEY,
9610     fixed_field TEXT    NOT NULL,
9611     tag         TEXT    NOT NULL,
9612     rec_type    TEXT    NOT NULL,
9613     start_pos   INT     NOT NULL,
9614     length      INT     NOT NULL,
9615     default_val TEXT    NOT NULL DEFAULT ' '
9616 );
9617
9618 CREATE TABLE config.marc21_physical_characteristic_type_map (
9619     ptype_key   TEXT    PRIMARY KEY,
9620     label       TEXT    NOT NULL -- I18N
9621 );
9622
9623 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9624     id          SERIAL  PRIMARY KEY,
9625     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9626     subfield    TEXT    NOT NULL,
9627     start_pos   INT     NOT NULL,
9628     length      INT     NOT NULL,
9629     label       TEXT    NOT NULL -- I18N
9630 );
9631
9632 CREATE TABLE config.marc21_physical_characteristic_value_map (
9633     id              SERIAL  PRIMARY KEY,
9634     value           TEXT    NOT NULL,
9635     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9636     label           TEXT    NOT NULL -- I18N
9637 );
9638
9639 ----------------------------------
9640 -- MARC21 record structure data --
9641 ----------------------------------
9642
9643 -- Record type map
9644 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9645 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9646 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9647 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9648 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9649 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9650 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9651 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9652
9653 ------ Physical Characteristics
9654
9655 -- Map
9656 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9657 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9658 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9660 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9663 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');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9668 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9669 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');
9670 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9671 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9672 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9674 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9676 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9678 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9680 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');
9681 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');
9682 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');
9683 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');
9684 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9685 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');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9687 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9688 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9689 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');
9690 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9691 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9692 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9693 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');
9694 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9695 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');
9696 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9697 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9698 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9699 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9700 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9701 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9702 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9703 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');
9704
9705 -- Electronic Resource
9706 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9707 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9708 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');
9709 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');
9710 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');
9711 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');
9712 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');
9713 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');
9714 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');
9715 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');
9716 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9717 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
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 ('c','d','3','1','Color');
9720 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');
9721 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');
9722 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9723 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');
9724 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9725 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');
9726 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9727 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9728 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9729 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.');
9730 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.');
9731 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.');
9732 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.');
9733 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.');
9734 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');
9735 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.');
9736 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9737 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.');
9738 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9739 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','f','5','1','Sound');
9740 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)');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
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_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9744 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9745 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9746 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');
9747 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9748 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');
9749 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');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9751 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9753 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');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9756 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9757 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');
9758 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');
9759 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');
9760 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)');
9761 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9762 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');
9763 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9764 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9765 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9766 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9767 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9768 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9769 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9770 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9771 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9772 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');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9775 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9776
9777 -- Globe
9778 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9779 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9780 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');
9781 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');
9782 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');
9783 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');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9786 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9787 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');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9789 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9791 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
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 ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9796 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9797 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9798 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9799 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9800 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9801 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9802 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');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9804 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9805
9806 -- Tactile Material
9807 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9808 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9809 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9810 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9811 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9812 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');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9814 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9815 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9816 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');
9817 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');
9818 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');
9819 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');
9820 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');
9821 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');
9822 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');
9823 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9824 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9825 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9826 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9827 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9828 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9829 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');
9830 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9831 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9832 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9833 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');
9834 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');
9835 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');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9837 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');
9838 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');
9839 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');
9840 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');
9841 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');
9842 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');
9843 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9844 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');
9845 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');
9846 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9847 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9848 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9849 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');
9850 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');
9851 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');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9853 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9854
9855 -- Projected Graphic
9856 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9857 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9858 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');
9859 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9860 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');
9861 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');
9862 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
9864 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9865 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9866 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');
9867 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9868 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');
9869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9870 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');
9871 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9872 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9873 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9874 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9875 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9876 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');
9877 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');
9878 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');
9879 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9880 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9881 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9882 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');
9883 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');
9884 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');
9885 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9886 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9887 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');
9888 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');
9889 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');
9890 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');
9891 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');
9892 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');
9893 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');
9894 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9895 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
9896 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9897 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9898 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9899 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.');
9900 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.');
9901 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.');
9902 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.');
9903 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.');
9904 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.');
9905 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.');
9906 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.)');
9907 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.)');
9908 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.)');
9909 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.)');
9910 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9911 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.)');
9912 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.)');
9913 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.)');
9914 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.)');
9915 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9916 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9917 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9918 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9919 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9920 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9921 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');
9922 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');
9923 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');
9924 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9926
9927 -- Microform
9928 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9929 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9930 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');
9931 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');
9932 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');
9933 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');
9934 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9935 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');
9936 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9938 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9939 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9940 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9941 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9942 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9943 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9944 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9945 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.');
9946 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.');
9947 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.');
9948 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9949 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.');
9950 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.)');
9951 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.)');
9952 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.)');
9953 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.)');
9954 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9956 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');
9957 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)');
9958 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)');
9959 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)');
9960 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)');
9961 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-)');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9963 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');
9964 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9965 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');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
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');
9968 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9969 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9970 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9971 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');
9972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9973 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9974 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9975 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');
9976 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9977 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9978 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9979 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');
9980 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');
9981 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');
9982 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');
9983 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9984 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9985 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');
9986 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');
9987 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');
9988 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');
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 base');
9990 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');
9991 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');
9992 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');
9993 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');
9994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9996
9997 -- Non-projected Graphic
9998 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9999 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
10000 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
10001 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
10002 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
10003 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');
10004 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
10005 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
10006 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
10008 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');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
10010 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');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10012 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10013 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
10014 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');
10015 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');
10016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10017 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');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10019 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10021 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10023 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');
10024 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');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10028 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10030 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');
10031 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10035 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10037 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10038 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10039 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
10040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10041 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');
10042 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');
10043 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10046 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10047 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10048 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');
10049 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10050 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10051 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10052 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
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
10058 -- Motion Picture
10059 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
10060 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
10061 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');
10062 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');
10063 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');
10064 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10066 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
10067 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');
10068 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10069 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');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10071 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10072 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10073 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
10074 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');
10075 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)');
10076 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
10077 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)');
10078 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');
10079 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');
10080 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10081 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10082 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');
10083 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');
10084 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');
10085 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10086 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10087 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');
10088 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');
10089 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');
10090 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');
10091 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');
10092 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');
10093 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');
10094 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10097 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10098 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
10099 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.');
10100 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.');
10101 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.');
10102 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.');
10103 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.');
10104 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.');
10105 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.');
10106 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10107 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10108 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10110 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10111 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');
10112 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');
10113 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10115 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10116 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10117 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');
10118 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10120 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10121 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');
10122 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');
10123 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');
10124 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');
10125 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10126
10127 -- Remote-sensing Image
10128 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10129 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10130 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10131 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10133 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10134 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
10135 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');
10136 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10137 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10138 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10139 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');
10140 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');
10141 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10142 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');
10143 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10144 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10145 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%');
10146 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%');
10147 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%');
10148 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%');
10149 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%');
10150 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%');
10151 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%');
10152 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%');
10153 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%');
10154 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%');
10155 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');
10156 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10157 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10159 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');
10160 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');
10161 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');
10162 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');
10163 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');
10164 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');
10165 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');
10166 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');
10167 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');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10170 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10172 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');
10173 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');
10174 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');
10175 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');
10176 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10177 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10178 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10179 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10180 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10181 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10182 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10183 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10184 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');
10185 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');
10186 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');
10187 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');
10188 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');
10189 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)');
10190 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');
10191 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10192 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');
10193 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)');
10194 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)');
10195 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)');
10196 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');
10197 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');
10198 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');
10199 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');
10200 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');
10201 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');
10202 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');
10203 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');
10204 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');
10205 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');
10206 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');
10207 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');
10208 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');
10209 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');
10210 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');
10211 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');
10212 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');
10213 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');
10214 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');
10215 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');
10216 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');
10217 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)');
10218 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');
10219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10220 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10221 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');
10222 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');
10223 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10224 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10225
10226 -- Sound Recording
10227 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10228 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10229 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');
10230 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10231 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');
10232 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');
10233 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10234 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');
10235 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');
10236 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10237 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');
10238 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10239 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10240 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');
10241 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');
10242 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');
10243 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');
10244 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');
10245 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');
10246 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');
10247 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');
10248 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');
10249 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');
10250 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');
10251 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');
10252 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');
10253 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');
10254 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10255 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10256 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10257 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10258 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10259 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10260 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10261 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10262 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10263 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');
10264 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');
10265 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');
10266 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10267 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10268 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10269 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.');
10270 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.');
10271 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.');
10272 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.');
10273 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.');
10274 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.');
10275 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.)');
10276 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.');
10277 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');
10278 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.');
10279 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.');
10280 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10281 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10282 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10283 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.');
10284 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.');
10285 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');
10286 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.');
10287 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.');
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 ('s','i','8','1','Tape configuration ');
10291 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');
10292 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');
10293 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');
10294 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');
10295 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');
10296 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');
10297 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');
10298 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10300 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10301 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');
10302 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');
10303 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');
10304 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');
10305 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');
10306 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');
10307 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');
10308 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');
10309 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');
10310 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10311 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10312 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10313 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');
10314 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');
10315 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');
10316 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');
10317 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10318 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10319
10320 -- Videorecording
10321 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10322 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10323 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10324 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10325 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10327 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10328 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10329 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10330 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');
10331 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10332 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10333 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');
10334 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10335 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10336 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10337 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10338 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10339 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');
10340 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10341 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');
10342 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10343 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10344 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10345 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10346 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');
10347 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');
10348 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');
10349 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');
10350 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.');
10351 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.');
10352 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10353 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10354 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10355 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');
10356 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');
10357 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');
10358 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10359 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10360 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');
10361 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');
10362 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');
10363 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');
10364 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');
10365 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');
10366 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');
10367 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10368 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10369 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10370 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10371 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10372 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.');
10373 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.');
10374 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.');
10375 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.');
10376 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.');
10377 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.');
10378 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10379 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10380 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10381 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10382 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10383 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');
10384 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');
10385 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10386 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10387 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10388
10389 -- Fixed Field position data -- 0-based!
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10451 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10452 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10453 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10454 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10455 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10456 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10457 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10458 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10459 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10460 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10461 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10462 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10463 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10464 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10465 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10466 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10467 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10468 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10469 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10470 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10471 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10472 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10473 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10474 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10475 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10476 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10477 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10478 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10479 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10480 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10481 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10482 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10483 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10484 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10485 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10486 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10487 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10488 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10489 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10490 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10491 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10492 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10493 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10494 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10495 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10496 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10497 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10498 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10499 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10500 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10501 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10502 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10503 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10504 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10505 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10506 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10507 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10508 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10509 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10510 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10511 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10512 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10513 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10514 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10515 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10516 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10517 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10518 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10519 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10520 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10521 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10522 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10523 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10524 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');
10525 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');
10526 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10527 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10528 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10529 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10530 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10531 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10532 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10533 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10534 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10535 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10536
10537 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10538 DECLARE
10539         ldr         RECORD;
10540         tval        TEXT;
10541         tval_rec    RECORD;
10542         bval        TEXT;
10543         bval_rec    RECORD;
10544     retval      config.marc21_rec_type_map%ROWTYPE;
10545 BEGIN
10546     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10547
10548     IF ldr.id IS NULL THEN
10549         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10550         RETURN retval;
10551     END IF;
10552
10553     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10554     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10555
10556
10557     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10558     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10559
10560     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10561
10562     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10563
10564
10565     IF retval.code IS NULL THEN
10566         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10567     END IF;
10568
10569     RETURN retval;
10570 END;
10571 $func$ LANGUAGE PLPGSQL;
10572
10573 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10574 DECLARE
10575     rtype       TEXT;
10576     ff_pos      RECORD;
10577     tag_data    RECORD;
10578     val         TEXT;
10579 BEGIN
10580     rtype := (biblio.marc21_record_type( rid )).code;
10581     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10582         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10583             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10584             RETURN val;
10585         END LOOP;
10586         val := REPEAT( ff_pos.default_val, ff_pos.length );
10587         RETURN val;
10588     END LOOP;
10589
10590     RETURN NULL;
10591 END;
10592 $func$ LANGUAGE PLPGSQL;
10593
10594 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10595 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10596 DECLARE
10597     rowid   INT := 0;
10598     _007    RECORD;
10599     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10600     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10601     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10602     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10603 BEGIN
10604
10605     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10606
10607     IF _007.id IS NOT NULL THEN
10608         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10609
10610         IF ptype.ptype_key IS NOT NULL THEN
10611             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10612                 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 );
10613
10614                 IF pval.id IS NOT NULL THEN
10615                     rowid := rowid + 1;
10616                     retval.id := rowid;
10617                     retval.record := rid;
10618                     retval.ptype := ptype.ptype_key;
10619                     retval.subfield := psf.id;
10620                     retval.value := pval.id;
10621                     RETURN NEXT retval;
10622                 END IF;
10623
10624             END LOOP;
10625         END IF;
10626     END IF;
10627
10628     RETURN;
10629 END;
10630 $func$ LANGUAGE PLPGSQL;
10631
10632 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10633 DROP VIEW IF EXISTS money.open_usr_summary;
10634 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10635
10636 -- The view should supply defaults for numeric (amount) columns
10637 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10638     SELECT  xact.id,
10639         xact.usr,
10640         xact.xact_start,
10641         xact.xact_finish,
10642         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10643         credit.payment_ts AS last_payment_ts,
10644         credit.note AS last_payment_note,
10645         credit.payment_type AS last_payment_type,
10646         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10647         debit.billing_ts AS last_billing_ts,
10648         debit.note AS last_billing_note,
10649         debit.billing_type AS last_billing_type,
10650         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10651         p.relname AS xact_type
10652       FROM  money.billable_xact xact
10653         JOIN pg_class p ON xact.tableoid = p.oid
10654         LEFT JOIN (
10655             SELECT  billing.xact,
10656                 sum(billing.amount) AS amount,
10657                 max(billing.billing_ts) AS billing_ts,
10658                 last(billing.note) AS note,
10659                 last(billing.billing_type) AS billing_type
10660               FROM  money.billing
10661               WHERE billing.voided IS FALSE
10662               GROUP BY billing.xact
10663             ) debit ON xact.id = debit.xact
10664         LEFT JOIN (
10665             SELECT  payment_view.xact,
10666                 sum(payment_view.amount) AS amount,
10667                 max(payment_view.payment_ts) AS payment_ts,
10668                 last(payment_view.note) AS note,
10669                 last(payment_view.payment_type) AS payment_type
10670               FROM  money.payment_view
10671               WHERE payment_view.voided IS FALSE
10672               GROUP BY payment_view.xact
10673             ) credit ON xact.id = credit.xact
10674       ORDER BY debit.billing_ts, credit.payment_ts;
10675
10676 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10677     SELECT * FROM money.billable_xact_summary_location_view
10678     WHERE xact_finish IS NULL;
10679
10680 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10681     SELECT 
10682         usr,
10683         SUM(total_paid) AS total_paid,
10684         SUM(total_owed) AS total_owed,
10685         SUM(balance_owed) AS balance_owed
10686     FROM  money.materialized_billable_xact_summary
10687     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10688     GROUP BY usr;
10689
10690 CREATE OR REPLACE VIEW money.usr_summary AS
10691     SELECT 
10692         usr, 
10693         sum(total_paid) AS total_paid, 
10694         sum(total_owed) AS total_owed, 
10695         sum(balance_owed) AS balance_owed
10696     FROM money.materialized_billable_xact_summary
10697     GROUP BY usr;
10698
10699 CREATE OR REPLACE VIEW money.open_usr_summary AS
10700     SELECT 
10701         usr, 
10702         sum(total_paid) AS total_paid, 
10703         sum(total_owed) AS total_owed, 
10704         sum(balance_owed) AS balance_owed
10705     FROM money.materialized_billable_xact_summary
10706     WHERE xact_finish IS NULL
10707     GROUP BY usr;
10708
10709 -- 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;
10710
10711 CREATE TABLE config.biblio_fingerprint (
10712         id                      SERIAL  PRIMARY KEY,
10713         name            TEXT    NOT NULL, 
10714         xpath           TEXT    NOT NULL,
10715     first_word  BOOL    NOT NULL DEFAULT FALSE,
10716         format          TEXT    NOT NULL DEFAULT 'marcxml'
10717 );
10718
10719 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10720     VALUES (
10721         'Title',
10722         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10723             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10724             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10725             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10726             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10727         'marcxml'
10728     );
10729
10730 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10731     VALUES (
10732         'Author',
10733         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10734             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10735             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10736             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10737             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10738         'marcxml',
10739         TRUE
10740     );
10741
10742 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10743 DECLARE
10744     qual        INT;
10745     ldr         TEXT;
10746     tval        TEXT;
10747     tval_rec    RECORD;
10748     bval        TEXT;
10749     bval_rec    RECORD;
10750     type_map    RECORD;
10751     ff_pos      RECORD;
10752     ff_tag_data TEXT;
10753 BEGIN
10754
10755     IF marc IS NULL OR marc = '' THEN
10756         RETURN NULL;
10757     END IF;
10758
10759     -- First, the count of tags
10760     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10761
10762     -- now go through a bunch of pain to get the record type
10763     IF best_type IS NOT NULL THEN
10764         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10765
10766         IF ldr IS NOT NULL THEN
10767             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10768             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10769
10770
10771             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10772             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10773
10774             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10775
10776             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10777
10778             IF type_map.code IS NOT NULL THEN
10779                 IF best_type = type_map.code THEN
10780                     qual := qual + qual / 2;
10781                 END IF;
10782
10783                 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
10784                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10785                     IF ff_tag_data = best_lang THEN
10786                             qual := qual + 100;
10787                     END IF;
10788                 END LOOP;
10789             END IF;
10790         END IF;
10791     END IF;
10792
10793     -- Now look for some quality metrics
10794     -- DCL record?
10795     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10796         qual := qual + 10;
10797     END IF;
10798
10799     -- From OCLC?
10800     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10801         qual := qual + 10;
10802     END IF;
10803
10804     RETURN qual;
10805
10806 END;
10807 $func$ LANGUAGE PLPGSQL;
10808
10809 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10810 DECLARE
10811     idx     config.biblio_fingerprint%ROWTYPE;
10812     xfrm        config.xml_transform%ROWTYPE;
10813     prev_xfrm   TEXT;
10814     transformed_xml TEXT;
10815     xml_node    TEXT;
10816     xml_node_list   TEXT[];
10817     raw_text    TEXT;
10818     output_text TEXT := '';
10819 BEGIN
10820
10821     IF marc IS NULL OR marc = '' THEN
10822         RETURN NULL;
10823     END IF;
10824
10825     -- Loop over the indexing entries
10826     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10827
10828         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10829
10830         -- See if we can skip the XSLT ... it's expensive
10831         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10832             -- Can't skip the transform
10833             IF xfrm.xslt <> '---' THEN
10834                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10835             ELSE
10836                 transformed_xml := marc;
10837             END IF;
10838
10839             prev_xfrm := xfrm.name;
10840         END IF;
10841
10842         raw_text := COALESCE(
10843             naco_normalize(
10844                 ARRAY_TO_STRING(
10845                     oils_xpath(
10846                         '//text()',
10847                         (oils_xpath(
10848                             idx.xpath,
10849                             transformed_xml,
10850                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10851                         ))[1]
10852                     ),
10853                     ''
10854                 )
10855             ),
10856             ''
10857         );
10858
10859         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10860         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10861
10862         IF idx.first_word IS TRUE THEN
10863             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10864         END IF;
10865
10866         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10867
10868     END LOOP;
10869
10870     RETURN output_text;
10871
10872 END;
10873 $func$ LANGUAGE PLPGSQL;
10874
10875 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10876 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10877 BEGIN
10878
10879     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10880
10881     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10882         RETURN NEW;
10883     END IF;
10884
10885     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10886     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10887
10888     RETURN NEW;
10889
10890 END;
10891 $func$ LANGUAGE PLPGSQL;
10892
10893 CREATE TABLE config.internal_flag (
10894     name    TEXT    PRIMARY KEY,
10895     value   TEXT,
10896     enabled BOOL    NOT NULL DEFAULT FALSE
10897 );
10898 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10899 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10900 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10901 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10902 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10903 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10904 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10905 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10906 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10907 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10908
10909 CREATE TABLE authority.bib_linking (
10910     id          BIGSERIAL   PRIMARY KEY,
10911     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10912     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10913 );
10914 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10915 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10916
10917 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10918     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10919 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10920
10921 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10922     DELETE FROM authority.bib_linking WHERE bib = $1;
10923     INSERT INTO authority.bib_linking (bib, authority)
10924         SELECT  y.bib,
10925                 y.authority
10926           FROM (    SELECT  DISTINCT $1 AS bib,
10927                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10928                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10929                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10930                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10931     SELECT $1;
10932 $func$ LANGUAGE SQL;
10933
10934 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10935 BEGIN
10936     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10937     IF NOT FOUND THEN
10938         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10939     END IF;
10940     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)
10941         SELECT  bib_id,
10942                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10943                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10944                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10945                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10946                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10947                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10948                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10949                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10950                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10951                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10952                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10953                 (   SELECT  v.value
10954                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10955                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10956                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10957                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10958                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10959                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10960
10961     RETURN;
10962 END;
10963 $func$ LANGUAGE PLPGSQL;
10964
10965 CREATE TABLE config.metabib_class (
10966     name    TEXT    PRIMARY KEY,
10967     label   TEXT    NOT NULL UNIQUE
10968 );
10969
10970 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10971 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10972 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10973 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10974 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10975
10976 CREATE TABLE metabib.facet_entry (
10977         id              BIGSERIAL       PRIMARY KEY,
10978         source          BIGINT          NOT NULL,
10979         field           INT             NOT NULL,
10980         value           TEXT            NOT NULL
10981 );
10982
10983 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10984 DECLARE
10985     fclass          RECORD;
10986     ind_data        metabib.field_entry_template%ROWTYPE;
10987 BEGIN
10988     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10989     IF NOT FOUND THEN
10990         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10991             -- RAISE NOTICE 'Emptying out %', fclass.name;
10992             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10993         END LOOP;
10994         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10995     END IF;
10996
10997     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10998         IF ind_data.field < 0 THEN
10999             ind_data.field = -1 * ind_data.field;
11000             INSERT INTO metabib.facet_entry (field, source, value)
11001                 VALUES (ind_data.field, ind_data.source, ind_data.value);
11002         ELSE
11003             EXECUTE $$
11004                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
11005                     VALUES ($$ ||
11006                         quote_literal(ind_data.field) || $$, $$ ||
11007                         quote_literal(ind_data.source) || $$, $$ ||
11008                         quote_literal(ind_data.value) ||
11009                     $$);$$;
11010         END IF;
11011
11012     END LOOP;
11013
11014     RETURN;
11015 END;
11016 $func$ LANGUAGE PLPGSQL;
11017
11018 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
11019 DECLARE
11020     uris            TEXT[];
11021     uri_xml         TEXT;
11022     uri_label       TEXT;
11023     uri_href        TEXT;
11024     uri_use         TEXT;
11025     uri_owner       TEXT;
11026     uri_owner_id    INT;
11027     uri_id          INT;
11028     uri_cn_id       INT;
11029     uri_map_id      INT;
11030 BEGIN
11031
11032     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
11033     IF ARRAY_UPPER(uris,1) > 0 THEN
11034         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
11035             -- First we pull info out of the 856
11036             uri_xml     := uris[i];
11037
11038             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
11039             CONTINUE WHEN uri_href IS NULL;
11040
11041             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11042             CONTINUE WHEN uri_label IS NULL;
11043
11044             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
11045             CONTINUE WHEN uri_owner IS NULL;
11046
11047             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11048
11049             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
11050
11051             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
11052             CONTINUE WHEN NOT FOUND;
11053
11054             -- now we look for a matching uri
11055             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11056             IF NOT FOUND THEN -- create one
11057                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
11058                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11059             END IF;
11060
11061             -- we need a call number to link through
11062             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;
11063             IF NOT FOUND THEN
11064                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
11065                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
11066                 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;
11067             END IF;
11068
11069             -- now, link them if they're not already
11070             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
11071             IF NOT FOUND THEN
11072                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
11073             END IF;
11074
11075         END LOOP;
11076     END IF;
11077
11078     RETURN;
11079 END;
11080 $func$ LANGUAGE PLPGSQL;
11081
11082 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
11083 DECLARE
11084     source_count    INT;
11085     old_mr          BIGINT;
11086     tmp_mr          metabib.metarecord%ROWTYPE;
11087     deleted_mrs     BIGINT[];
11088 BEGIN
11089
11090     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11091
11092     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
11093
11094         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11095             old_mr := tmp_mr.id;
11096         ELSE
11097             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11098             IF source_count = 0 THEN -- No other records
11099                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11100                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11101             END IF;
11102         END IF;
11103
11104     END LOOP;
11105
11106     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11107         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11108         IF old_mr IS NULL THEN -- nope, create one and grab its id
11109             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11110             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11111         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11112             UPDATE  metabib.metarecord
11113               SET   mods = NULL,
11114                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11115               WHERE id = old_mr;
11116         END IF;
11117     ELSE -- there was one we already attached to, update its mods cache and master_record
11118         UPDATE  metabib.metarecord
11119           SET   mods = NULL,
11120                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11121           WHERE id = old_mr;
11122     END IF;
11123
11124     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11125
11126     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11127         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
11128     END IF;
11129
11130     RETURN old_mr;
11131
11132 END;
11133 $func$ LANGUAGE PLPGSQL;
11134
11135 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11136 BEGIN
11137     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11138     IF NOT FOUND THEN
11139         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11140     END IF;
11141     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11142         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11143
11144     RETURN;
11145 END;
11146 $func$ LANGUAGE PLPGSQL;
11147
11148 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11149 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11150 BEGIN
11151
11152     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11153         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11154         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11155         RETURN NEW; -- and we're done
11156     END IF;
11157
11158     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11159         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11160
11161         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11162             RETURN NEW;
11163         END IF;
11164     END IF;
11165
11166     -- Record authority linking
11167     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11168     IF NOT FOUND THEN
11169         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11170     END IF;
11171
11172     -- Flatten and insert the mfr data
11173     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11174     IF NOT FOUND THEN
11175         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11176         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11177         IF NOT FOUND THEN
11178             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11179         END IF;
11180     END IF;
11181
11182     -- Gather and insert the field entry data
11183     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11184
11185     -- Located URI magic
11186     IF TG_OP = 'INSERT' THEN
11187         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11188         IF NOT FOUND THEN
11189             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11190         END IF;
11191     ELSE
11192         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11193         IF NOT FOUND THEN
11194             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11195         END IF;
11196     END IF;
11197
11198     -- (re)map metarecord-bib linking
11199     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11200         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11201         IF NOT FOUND THEN
11202             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11203         END IF;
11204     ELSE -- we're doing an update, and we're not deleted, remap
11205         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11206         IF NOT FOUND THEN
11207             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11208         END IF;
11209     END IF;
11210
11211     RETURN NEW;
11212 END;
11213 $func$ LANGUAGE PLPGSQL;
11214
11215 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11216 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 ();
11217
11218 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
11219 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11220
11221 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11222 DECLARE
11223     xpath_list  TEXT[];
11224     select_list TEXT[];
11225     where_list  TEXT[];
11226     q           TEXT;
11227     out_record  RECORD;
11228     empty_test  RECORD;
11229 BEGIN
11230     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11231  
11232     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11233  
11234     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11235         IF xpath_list[i] = 'null()' THEN
11236             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11237         ELSE
11238             select_list := ARRAY_APPEND(
11239                 select_list,
11240                 $sel$
11241                 EXPLODE_ARRAY(
11242                     COALESCE(
11243                         NULLIF(
11244                             oils_xpath(
11245                                 $sel$ ||
11246                                     quote_literal(
11247                                         CASE
11248                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11249                                             ELSE xpath_list[i] || '//text()'
11250                                         END
11251                                     ) ||
11252                                 $sel$,
11253                                 $sel$ || document_field || $sel$
11254                             ),
11255                            '{}'::TEXT[]
11256                         ),
11257                         '{NULL}'::TEXT[]
11258                     )
11259                 ) AS c_$sel$ || i
11260             );
11261             where_list := ARRAY_APPEND(
11262                 where_list,
11263                 'c_' || i || ' IS NOT NULL'
11264             );
11265         END IF;
11266     END LOOP;
11267  
11268     q := $q$
11269 SELECT * FROM (
11270     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11271 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' OR ' );
11272     -- RAISE NOTICE 'query: %', q;
11273  
11274     FOR out_record IN EXECUTE q LOOP
11275         RETURN NEXT out_record;
11276     END LOOP;
11277  
11278     RETURN;
11279 END;
11280 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11281
11282 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11283 DECLARE
11284
11285     owning_lib      TEXT;
11286     circ_lib        TEXT;
11287     call_number     TEXT;
11288     copy_number     TEXT;
11289     status          TEXT;
11290     location        TEXT;
11291     circulate       TEXT;
11292     deposit         TEXT;
11293     deposit_amount  TEXT;
11294     ref             TEXT;
11295     holdable        TEXT;
11296     price           TEXT;
11297     barcode         TEXT;
11298     circ_modifier   TEXT;
11299     circ_as_type    TEXT;
11300     alert_message   TEXT;
11301     opac_visible    TEXT;
11302     pub_note        TEXT;
11303     priv_note       TEXT;
11304
11305     attr_def        RECORD;
11306     tmp_attr_set    RECORD;
11307     attr_set        vandelay.import_item%ROWTYPE;
11308
11309     xpath           TEXT;
11310
11311 BEGIN
11312
11313     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11314
11315     IF FOUND THEN
11316
11317         attr_set.definition := attr_def.id; 
11318     
11319         -- Build the combined XPath
11320     
11321         owning_lib :=
11322             CASE
11323                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11324                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11325                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11326             END;
11327     
11328         circ_lib :=
11329             CASE
11330                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11331                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11332                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11333             END;
11334     
11335         call_number :=
11336             CASE
11337                 WHEN attr_def.call_number IS NULL THEN 'null()'
11338                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11339                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11340             END;
11341     
11342         copy_number :=
11343             CASE
11344                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11345                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11346                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11347             END;
11348     
11349         status :=
11350             CASE
11351                 WHEN attr_def.status IS NULL THEN 'null()'
11352                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11353                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11354             END;
11355     
11356         location :=
11357             CASE
11358                 WHEN attr_def.location IS NULL THEN 'null()'
11359                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11360                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11361             END;
11362     
11363         circulate :=
11364             CASE
11365                 WHEN attr_def.circulate IS NULL THEN 'null()'
11366                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11367                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11368             END;
11369     
11370         deposit :=
11371             CASE
11372                 WHEN attr_def.deposit IS NULL THEN 'null()'
11373                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11374                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11375             END;
11376     
11377         deposit_amount :=
11378             CASE
11379                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11380                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11381                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11382             END;
11383     
11384         ref :=
11385             CASE
11386                 WHEN attr_def.ref IS NULL THEN 'null()'
11387                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11388                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11389             END;
11390     
11391         holdable :=
11392             CASE
11393                 WHEN attr_def.holdable IS NULL THEN 'null()'
11394                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11395                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11396             END;
11397     
11398         price :=
11399             CASE
11400                 WHEN attr_def.price IS NULL THEN 'null()'
11401                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11402                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11403             END;
11404     
11405         barcode :=
11406             CASE
11407                 WHEN attr_def.barcode IS NULL THEN 'null()'
11408                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11409                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11410             END;
11411     
11412         circ_modifier :=
11413             CASE
11414                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11415                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11416                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11417             END;
11418     
11419         circ_as_type :=
11420             CASE
11421                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11422                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11423                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11424             END;
11425     
11426         alert_message :=
11427             CASE
11428                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11429                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11430                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11431             END;
11432     
11433         opac_visible :=
11434             CASE
11435                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11436                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11437                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11438             END;
11439
11440         pub_note :=
11441             CASE
11442                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11443                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11444                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11445             END;
11446         priv_note :=
11447             CASE
11448                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11449                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11450                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11451             END;
11452     
11453     
11454         xpath := 
11455             owning_lib      || '|' || 
11456             circ_lib        || '|' || 
11457             call_number     || '|' || 
11458             copy_number     || '|' || 
11459             status          || '|' || 
11460             location        || '|' || 
11461             circulate       || '|' || 
11462             deposit         || '|' || 
11463             deposit_amount  || '|' || 
11464             ref             || '|' || 
11465             holdable        || '|' || 
11466             price           || '|' || 
11467             barcode         || '|' || 
11468             circ_modifier   || '|' || 
11469             circ_as_type    || '|' || 
11470             alert_message   || '|' || 
11471             pub_note        || '|' || 
11472             priv_note       || '|' || 
11473             opac_visible;
11474
11475         -- RAISE NOTICE 'XPath: %', xpath;
11476         
11477         FOR tmp_attr_set IN
11478                 SELECT  *
11479                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11480                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11481                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11482                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11483         LOOP
11484     
11485             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11486             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11487
11488             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11489             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11490     
11491             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11492             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11493             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11494     
11495             SELECT  id INTO attr_set.location
11496               FROM  asset.copy_location
11497               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11498                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11499     
11500             attr_set.circulate      :=
11501                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11502                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11503
11504             attr_set.deposit        :=
11505                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11506                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11507
11508             attr_set.holdable       :=
11509                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11510                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11511
11512             attr_set.opac_visible   :=
11513                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11514                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11515
11516             attr_set.ref            :=
11517                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11518                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11519     
11520             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11521             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11522             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11523     
11524             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11525             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11526             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11527             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11528             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11529             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11530             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11531             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11532     
11533             RETURN NEXT attr_set;
11534     
11535         END LOOP;
11536     
11537     END IF;
11538
11539     RETURN;
11540
11541 END;
11542 $$ LANGUAGE PLPGSQL;
11543
11544 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11545 DECLARE
11546     attr_def    BIGINT;
11547     item_data   vandelay.import_item%ROWTYPE;
11548 BEGIN
11549
11550     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11551
11552     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11553         INSERT INTO vandelay.import_item (
11554             record,
11555             definition,
11556             owning_lib,
11557             circ_lib,
11558             call_number,
11559             copy_number,
11560             status,
11561             location,
11562             circulate,
11563             deposit,
11564             deposit_amount,
11565             ref,
11566             holdable,
11567             price,
11568             barcode,
11569             circ_modifier,
11570             circ_as_type,
11571             alert_message,
11572             pub_note,
11573             priv_note,
11574             opac_visible
11575         ) VALUES (
11576             NEW.id,
11577             item_data.definition,
11578             item_data.owning_lib,
11579             item_data.circ_lib,
11580             item_data.call_number,
11581             item_data.copy_number,
11582             item_data.status,
11583             item_data.location,
11584             item_data.circulate,
11585             item_data.deposit,
11586             item_data.deposit_amount,
11587             item_data.ref,
11588             item_data.holdable,
11589             item_data.price,
11590             item_data.barcode,
11591             item_data.circ_modifier,
11592             item_data.circ_as_type,
11593             item_data.alert_message,
11594             item_data.pub_note,
11595             item_data.priv_note,
11596             item_data.opac_visible
11597         );
11598     END LOOP;
11599
11600     RETURN NULL;
11601 END;
11602 $func$ LANGUAGE PLPGSQL;
11603
11604 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11605 BEGIN
11606     EXECUTE $$
11607         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11608     $$;
11609         RETURN TRUE;
11610 END;
11611 $creator$ LANGUAGE 'plpgsql';
11612
11613 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11614 BEGIN
11615     EXECUTE $$
11616         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11617             audit_id    BIGINT                          PRIMARY KEY,
11618             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11619             audit_action        TEXT                            NOT NULL,
11620             LIKE $$ || sch || $$.$$ || tbl || $$
11621         );
11622     $$;
11623         RETURN TRUE;
11624 END;
11625 $creator$ LANGUAGE 'plpgsql';
11626
11627 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11628 BEGIN
11629     EXECUTE $$
11630         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11631         RETURNS TRIGGER AS $func$
11632         BEGIN
11633             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11634                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11635                     now(),
11636                     SUBSTR(TG_OP,1,1),
11637                     OLD.*;
11638             RETURN NULL;
11639         END;
11640         $func$ LANGUAGE 'plpgsql';
11641     $$;
11642         RETURN TRUE;
11643 END;
11644 $creator$ LANGUAGE 'plpgsql';
11645
11646 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11647 BEGIN
11648     EXECUTE $$
11649         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11650             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11651             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11652     $$;
11653         RETURN TRUE;
11654 END;
11655 $creator$ LANGUAGE 'plpgsql';
11656
11657 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11658 BEGIN
11659     EXECUTE $$
11660         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11661             SELECT      -1, now() as audit_time, '-' as audit_action, *
11662               FROM      $$ || sch || $$.$$ || tbl || $$
11663                 UNION ALL
11664             SELECT      *
11665               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11666     $$;
11667         RETURN TRUE;
11668 END;
11669 $creator$ LANGUAGE 'plpgsql';
11670
11671 -- The main event
11672
11673 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11674 BEGIN
11675     PERFORM acq.create_acq_seq(sch, tbl);
11676     PERFORM acq.create_acq_history(sch, tbl);
11677     PERFORM acq.create_acq_func(sch, tbl);
11678     PERFORM acq.create_acq_update_trigger(sch, tbl);
11679     PERFORM acq.create_acq_lifecycle(sch, tbl);
11680     RETURN TRUE;
11681 END;
11682 $creator$ LANGUAGE 'plpgsql';
11683
11684 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11685
11686 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11687     SELECT  fund.id AS fund,
11688             fund_debit.encumbrance AS encumbrance,
11689             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11690       FROM acq.fund AS fund
11691                         LEFT JOIN acq.fund_debit AS fund_debit
11692                                 ON ( fund.id = fund_debit.fund )
11693       GROUP BY 1,2;
11694
11695 CREATE TABLE acq.debit_attribution (
11696         id                     INT         NOT NULL PRIMARY KEY,
11697         fund_debit             INT         NOT NULL
11698                                            REFERENCES acq.fund_debit
11699                                            DEFERRABLE INITIALLY DEFERRED,
11700     debit_amount           NUMERIC     NOT NULL,
11701         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11702                                            DEFERRABLE INITIALLY DEFERRED,
11703     credit_amount          NUMERIC
11704 );
11705
11706 CREATE INDEX acq_attribution_debit_idx
11707         ON acq.debit_attribution( fund_debit );
11708
11709 CREATE INDEX acq_attribution_credit_idx
11710         ON acq.debit_attribution( funding_source_credit );
11711
11712 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11713 /*
11714 Function to attribute expenditures and encumbrances to funding source credits,
11715 and thereby to funding sources.
11716
11717 Read the debits in chonological order, attributing each one to one or
11718 more funding source credits.  Constraints:
11719
11720 1. Don't attribute more to a credit than the amount of the credit.
11721
11722 2. For a given fund, don't attribute more to a funding source than the
11723 source has allocated to that fund.
11724
11725 3. Attribute debits to credits with deadlines before attributing them to
11726 credits without deadlines.  Otherwise attribute to the earliest credits
11727 first, based on the deadline date when present, or on the effective date
11728 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11729 This ordering is defined by an ORDER BY clause on the view
11730 acq.ordered_funding_source_credit.
11731
11732 Start by truncating the table acq.debit_attribution.  Then insert a row
11733 into that table for each attribution.  If a debit cannot be fully
11734 attributed, insert a row for the unattributable balance, with the 
11735 funding_source_credit and credit_amount columns NULL.
11736 */
11737 DECLARE
11738         curr_fund_source_bal RECORD;
11739         seqno                INT;     -- sequence num for credits applicable to a fund
11740         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11741         fc                   RECORD;  -- used for loading t_fund_credit table
11742         sc                   RECORD;  -- used for loading t_fund_credit table
11743         --
11744         -- Used exclusively in the main loop:
11745         --
11746         deb                 RECORD;   -- current row from acq.fund_debit table
11747         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11748         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11749         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11750         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11751         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11752         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11753         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11754         attrib_count        INT;      -- populates id of acq.debit_attribution
11755 BEGIN
11756         --
11757         -- Load a temporary table.  For each combination of fund and funding source,
11758         -- load an entry with the total amount allocated to that fund by that source.
11759         -- This sum may reflect transfers as well as original allocations.  We will
11760         -- reduce this balance whenever we attribute debits to it.
11761         --
11762         CREATE TEMP TABLE t_fund_source_bal
11763         ON COMMIT DROP AS
11764                 SELECT
11765                         fund AS fund,
11766                         funding_source AS source,
11767                         sum( amount ) AS balance
11768                 FROM
11769                         acq.fund_allocation
11770                 GROUP BY
11771                         fund,
11772                         funding_source
11773                 HAVING
11774                         sum( amount ) > 0;
11775         --
11776         CREATE INDEX t_fund_source_bal_idx
11777                 ON t_fund_source_bal( fund, source );
11778         -------------------------------------------------------------------------------
11779         --
11780         -- Load another temporary table.  For each fund, load zero or more
11781         -- funding source credits from which that fund can get money.
11782         --
11783         CREATE TEMP TABLE t_fund_credit (
11784                 fund        INT,
11785                 seq         INT,
11786                 credit      INT
11787         ) ON COMMIT DROP;
11788         --
11789         FOR fc IN
11790                 SELECT DISTINCT fund
11791                 FROM acq.fund_allocation
11792                 ORDER BY fund
11793         LOOP                  -- Loop over the funds
11794                 seqno := 1;
11795                 FOR sc IN
11796                         SELECT
11797                                 ofsc.id
11798                         FROM
11799                                 acq.ordered_funding_source_credit AS ofsc
11800                         WHERE
11801                                 ofsc.funding_source IN
11802                                 (
11803                                         SELECT funding_source
11804                                         FROM acq.fund_allocation
11805                                         WHERE fund = fc.fund
11806                                 )
11807                 ORDER BY
11808                     ofsc.sort_priority,
11809                     ofsc.sort_date,
11810                     ofsc.id
11811                 LOOP                        -- Add each credit to the list
11812                         INSERT INTO t_fund_credit (
11813                                 fund,
11814                                 seq,
11815                                 credit
11816                         ) VALUES (
11817                                 fc.fund,
11818                                 seqno,
11819                                 sc.id
11820                         );
11821                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11822                         seqno := seqno + 1;
11823                 END LOOP;     -- Loop over credits for a given fund
11824         END LOOP;         -- Loop over funds
11825         --
11826         CREATE INDEX t_fund_credit_idx
11827                 ON t_fund_credit( fund, seq );
11828         -------------------------------------------------------------------------------
11829         --
11830         -- Load yet another temporary table.  This one is a list of funding source
11831         -- credits, with their balances.  We shall reduce those balances as we
11832         -- attribute debits to them.
11833         --
11834         CREATE TEMP TABLE t_credit
11835         ON COMMIT DROP AS
11836         SELECT
11837             fsc.id AS credit,
11838             fsc.funding_source AS source,
11839             fsc.amount AS balance,
11840             fs.currency_type AS currency_type
11841         FROM
11842             acq.funding_source_credit AS fsc,
11843             acq.funding_source fs
11844         WHERE
11845             fsc.funding_source = fs.id
11846                         AND fsc.amount > 0;
11847         --
11848         CREATE INDEX t_credit_idx
11849                 ON t_credit( credit );
11850         --
11851         -------------------------------------------------------------------------------
11852         --
11853         -- Now that we have loaded the lookup tables: loop through the debits,
11854         -- attributing each one to one or more funding source credits.
11855         -- 
11856         truncate table acq.debit_attribution;
11857         --
11858         attrib_count := 0;
11859         FOR deb in
11860                 SELECT
11861                         fd.id,
11862                         fd.fund,
11863                         fd.amount,
11864                         f.currency_type,
11865                         fd.encumbrance
11866                 FROM
11867                         acq.fund_debit fd,
11868                         acq.fund f
11869                 WHERE
11870                         fd.fund = f.id
11871                 ORDER BY
11872                         fd.id
11873         LOOP
11874                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11875                 --
11876                 debit_balance := deb.amount;
11877                 --
11878                 -- Loop over the funding source credits that are eligible
11879                 -- to pay for this debit
11880                 --
11881                 FOR fund_credit IN
11882                         SELECT
11883                                 credit
11884                         FROM
11885                                 t_fund_credit
11886                         WHERE
11887                                 fund = deb.fund
11888                         ORDER BY
11889                                 seq
11890                 LOOP
11891                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11892                         --
11893                         -- Look up the balance for this credit.  If it's zero, then
11894                         -- it's not useful, so treat it as if you didn't find it.
11895                         -- (Actually there shouldn't be any zero balances in the table,
11896                         -- but we check just to make sure.)
11897                         --
11898                         SELECT *
11899                         INTO curr_credit_bal
11900                         FROM t_credit
11901                         WHERE
11902                                 credit = fund_credit.credit
11903                                 AND balance > 0;
11904                         --
11905                         IF curr_credit_bal IS NULL THEN
11906                                 --
11907                                 -- This credit is exhausted; try the next one.
11908                                 --
11909                                 CONTINUE;
11910                         END IF;
11911                         --
11912                         --
11913                         -- At this point we have an applicable credit with some money left.
11914                         -- Now see if the relevant funding_source has any money left.
11915                         --
11916                         -- Look up the balance of the allocation for this combination of
11917                         -- fund and source.  If you find such an entry, but it has a zero
11918                         -- balance, then it's not useful, so treat it as unfound.
11919                         -- (Actually there shouldn't be any zero balances in the table,
11920                         -- but we check just to make sure.)
11921                         --
11922                         SELECT *
11923                         INTO curr_fund_source_bal
11924                         FROM t_fund_source_bal
11925                         WHERE
11926                                 fund = deb.fund
11927                                 AND source = curr_credit_bal.source
11928                                 AND balance > 0;
11929                         --
11930                         IF curr_fund_source_bal IS NULL THEN
11931                                 --
11932                                 -- This fund/source doesn't exist or is already exhausted,
11933                                 -- so we can't use this credit.  Go on to the next one.
11934                                 --
11935                                 CONTINUE;
11936                         END IF;
11937                         --
11938                         -- Convert the available balances to the currency of the fund
11939                         --
11940                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11941                                 curr_credit_bal.currency_type, deb.currency_type );
11942                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11943                                 curr_credit_bal.currency_type, deb.currency_type );
11944                         --
11945                         -- Determine how much we can attribute to this credit: the minimum
11946                         -- of the debit amount, the fund/source balance, and the
11947                         -- credit balance
11948                         --
11949                         --RAISE NOTICE '   deb bal %', debit_balance;
11950                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11951                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11952                         --
11953                         conv_attr_amount := NULL;
11954                         attr_amount := debit_balance;
11955                         --
11956                         IF attr_amount > conv_alloc_balance THEN
11957                                 attr_amount := conv_alloc_balance;
11958                                 conv_attr_amount := curr_fund_source_bal.balance;
11959                         END IF;
11960                         IF attr_amount > conv_cred_balance THEN
11961                                 attr_amount := conv_cred_balance;
11962                                 conv_attr_amount := curr_credit_bal.balance;
11963                         END IF;
11964                         --
11965                         -- If we're attributing all of one of the balances, then that's how
11966                         -- much we will deduct from the balances, and we already captured
11967                         -- that amount above.  Otherwise we must convert the amount of the
11968                         -- attribution from the currency of the fund back to the currency of
11969                         -- the funding source.
11970                         --
11971                         IF conv_attr_amount IS NULL THEN
11972                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11973                                         deb.currency_type, curr_credit_bal.currency_type );
11974                         END IF;
11975                         --
11976                         -- Insert a row to record the attribution
11977                         --
11978                         attrib_count := attrib_count + 1;
11979                         INSERT INTO acq.debit_attribution (
11980                                 id,
11981                                 fund_debit,
11982                                 debit_amount,
11983                                 funding_source_credit,
11984                                 credit_amount
11985                         ) VALUES (
11986                                 attrib_count,
11987                                 deb.id,
11988                                 attr_amount,
11989                                 curr_credit_bal.credit,
11990                                 conv_attr_amount
11991                         );
11992                         --
11993                         -- Subtract the attributed amount from the various balances
11994                         --
11995                         debit_balance := debit_balance - attr_amount;
11996                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11997                         --
11998                         IF curr_fund_source_bal.balance <= 0 THEN
11999                                 --
12000                                 -- This allocation is exhausted.  Delete it so
12001                                 -- that we don't waste time looking at it again.
12002                                 --
12003                                 DELETE FROM t_fund_source_bal
12004                                 WHERE
12005                                         fund = curr_fund_source_bal.fund
12006                                         AND source = curr_fund_source_bal.source;
12007                         ELSE
12008                                 UPDATE t_fund_source_bal
12009                                 SET balance = balance - conv_attr_amount
12010                                 WHERE
12011                                         fund = curr_fund_source_bal.fund
12012                                         AND source = curr_fund_source_bal.source;
12013                         END IF;
12014                         --
12015                         IF curr_credit_bal.balance <= 0 THEN
12016                                 --
12017                                 -- This funding source credit is exhausted.  Delete it
12018                                 -- so that we don't waste time looking at it again.
12019                                 --
12020                                 --DELETE FROM t_credit
12021                                 --WHERE
12022                                 --      credit = curr_credit_bal.credit;
12023                                 --
12024                                 DELETE FROM t_fund_credit
12025                                 WHERE
12026                                         credit = curr_credit_bal.credit;
12027                         ELSE
12028                                 UPDATE t_credit
12029                                 SET balance = curr_credit_bal.balance
12030                                 WHERE
12031                                         credit = curr_credit_bal.credit;
12032                         END IF;
12033                         --
12034                         -- Are we done with this debit yet?
12035                         --
12036                         IF debit_balance <= 0 THEN
12037                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
12038                         END IF;
12039                 END LOOP;       -- End loop over credits
12040                 --
12041                 IF debit_balance <> 0 THEN
12042                         --
12043                         -- We weren't able to attribute this debit, or at least not
12044                         -- all of it.  Insert a row for the unattributed balance.
12045                         --
12046                         attrib_count := attrib_count + 1;
12047                         INSERT INTO acq.debit_attribution (
12048                                 id,
12049                                 fund_debit,
12050                                 debit_amount,
12051                                 funding_source_credit,
12052                                 credit_amount
12053                         ) VALUES (
12054                                 attrib_count,
12055                                 deb.id,
12056                                 debit_balance,
12057                                 NULL,
12058                                 NULL
12059                         );
12060                 END IF;
12061         END LOOP;   -- End of loop over debits
12062 END;
12063 $$ LANGUAGE 'plpgsql';
12064
12065 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
12066 DECLARE
12067     query TEXT;
12068     output TEXT;
12069 BEGIN
12070     query := $q$
12071         SELECT  regexp_replace(
12072                     oils_xpath_string(
12073                         $q$ || quote_literal($3) || $q$,
12074                         marc,
12075                         ' '
12076                     ),
12077                     $q$ || quote_literal($4) || $q$,
12078                     '',
12079                     'g')
12080           FROM  $q$ || $1 || $q$
12081           WHERE id = $q$ || $2;
12082
12083     EXECUTE query INTO output;
12084
12085     -- RAISE NOTICE 'query: %, output; %', query, output;
12086
12087     RETURN output;
12088 END;
12089 $$ LANGUAGE PLPGSQL IMMUTABLE;
12090
12091 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12092     SELECT extract_marc_field($1,$2,$3,'');
12093 $$ LANGUAGE SQL IMMUTABLE;
12094
12095 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12096 DECLARE
12097     moved_objects INT := 0;
12098     source_cn     asset.call_number%ROWTYPE;
12099     target_cn     asset.call_number%ROWTYPE;
12100     metarec       metabib.metarecord%ROWTYPE;
12101     hold          action.hold_request%ROWTYPE;
12102     ser_rec       serial.record_entry%ROWTYPE;
12103     uri_count     INT := 0;
12104     counter       INT := 0;
12105     uri_datafield TEXT;
12106     uri_text      TEXT := '';
12107 BEGIN
12108
12109     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12110     SELECT  INTO uri_count COUNT(*)
12111       FROM  asset.uri_call_number_map m
12112             JOIN asset.call_number cn ON (m.call_number = cn.id)
12113       WHERE cn.record = source_record;
12114
12115     IF uri_count > 0 THEN
12116
12117         SELECT  COUNT(*) INTO counter
12118           FROM  oils_xpath_table(
12119                     'id',
12120                     'marc',
12121                     'biblio.record_entry',
12122                     '//*[@tag="856"]',
12123                     'id=' || source_record
12124                 ) as t(i int,c text);
12125
12126         FOR i IN 1 .. counter LOOP
12127             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12128                         ' tag="856"' || 
12129                         ' ind1="' || FIRST(ind1) || '"'  || 
12130                         ' ind2="' || FIRST(ind2) || '">' || 
12131                         array_to_string(
12132                             array_accum(
12133                                 '<subfield code="' || subfield || '">' ||
12134                                 regexp_replace(
12135                                     regexp_replace(
12136                                         regexp_replace(data,'&','&amp;','g'),
12137                                         '>', '&gt;', 'g'
12138                                     ),
12139                                     '<', '&lt;', 'g'
12140                                 ) || '</subfield>'
12141                             ), ''
12142                         ) || '</datafield>' INTO uri_datafield
12143               FROM  oils_xpath_table(
12144                         'id',
12145                         'marc',
12146                         'biblio.record_entry',
12147                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12148                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12149                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12150                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12151                         'id=' || source_record
12152                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12153
12154             uri_text := uri_text || uri_datafield;
12155         END LOOP;
12156
12157         IF uri_text <> '' THEN
12158             UPDATE  biblio.record_entry
12159               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12160               WHERE id = target_record;
12161         END IF;
12162
12163     END IF;
12164
12165     -- Find and move metarecords to the target record
12166     SELECT  INTO metarec *
12167       FROM  metabib.metarecord
12168       WHERE master_record = source_record;
12169
12170     IF FOUND THEN
12171         UPDATE  metabib.metarecord
12172           SET   master_record = target_record,
12173             mods = NULL
12174           WHERE id = metarec.id;
12175
12176         moved_objects := moved_objects + 1;
12177     END IF;
12178
12179     -- Find call numbers attached to the source ...
12180     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12181
12182         SELECT  INTO target_cn *
12183           FROM  asset.call_number
12184           WHERE label = source_cn.label
12185             AND owning_lib = source_cn.owning_lib
12186             AND record = target_record;
12187
12188         -- ... and if there's a conflicting one on the target ...
12189         IF FOUND THEN
12190
12191             -- ... move the copies to that, and ...
12192             UPDATE  asset.copy
12193               SET   call_number = target_cn.id
12194               WHERE call_number = source_cn.id;
12195
12196             -- ... move V holds to the move-target call number
12197             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12198
12199                 UPDATE  action.hold_request
12200                   SET   target = target_cn.id
12201                   WHERE id = hold.id;
12202
12203                 moved_objects := moved_objects + 1;
12204             END LOOP;
12205
12206         -- ... if not ...
12207         ELSE
12208             -- ... just move the call number to the target record
12209             UPDATE  asset.call_number
12210               SET   record = target_record
12211               WHERE id = source_cn.id;
12212         END IF;
12213
12214         moved_objects := moved_objects + 1;
12215     END LOOP;
12216
12217     -- Find T holds targeting the source record ...
12218     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12219
12220         -- ... and move them to the target record
12221         UPDATE  action.hold_request
12222           SET   target = target_record
12223           WHERE id = hold.id;
12224
12225         moved_objects := moved_objects + 1;
12226     END LOOP;
12227
12228     -- Find serial records targeting the source record ...
12229     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12230         -- ... and move them to the target record
12231         UPDATE  serial.record_entry
12232           SET   record = target_record
12233           WHERE id = ser_rec.id;
12234
12235         moved_objects := moved_objects + 1;
12236     END LOOP;
12237
12238     -- Finally, "delete" the source record
12239     DELETE FROM biblio.record_entry WHERE id = source_record;
12240
12241     -- That's all, folks!
12242     RETURN moved_objects;
12243 END;
12244 $func$ LANGUAGE plpgsql;
12245
12246 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12247         old_fund   IN INT,
12248         old_amount IN NUMERIC,     -- in currency of old fund
12249         new_fund   IN INT,
12250         new_amount IN NUMERIC,     -- in currency of new fund
12251         user_id    IN INT,
12252         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12253         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12254 ) RETURNS VOID AS $$
12255 /* -------------------------------------------------------------------------------
12256
12257 Function to transfer money from one fund to another.
12258
12259 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12260 negative amount for the old (losing) fund and a positive amount for the new
12261 (gaining) fund.  In some cases there may be more than one such pair of entries
12262 in order to pull the money from different funding sources, or more specifically
12263 from different funding source credits.  For each such pair there is also an
12264 entry in acq.fund_transfer.
12265
12266 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12267 choose a funding source for the transferred money to come from.  This choice
12268 must meet two constraints, so far as possible:
12269
12270 1. The amount transferred from a given funding source must not exceed the
12271 amount allocated to the old fund by the funding source.  To that end we
12272 compare the amount being transferred to the amount allocated.
12273
12274 2. We shouldn't transfer money that has already been spent or encumbered, as
12275 defined by the funding attribution process.  We attribute expenses to the
12276 oldest funding source credits first.  In order to avoid transferring that
12277 attributed money, we reverse the priority, transferring from the newest funding
12278 source credits first.  There can be no guarantee that this approach will
12279 avoid overcommitting a fund, but no other approach can do any better.
12280
12281 In this context the age of a funding source credit is defined by the
12282 deadline_date for credits with deadline_dates, and by the effective_date for
12283 credits without deadline_dates, with the proviso that credits with deadline_dates
12284 are all considered "older" than those without.
12285
12286 ----------
12287
12288 In the signature for this function, there is one last parameter commented out,
12289 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12290 driving the main loop has an OR clause commented out, which references the
12291 funding_source_in parameter.
12292
12293 If these lines are uncommented, this function will allow the user optionally to
12294 restrict a fund transfer to a specified funding source.  If the source
12295 parameter is left NULL, then there will be no such restriction.
12296
12297 ------------------------------------------------------------------------------- */ 
12298 DECLARE
12299         same_currency      BOOLEAN;
12300         currency_ratio     NUMERIC;
12301         old_fund_currency  TEXT;
12302         old_remaining      NUMERIC;  -- in currency of old fund
12303         new_fund_currency  TEXT;
12304         new_fund_active    BOOLEAN;
12305         new_remaining      NUMERIC;  -- in currency of new fund
12306         curr_old_amt       NUMERIC;  -- in currency of old fund
12307         curr_new_amt       NUMERIC;  -- in currency of new fund
12308         source_addition    NUMERIC;  -- in currency of funding source
12309         source_deduction   NUMERIC;  -- in currency of funding source
12310         orig_allocated_amt NUMERIC;  -- in currency of funding source
12311         allocated_amt      NUMERIC;  -- in currency of fund
12312         source             RECORD;
12313 BEGIN
12314         --
12315         -- Sanity checks
12316         --
12317         IF old_fund IS NULL THEN
12318                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12319         END IF;
12320         --
12321         IF old_amount IS NULL THEN
12322                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12323         END IF;
12324         --
12325         -- The new fund and its amount must be both NULL or both not NULL.
12326         --
12327         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12328                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12329         END IF;
12330         --
12331         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12332                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12333         END IF;
12334         --
12335         IF user_id IS NULL THEN
12336                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12337         END IF;
12338         --
12339         -- Initialize the amounts to be transferred, each denominated
12340         -- in the currency of its respective fund.  They will be
12341         -- reduced on each iteration of the loop.
12342         --
12343         old_remaining := old_amount;
12344         new_remaining := new_amount;
12345         --
12346         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12347         --      old_amount, old_fund, new_amount, new_fund;
12348         --
12349         -- Get the currency types of the old and new funds.
12350         --
12351         SELECT
12352                 currency_type
12353         INTO
12354                 old_fund_currency
12355         FROM
12356                 acq.fund
12357         WHERE
12358                 id = old_fund;
12359         --
12360         IF old_fund_currency IS NULL THEN
12361                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12362         END IF;
12363         --
12364         IF new_fund IS NOT NULL THEN
12365                 SELECT
12366                         currency_type,
12367                         active
12368                 INTO
12369                         new_fund_currency,
12370                         new_fund_active
12371                 FROM
12372                         acq.fund
12373                 WHERE
12374                         id = new_fund;
12375                 --
12376                 IF new_fund_currency IS NULL THEN
12377                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12378                 ELSIF NOT new_fund_active THEN
12379                         --
12380                         -- No point in putting money into a fund from whence you can't spend it
12381                         --
12382                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12383                 END IF;
12384                 --
12385                 IF new_amount = old_amount THEN
12386                         same_currency := true;
12387                         currency_ratio := 1;
12388                 ELSE
12389                         --
12390                         -- We'll have to translate currency between funds.  We presume that
12391                         -- the calling code has already applied an appropriate exchange rate,
12392                         -- so we'll apply the same conversion to each sub-transfer.
12393                         --
12394                         same_currency := false;
12395                         currency_ratio := new_amount / old_amount;
12396                 END IF;
12397         END IF;
12398         --
12399         -- Identify the funding source(s) from which we want to transfer the money.
12400         -- The principle is that we want to transfer the newest money first, because
12401         -- we spend the oldest money first.  The priority for spending is defined
12402         -- by a sort of the view acq.ordered_funding_source_credit.
12403         --
12404         FOR source in
12405                 SELECT
12406                         ofsc.id,
12407                         ofsc.funding_source,
12408                         ofsc.amount,
12409                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12410                                 AS converted_amt,
12411                         fs.currency_type
12412                 FROM
12413                         acq.ordered_funding_source_credit AS ofsc,
12414                         acq.funding_source fs
12415                 WHERE
12416                         ofsc.funding_source = fs.id
12417                         and ofsc.funding_source IN
12418                         (
12419                                 SELECT funding_source
12420                                 FROM acq.fund_allocation
12421                                 WHERE fund = old_fund
12422                         )
12423                         -- and
12424                         -- (
12425                         --      ofsc.funding_source = funding_source_in
12426                         --      OR funding_source_in IS NULL
12427                         -- )
12428                 ORDER BY
12429                         ofsc.sort_priority desc,
12430                         ofsc.sort_date desc,
12431                         ofsc.id desc
12432         LOOP
12433                 --
12434                 -- Determine how much money the old fund got from this funding source,
12435                 -- denominated in the currency types of the source and of the fund.
12436                 -- This result may reflect transfers from previous iterations.
12437                 --
12438                 SELECT
12439                         COALESCE( sum( amount ), 0 ),
12440                         COALESCE( sum( amount )
12441                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12442                 INTO
12443                         orig_allocated_amt,     -- in currency of the source
12444                         allocated_amt           -- in currency of the old fund
12445                 FROM
12446                         acq.fund_allocation
12447                 WHERE
12448                         fund = old_fund
12449                         and funding_source = source.funding_source;
12450                 --      
12451                 -- Determine how much to transfer from this credit, in the currency
12452                 -- of the fund.   Begin with the amount remaining to be attributed:
12453                 --
12454                 curr_old_amt := old_remaining;
12455                 --
12456                 -- Can't attribute more than was allocated from the fund:
12457                 --
12458                 IF curr_old_amt > allocated_amt THEN
12459                         curr_old_amt := allocated_amt;
12460                 END IF;
12461                 --
12462                 -- Can't attribute more than the amount of the current credit:
12463                 --
12464                 IF curr_old_amt > source.converted_amt THEN
12465                         curr_old_amt := source.converted_amt;
12466                 END IF;
12467                 --
12468                 curr_old_amt := trunc( curr_old_amt, 2 );
12469                 --
12470                 old_remaining := old_remaining - curr_old_amt;
12471                 --
12472                 -- Determine the amount to be deducted, if any,
12473                 -- from the old allocation.
12474                 --
12475                 IF old_remaining > 0 THEN
12476                         --
12477                         -- In this case we're using the whole allocation, so use that
12478                         -- amount directly instead of applying a currency translation
12479                         -- and thereby inviting round-off errors.
12480                         --
12481                         source_deduction := - orig_allocated_amt;
12482                 ELSE 
12483                         source_deduction := trunc(
12484                                 ( - curr_old_amt ) *
12485                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12486                                 2 );
12487                 END IF;
12488                 --
12489                 IF source_deduction <> 0 THEN
12490                         --
12491                         -- Insert negative allocation for old fund in fund_allocation,
12492                         -- converted into the currency of the funding source
12493                         --
12494                         INSERT INTO acq.fund_allocation (
12495                                 funding_source,
12496                                 fund,
12497                                 amount,
12498                                 allocator,
12499                                 note
12500                         ) VALUES (
12501                                 source.funding_source,
12502                                 old_fund,
12503                                 source_deduction,
12504                                 user_id,
12505                                 'Transfer to fund ' || new_fund
12506                         );
12507                 END IF;
12508                 --
12509                 IF new_fund IS NOT NULL THEN
12510                         --
12511                         -- Determine how much to add to the new fund, in
12512                         -- its currency, and how much remains to be added:
12513                         --
12514                         IF same_currency THEN
12515                                 curr_new_amt := curr_old_amt;
12516                         ELSE
12517                                 IF old_remaining = 0 THEN
12518                                         --
12519                                         -- This is the last iteration, so nothing should be left
12520                                         --
12521                                         curr_new_amt := new_remaining;
12522                                         new_remaining := 0;
12523                                 ELSE
12524                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12525                                         new_remaining := new_remaining - curr_new_amt;
12526                                 END IF;
12527                         END IF;
12528                         --
12529                         -- Determine how much to add, if any,
12530                         -- to the new fund's allocation.
12531                         --
12532                         IF old_remaining > 0 THEN
12533                                 --
12534                                 -- In this case we're using the whole allocation, so use that amount
12535                                 -- amount directly instead of applying a currency translation and
12536                                 -- thereby inviting round-off errors.
12537                                 --
12538                                 source_addition := orig_allocated_amt;
12539                         ELSIF source.currency_type = old_fund_currency THEN
12540                                 --
12541                                 -- In this case we don't need a round trip currency translation,
12542                                 -- thereby inviting round-off errors:
12543                                 --
12544                                 source_addition := curr_old_amt;
12545                         ELSE 
12546                                 source_addition := trunc(
12547                                         curr_new_amt *
12548                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12549                                         2 );
12550                         END IF;
12551                         --
12552                         IF source_addition <> 0 THEN
12553                                 --
12554                                 -- Insert positive allocation for new fund in fund_allocation,
12555                                 -- converted to the currency of the founding source
12556                                 --
12557                                 INSERT INTO acq.fund_allocation (
12558                                         funding_source,
12559                                         fund,
12560                                         amount,
12561                                         allocator,
12562                                         note
12563                                 ) VALUES (
12564                                         source.funding_source,
12565                                         new_fund,
12566                                         source_addition,
12567                                         user_id,
12568                                         'Transfer from fund ' || old_fund
12569                                 );
12570                         END IF;
12571                 END IF;
12572                 --
12573                 IF trunc( curr_old_amt, 2 ) <> 0
12574                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12575                         --
12576                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12577                         --
12578                         INSERT INTO acq.fund_transfer (
12579                                 src_fund,
12580                                 src_amount,
12581                                 dest_fund,
12582                                 dest_amount,
12583                                 transfer_user,
12584                                 note,
12585                                 funding_source_credit
12586                         ) VALUES (
12587                                 old_fund,
12588                                 trunc( curr_old_amt, 2 ),
12589                                 new_fund,
12590                                 trunc( curr_new_amt, 2 ),
12591                                 user_id,
12592                                 xfer_note,
12593                                 source.id
12594                         );
12595                 END IF;
12596                 --
12597                 if old_remaining <= 0 THEN
12598                         EXIT;                   -- Nothing more to be transferred
12599                 END IF;
12600         END LOOP;
12601 END;
12602 $$ LANGUAGE plpgsql;
12603
12604 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
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 = org_unit_id
12648         LOOP
12649                 BEGIN
12650                         INSERT INTO acq.fund (
12651                                 org,
12652                                 name,
12653                                 year,
12654                                 currency_type,
12655                                 code,
12656                                 rollover,
12657                                 propagate,
12658                                 balance_warning_percent,
12659                                 balance_stop_percent
12660                         ) VALUES (
12661                                 old_fund.org,
12662                                 old_fund.name,
12663                                 old_year + 1,
12664                                 old_fund.currency_type,
12665                                 old_fund.code,
12666                                 old_fund.rollover,
12667                                 true,
12668                                 old_fund.balance_warning_percent,
12669                                 old_fund.balance_stop_percent
12670                         )
12671                         RETURNING id INTO new_id;
12672                 EXCEPTION
12673                         WHEN unique_violation THEN
12674                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12675                                 CONTINUE;
12676                 END;
12677                 --RAISE NOTICE 'Propagating fund % to fund %',
12678                 --      old_fund.code, new_id;
12679         END LOOP;
12680 END;
12681 $$ LANGUAGE plpgsql;
12682
12683 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12684         old_year INTEGER,
12685         user_id INTEGER,
12686         org_unit_id INTEGER
12687 ) RETURNS VOID AS $$
12688 DECLARE
12689 --
12690 new_id      INT;
12691 old_fund    RECORD;
12692 org_found   BOOLEAN;
12693 --
12694 BEGIN
12695         --
12696         -- Sanity checks
12697         --
12698         IF old_year IS NULL THEN
12699                 RAISE EXCEPTION 'Input year argument is NULL';
12700         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12701                 RAISE EXCEPTION 'Input year is out of range';
12702         END IF;
12703         --
12704         IF user_id IS NULL THEN
12705                 RAISE EXCEPTION 'Input user id argument is NULL';
12706         END IF;
12707         --
12708         IF org_unit_id IS NULL THEN
12709                 RAISE EXCEPTION 'Org unit id argument is NULL';
12710         ELSE
12711                 SELECT TRUE INTO org_found
12712                 FROM actor.org_unit
12713                 WHERE id = org_unit_id;
12714                 --
12715                 IF org_found IS NULL THEN
12716                         RAISE EXCEPTION 'Org unit id is invalid';
12717                 END IF;
12718         END IF;
12719         --
12720         -- Loop over the applicable funds
12721         --
12722         FOR old_fund in SELECT * FROM acq.fund
12723         WHERE
12724                 year = old_year
12725                 AND propagate
12726                 AND org in (
12727                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12728                 )
12729         LOOP
12730                 BEGIN
12731                         INSERT INTO acq.fund (
12732                                 org,
12733                                 name,
12734                                 year,
12735                                 currency_type,
12736                                 code,
12737                                 rollover,
12738                                 propagate,
12739                                 balance_warning_percent,
12740                                 balance_stop_percent
12741                         ) VALUES (
12742                                 old_fund.org,
12743                                 old_fund.name,
12744                                 old_year + 1,
12745                                 old_fund.currency_type,
12746                                 old_fund.code,
12747                                 old_fund.rollover,
12748                                 true,
12749                                 old_fund.balance_warning_percent,
12750                                 old_fund.balance_stop_percent
12751                         )
12752                         RETURNING id INTO new_id;
12753                 EXCEPTION
12754                         WHEN unique_violation THEN
12755                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12756                                 CONTINUE;
12757                 END;
12758                 --RAISE NOTICE 'Propagating fund % to fund %',
12759                 --      old_fund.code, new_id;
12760         END LOOP;
12761 END;
12762 $$ LANGUAGE plpgsql;
12763
12764 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12765         old_year INTEGER,
12766         user_id INTEGER,
12767         org_unit_id INTEGER
12768 ) RETURNS VOID AS $$
12769 DECLARE
12770 --
12771 new_fund    INT;
12772 new_year    INT := old_year + 1;
12773 org_found   BOOL;
12774 xfer_amount NUMERIC;
12775 roll_fund   RECORD;
12776 deb         RECORD;
12777 detail      RECORD;
12778 --
12779 BEGIN
12780         --
12781         -- Sanity checks
12782         --
12783         IF old_year IS NULL THEN
12784                 RAISE EXCEPTION 'Input year argument is NULL';
12785     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12786         RAISE EXCEPTION 'Input year is out of range';
12787         END IF;
12788         --
12789         IF user_id IS NULL THEN
12790                 RAISE EXCEPTION 'Input user id argument is NULL';
12791         END IF;
12792         --
12793         IF org_unit_id IS NULL THEN
12794                 RAISE EXCEPTION 'Org unit id argument is NULL';
12795         ELSE
12796                 --
12797                 -- Validate the org unit
12798                 --
12799                 SELECT TRUE
12800                 INTO org_found
12801                 FROM actor.org_unit
12802                 WHERE id = org_unit_id;
12803                 --
12804                 IF org_found IS NULL THEN
12805                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12806                 END IF;
12807         END IF;
12808         --
12809         -- Loop over the propagable funds to identify the details
12810         -- from the old fund plus the id of the new one, if it exists.
12811         --
12812         FOR roll_fund in
12813         SELECT
12814             oldf.id AS old_fund,
12815             oldf.org,
12816             oldf.name,
12817             oldf.currency_type,
12818             oldf.code,
12819                 oldf.rollover,
12820             newf.id AS new_fund_id
12821         FROM
12822         acq.fund AS oldf
12823         LEFT JOIN acq.fund AS newf
12824                 ON ( oldf.code = newf.code )
12825         WHERE
12826                     oldf.org = org_unit_id
12827                 and oldf.year = old_year
12828                 and oldf.propagate
12829         and newf.year = new_year
12830         LOOP
12831                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12832                 --
12833                 IF roll_fund.new_fund_id IS NULL THEN
12834                         --
12835                         -- The old fund hasn't been propagated yet.  Propagate it now.
12836                         --
12837                         INSERT INTO acq.fund (
12838                                 org,
12839                                 name,
12840                                 year,
12841                                 currency_type,
12842                                 code,
12843                                 rollover,
12844                                 propagate,
12845                                 balance_warning_percent,
12846                                 balance_stop_percent
12847                         ) VALUES (
12848                                 roll_fund.org,
12849                                 roll_fund.name,
12850                                 new_year,
12851                                 roll_fund.currency_type,
12852                                 roll_fund.code,
12853                                 true,
12854                                 true,
12855                                 roll_fund.balance_warning_percent,
12856                                 roll_fund.balance_stop_percent
12857                         )
12858                         RETURNING id INTO new_fund;
12859                 ELSE
12860                         new_fund = roll_fund.new_fund_id;
12861                 END IF;
12862                 --
12863                 -- Determine the amount to transfer
12864                 --
12865                 SELECT amount
12866                 INTO xfer_amount
12867                 FROM acq.fund_spent_balance
12868                 WHERE fund = roll_fund.old_fund;
12869                 --
12870                 IF xfer_amount <> 0 THEN
12871                         IF roll_fund.rollover THEN
12872                                 --
12873                                 -- Transfer balance from old fund to new
12874                                 --
12875                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12876                                 --
12877                                 PERFORM acq.transfer_fund(
12878                                         roll_fund.old_fund,
12879                                         xfer_amount,
12880                                         new_fund,
12881                                         xfer_amount,
12882                                         user_id,
12883                                         'Rollover'
12884                                 );
12885                         ELSE
12886                                 --
12887                                 -- Transfer balance from old fund to the void
12888                                 --
12889                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12890                                 --
12891                                 PERFORM acq.transfer_fund(
12892                                         roll_fund.old_fund,
12893                                         xfer_amount,
12894                                         NULL,
12895                                         NULL,
12896                                         user_id,
12897                                         'Rollover'
12898                                 );
12899                         END IF;
12900                 END IF;
12901                 --
12902                 IF roll_fund.rollover THEN
12903                         --
12904                         -- Move any lineitems from the old fund to the new one
12905                         -- where the associated debit is an encumbrance.
12906                         --
12907                         -- Any other tables tying expenditure details to funds should
12908                         -- receive similar treatment.  At this writing there are none.
12909                         --
12910                         UPDATE acq.lineitem_detail
12911                         SET fund = new_fund
12912                         WHERE
12913                         fund = roll_fund.old_fund -- this condition may be redundant
12914                         AND fund_debit in
12915                         (
12916                                 SELECT id
12917                                 FROM acq.fund_debit
12918                                 WHERE
12919                                 fund = roll_fund.old_fund
12920                                 AND encumbrance
12921                         );
12922                         --
12923                         -- Move encumbrance debits from the old fund to the new fund
12924                         --
12925                         UPDATE acq.fund_debit
12926                         SET fund = new_fund
12927                         wHERE
12928                                 fund = roll_fund.old_fund
12929                                 AND encumbrance;
12930                 END IF;
12931                 --
12932                 -- Mark old fund as inactive, now that we've closed it
12933                 --
12934                 UPDATE acq.fund
12935                 SET active = FALSE
12936                 WHERE id = roll_fund.old_fund;
12937         END LOOP;
12938 END;
12939 $$ LANGUAGE plpgsql;
12940
12941 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12942         old_year INTEGER,
12943         user_id INTEGER,
12944         org_unit_id INTEGER
12945 ) RETURNS VOID AS $$
12946 DECLARE
12947 --
12948 new_fund    INT;
12949 new_year    INT := old_year + 1;
12950 org_found   BOOL;
12951 xfer_amount NUMERIC;
12952 roll_fund   RECORD;
12953 deb         RECORD;
12954 detail      RECORD;
12955 --
12956 BEGIN
12957         --
12958         -- Sanity checks
12959         --
12960         IF old_year IS NULL THEN
12961                 RAISE EXCEPTION 'Input year argument is NULL';
12962     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12963         RAISE EXCEPTION 'Input year is out of range';
12964         END IF;
12965         --
12966         IF user_id IS NULL THEN
12967                 RAISE EXCEPTION 'Input user id argument is NULL';
12968         END IF;
12969         --
12970         IF org_unit_id IS NULL THEN
12971                 RAISE EXCEPTION 'Org unit id argument is NULL';
12972         ELSE
12973                 --
12974                 -- Validate the org unit
12975                 --
12976                 SELECT TRUE
12977                 INTO org_found
12978                 FROM actor.org_unit
12979                 WHERE id = org_unit_id;
12980                 --
12981                 IF org_found IS NULL THEN
12982                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12983                 END IF;
12984         END IF;
12985         --
12986         -- Loop over the propagable funds to identify the details
12987         -- from the old fund plus the id of the new one, if it exists.
12988         --
12989         FOR roll_fund in
12990         SELECT
12991             oldf.id AS old_fund,
12992             oldf.org,
12993             oldf.name,
12994             oldf.currency_type,
12995             oldf.code,
12996                 oldf.rollover,
12997             newf.id AS new_fund_id
12998         FROM
12999         acq.fund AS oldf
13000         LEFT JOIN acq.fund AS newf
13001                 ON ( oldf.code = newf.code )
13002         WHERE
13003                     oldf.year = old_year
13004                 AND oldf.propagate
13005         AND newf.year = new_year
13006                 AND oldf.org in (
13007                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
13008                 )
13009         LOOP
13010                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
13011                 --
13012                 IF roll_fund.new_fund_id IS NULL THEN
13013                         --
13014                         -- The old fund hasn't been propagated yet.  Propagate it now.
13015                         --
13016                         INSERT INTO acq.fund (
13017                                 org,
13018                                 name,
13019                                 year,
13020                                 currency_type,
13021                                 code,
13022                                 rollover,
13023                                 propagate,
13024                                 balance_warning_percent,
13025                                 balance_stop_percent
13026                         ) VALUES (
13027                                 roll_fund.org,
13028                                 roll_fund.name,
13029                                 new_year,
13030                                 roll_fund.currency_type,
13031                                 roll_fund.code,
13032                                 true,
13033                                 true,
13034                                 roll_fund.balance_warning_percent,
13035                                 roll_fund.balance_stop_percent
13036                         )
13037                         RETURNING id INTO new_fund;
13038                 ELSE
13039                         new_fund = roll_fund.new_fund_id;
13040                 END IF;
13041                 --
13042                 -- Determine the amount to transfer
13043                 --
13044                 SELECT amount
13045                 INTO xfer_amount
13046                 FROM acq.fund_spent_balance
13047                 WHERE fund = roll_fund.old_fund;
13048                 --
13049                 IF xfer_amount <> 0 THEN
13050                         IF roll_fund.rollover THEN
13051                                 --
13052                                 -- Transfer balance from old fund to new
13053                                 --
13054                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
13055                                 --
13056                                 PERFORM acq.transfer_fund(
13057                                         roll_fund.old_fund,
13058                                         xfer_amount,
13059                                         new_fund,
13060                                         xfer_amount,
13061                                         user_id,
13062                                         'Rollover'
13063                                 );
13064                         ELSE
13065                                 --
13066                                 -- Transfer balance from old fund to the void
13067                                 --
13068                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
13069                                 --
13070                                 PERFORM acq.transfer_fund(
13071                                         roll_fund.old_fund,
13072                                         xfer_amount,
13073                                         NULL,
13074                                         NULL,
13075                                         user_id,
13076                                         'Rollover'
13077                                 );
13078                         END IF;
13079                 END IF;
13080                 --
13081                 IF roll_fund.rollover THEN
13082                         --
13083                         -- Move any lineitems from the old fund to the new one
13084                         -- where the associated debit is an encumbrance.
13085                         --
13086                         -- Any other tables tying expenditure details to funds should
13087                         -- receive similar treatment.  At this writing there are none.
13088                         --
13089                         UPDATE acq.lineitem_detail
13090                         SET fund = new_fund
13091                         WHERE
13092                         fund = roll_fund.old_fund -- this condition may be redundant
13093                         AND fund_debit in
13094                         (
13095                                 SELECT id
13096                                 FROM acq.fund_debit
13097                                 WHERE
13098                                 fund = roll_fund.old_fund
13099                                 AND encumbrance
13100                         );
13101                         --
13102                         -- Move encumbrance debits from the old fund to the new fund
13103                         --
13104                         UPDATE acq.fund_debit
13105                         SET fund = new_fund
13106                         wHERE
13107                                 fund = roll_fund.old_fund
13108                                 AND encumbrance;
13109                 END IF;
13110                 --
13111                 -- Mark old fund as inactive, now that we've closed it
13112                 --
13113                 UPDATE acq.fund
13114                 SET active = FALSE
13115                 WHERE id = roll_fund.old_fund;
13116         END LOOP;
13117 END;
13118 $$ LANGUAGE plpgsql;
13119
13120 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13121     SELECT regexp_replace($1, ',', '', 'g');
13122 $$ LANGUAGE SQL STRICT IMMUTABLE;
13123
13124 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13125     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13126 $$ LANGUAGE SQL STRICT IMMUTABLE;
13127
13128 CREATE TABLE acq.distribution_formula_application (
13129     id BIGSERIAL PRIMARY KEY,
13130     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13131     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13132     formula INT NOT NULL
13133         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13134     lineitem INT NOT NULL
13135         REFERENCES acq.lineitem( id )
13136                 ON DELETE CASCADE
13137                 DEFERRABLE INITIALLY DEFERRED
13138 );
13139
13140 CREATE INDEX acqdfa_df_idx
13141     ON acq.distribution_formula_application(formula);
13142 CREATE INDEX acqdfa_li_idx
13143     ON acq.distribution_formula_application(lineitem);
13144 CREATE INDEX acqdfa_creator_idx
13145     ON acq.distribution_formula_application(creator);
13146
13147 CREATE TABLE acq.user_request_type (
13148     id      SERIAL  PRIMARY KEY,
13149     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13150 );
13151
13152 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13153 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13154 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13155 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13156 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13157
13158 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13159
13160 CREATE TABLE acq.cancel_reason (
13161         id            SERIAL            PRIMARY KEY,
13162         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13163                                         DEFERRABLE INITIALLY DEFERRED,
13164         label         TEXT              NOT NULL,
13165         description   TEXT              NOT NULL,
13166         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13167         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13168 );
13169
13170 -- Reserve ids 1-999 for stock reasons
13171 -- Reserve ids 1000-1999 for EDI reasons
13172 -- 2000+ are available for staff to create
13173
13174 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13175
13176 CREATE TABLE acq.user_request (
13177     id                  SERIAL  PRIMARY KEY,
13178     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13179     hold                BOOL    NOT NULL DEFAULT TRUE,
13180
13181     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13182     holdable_formats    TEXT,           -- nullable, for use in hold creation
13183     phone_notify        TEXT,
13184     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13185     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13186     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13187     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13188     need_before         TIMESTAMPTZ,    -- don't create holds after this
13189     max_fee             TEXT,
13190
13191     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13192     isxn                TEXT,
13193     title               TEXT,
13194     volume              TEXT,
13195     author              TEXT,
13196     article_title       TEXT,
13197     article_pages       TEXT,
13198     publisher           TEXT,
13199     location            TEXT,
13200     pubdate             TEXT,
13201     mentioned           TEXT,
13202     other_info          TEXT,
13203         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13204                                              DEFERRABLE INITIALLY DEFERRED
13205 );
13206
13207 CREATE TABLE acq.lineitem_alert_text (
13208         id               SERIAL         PRIMARY KEY,
13209         code             TEXT           NOT NULL,
13210         description      TEXT,
13211         owning_lib       INT            NOT NULL
13212                                         REFERENCES actor.org_unit(id)
13213                                         DEFERRABLE INITIALLY DEFERRED,
13214         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13215 );
13216
13217 ALTER TABLE acq.lineitem_note
13218         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13219                                          DEFERRABLE INITIALLY DEFERRED;
13220
13221 -- add ON DELETE CASCADE clause
13222
13223 ALTER TABLE acq.lineitem_note
13224         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13225
13226 ALTER TABLE acq.lineitem_note
13227         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13228                 ON DELETE CASCADE
13229                 DEFERRABLE INITIALLY DEFERRED;
13230
13231 ALTER TABLE acq.lineitem_note
13232         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13233
13234 CREATE TABLE acq.invoice_method (
13235     code    TEXT    PRIMARY KEY,
13236     name    TEXT    NOT NULL -- i18n-ize
13237 );
13238 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13239 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13240
13241 CREATE TABLE acq.invoice_payment_method (
13242         code      TEXT     PRIMARY KEY,
13243         name      TEXT     NOT NULL
13244 );
13245
13246 CREATE TABLE acq.invoice (
13247     id             SERIAL      PRIMARY KEY,
13248     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13249     provider       INT         NOT NULL REFERENCES acq.provider (id),
13250     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13251     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13252     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13253     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13254     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13255         payment_auth   TEXT,
13256         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13257                                    DEFERRABLE INITIALLY DEFERRED,
13258         note           TEXT,
13259     complete       BOOL        NOT NULL DEFAULT FALSE,
13260     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13261 );
13262
13263 CREATE TABLE acq.invoice_entry (
13264     id              SERIAL      PRIMARY KEY,
13265     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13266     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13267     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13268     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13269     phys_item_count INT, -- and how many did staff count
13270     note            TEXT,
13271     billed_per_item BOOL,
13272     cost_billed     NUMERIC(8,2),
13273     actual_cost     NUMERIC(8,2),
13274         amount_paid     NUMERIC (8,2)
13275 );
13276
13277 CREATE TABLE acq.invoice_item_type (
13278     code    TEXT    PRIMARY KEY,
13279     name    TEXT    NOT NULL, -- i18n-ize
13280         prorate BOOL    NOT NULL DEFAULT FALSE
13281 );
13282
13283 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13284 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13285 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13286 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13287 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13288 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13289
13290 CREATE TABLE acq.po_item (
13291         id              SERIAL      PRIMARY KEY,
13292         purchase_order  INT         REFERENCES acq.purchase_order (id)
13293                                     ON UPDATE CASCADE ON DELETE SET NULL
13294                                     DEFERRABLE INITIALLY DEFERRED,
13295         fund_debit      INT         REFERENCES acq.fund_debit (id)
13296                                     DEFERRABLE INITIALLY DEFERRED,
13297         inv_item_type   TEXT        NOT NULL
13298                                     REFERENCES acq.invoice_item_type (code)
13299                                     DEFERRABLE INITIALLY DEFERRED,
13300         title           TEXT,
13301         author          TEXT,
13302         note            TEXT,
13303         estimated_cost  NUMERIC(8,2),
13304         fund            INT         REFERENCES acq.fund (id)
13305                                     DEFERRABLE INITIALLY DEFERRED,
13306         target          BIGINT
13307 );
13308
13309 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13310     id              SERIAL      PRIMARY KEY,
13311     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13312     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13313     fund_debit      INT         REFERENCES acq.fund_debit (id),
13314     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13315     title           TEXT,
13316     author          TEXT,
13317     note            TEXT,
13318     cost_billed     NUMERIC(8,2),
13319     actual_cost     NUMERIC(8,2),
13320     fund            INT         REFERENCES acq.fund (id)
13321                                 DEFERRABLE INITIALLY DEFERRED,
13322     amount_paid     NUMERIC (8,2),
13323     po_item         INT         REFERENCES acq.po_item (id)
13324                                 DEFERRABLE INITIALLY DEFERRED,
13325     target          BIGINT
13326 );
13327
13328 CREATE TABLE acq.edi_message (
13329     id               SERIAL          PRIMARY KEY,
13330     account          INTEGER         REFERENCES acq.edi_account(id)
13331                                      DEFERRABLE INITIALLY DEFERRED,
13332     remote_file      TEXT,
13333     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13334     translate_time   TIMESTAMPTZ,
13335     process_time     TIMESTAMPTZ,
13336     error_time       TIMESTAMPTZ,
13337     status           TEXT            NOT NULL DEFAULT 'new'
13338                                      CONSTRAINT status_value CHECK
13339                                      ( status IN (
13340                                         'new',          -- needs to be translated
13341                                         'translated',   -- needs to be processed
13342                                         'trans_error',  -- error in translation step
13343                                         'processed',    -- needs to have remote_file deleted
13344                                         'proc_error',   -- error in processing step
13345                                         'delete_error', -- error in deletion
13346                                         'retry',        -- need to retry
13347                                         'complete'      -- done
13348                                      )),
13349     edi              TEXT,
13350     jedi             TEXT,
13351     error            TEXT,
13352     purchase_order   INT             REFERENCES acq.purchase_order
13353                                      DEFERRABLE INITIALLY DEFERRED,
13354     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13355                                      CHECK ( message_type IN (
13356                                         'ORDERS',
13357                                         'ORDRSP',
13358                                         'INVOIC',
13359                                         'OSTENQ',
13360                                         'OSTRPT'
13361                                      ))
13362 );
13363
13364 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13365
13366 ALTER TABLE acq.provider_address
13367         ADD COLUMN fax_phone TEXT;
13368
13369 ALTER TABLE acq.provider_contact_address
13370         ADD COLUMN fax_phone TEXT;
13371
13372 CREATE TABLE acq.provider_note (
13373     id      SERIAL              PRIMARY KEY,
13374     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13375     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13376     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13377     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13378     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13379     value       TEXT            NOT NULL
13380 );
13381 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13382 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13383 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13384
13385 -- For each fund: the total allocation from all sources, in the
13386 -- currency of the fund (or 0 if there are no allocations)
13387
13388 CREATE VIEW acq.all_fund_allocation_total AS
13389 SELECT
13390     f.id AS fund,
13391     COALESCE( SUM( a.amount * acq.exchange_ratio(
13392         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13393     AS amount
13394 FROM
13395     acq.fund f
13396         LEFT JOIN acq.fund_allocation a
13397             ON a.fund = f.id
13398         LEFT JOIN acq.funding_source s
13399             ON a.funding_source = s.id
13400 GROUP BY
13401     f.id;
13402
13403 -- For every fund: the total encumbrances (or 0 if none),
13404 -- in the currency of the fund.
13405
13406 CREATE VIEW acq.all_fund_encumbrance_total AS
13407 SELECT
13408         f.id AS fund,
13409         COALESCE( encumb.amount, 0 ) AS amount
13410 FROM
13411         acq.fund AS f
13412                 LEFT JOIN (
13413                         SELECT
13414                                 fund,
13415                                 sum( amount ) AS amount
13416                         FROM
13417                                 acq.fund_debit
13418                         WHERE
13419                                 encumbrance
13420                         GROUP BY fund
13421                 ) AS encumb
13422                         ON f.id = encumb.fund;
13423
13424 -- For every fund: the total spent (or 0 if none),
13425 -- in the currency of the fund.
13426
13427 CREATE VIEW acq.all_fund_spent_total AS
13428 SELECT
13429     f.id AS fund,
13430     COALESCE( spent.amount, 0 ) AS amount
13431 FROM
13432     acq.fund AS f
13433         LEFT JOIN (
13434             SELECT
13435                 fund,
13436                 sum( amount ) AS amount
13437             FROM
13438                 acq.fund_debit
13439             WHERE
13440                 NOT encumbrance
13441             GROUP BY fund
13442         ) AS spent
13443             ON f.id = spent.fund;
13444
13445 -- For each fund: the amount not yet spent, in the currency
13446 -- of the fund.  May include encumbrances.
13447
13448 CREATE VIEW acq.all_fund_spent_balance AS
13449 SELECT
13450         c.fund,
13451         c.amount - d.amount AS amount
13452 FROM acq.all_fund_allocation_total c
13453     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13454
13455 -- For each fund: the amount neither spent nor encumbered,
13456 -- in the currency of the fund
13457
13458 CREATE VIEW acq.all_fund_combined_balance AS
13459 SELECT
13460      a.fund,
13461      a.amount - COALESCE( c.amount, 0 ) AS amount
13462 FROM
13463      acq.all_fund_allocation_total a
13464         LEFT OUTER JOIN (
13465             SELECT
13466                 fund,
13467                 SUM( amount ) AS amount
13468             FROM
13469                 acq.fund_debit
13470             GROUP BY
13471                 fund
13472         ) AS c USING ( fund );
13473
13474 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 $$
13475 DECLARE
13476         suffix TEXT;
13477         bucket_row RECORD;
13478         picklist_row RECORD;
13479         queue_row RECORD;
13480         folder_row RECORD;
13481 BEGIN
13482
13483     -- do some initial cleanup 
13484     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13485     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13486     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13487
13488     -- actor.*
13489     IF del_cards THEN
13490         DELETE FROM actor.card where usr = src_usr;
13491     ELSE
13492         IF deactivate_cards THEN
13493             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13494         END IF;
13495         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13496     END IF;
13497
13498
13499     IF del_addrs THEN
13500         DELETE FROM actor.usr_address WHERE usr = src_usr;
13501     ELSE
13502         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13503     END IF;
13504
13505     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13506     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13507     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13508     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13509     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13510
13511     -- permission.*
13512     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13513     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13514     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13515     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13516
13517
13518     -- container.*
13519         
13520         -- For each *_bucket table: transfer every bucket belonging to src_usr
13521         -- into the custody of dest_usr.
13522         --
13523         -- In order to avoid colliding with an existing bucket owned by
13524         -- the destination user, append the source user's id (in parenthesese)
13525         -- to the name.  If you still get a collision, add successive
13526         -- spaces to the name and keep trying until you succeed.
13527         --
13528         FOR bucket_row in
13529                 SELECT id, name
13530                 FROM   container.biblio_record_entry_bucket
13531                 WHERE  owner = src_usr
13532         LOOP
13533                 suffix := ' (' || src_usr || ')';
13534                 LOOP
13535                         BEGIN
13536                                 UPDATE  container.biblio_record_entry_bucket
13537                                 SET     owner = dest_usr, name = name || suffix
13538                                 WHERE   id = bucket_row.id;
13539                         EXCEPTION WHEN unique_violation THEN
13540                                 suffix := suffix || ' ';
13541                                 CONTINUE;
13542                         END;
13543                         EXIT;
13544                 END LOOP;
13545         END LOOP;
13546
13547         FOR bucket_row in
13548                 SELECT id, name
13549                 FROM   container.call_number_bucket
13550                 WHERE  owner = src_usr
13551         LOOP
13552                 suffix := ' (' || src_usr || ')';
13553                 LOOP
13554                         BEGIN
13555                                 UPDATE  container.call_number_bucket
13556                                 SET     owner = dest_usr, name = name || suffix
13557                                 WHERE   id = bucket_row.id;
13558                         EXCEPTION WHEN unique_violation THEN
13559                                 suffix := suffix || ' ';
13560                                 CONTINUE;
13561                         END;
13562                         EXIT;
13563                 END LOOP;
13564         END LOOP;
13565
13566         FOR bucket_row in
13567                 SELECT id, name
13568                 FROM   container.copy_bucket
13569                 WHERE  owner = src_usr
13570         LOOP
13571                 suffix := ' (' || src_usr || ')';
13572                 LOOP
13573                         BEGIN
13574                                 UPDATE  container.copy_bucket
13575                                 SET     owner = dest_usr, name = name || suffix
13576                                 WHERE   id = bucket_row.id;
13577                         EXCEPTION WHEN unique_violation THEN
13578                                 suffix := suffix || ' ';
13579                                 CONTINUE;
13580                         END;
13581                         EXIT;
13582                 END LOOP;
13583         END LOOP;
13584
13585         FOR bucket_row in
13586                 SELECT id, name
13587                 FROM   container.user_bucket
13588                 WHERE  owner = src_usr
13589         LOOP
13590                 suffix := ' (' || src_usr || ')';
13591                 LOOP
13592                         BEGIN
13593                                 UPDATE  container.user_bucket
13594                                 SET     owner = dest_usr, name = name || suffix
13595                                 WHERE   id = bucket_row.id;
13596                         EXCEPTION WHEN unique_violation THEN
13597                                 suffix := suffix || ' ';
13598                                 CONTINUE;
13599                         END;
13600                         EXIT;
13601                 END LOOP;
13602         END LOOP;
13603
13604         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13605
13606     -- vandelay.*
13607         -- transfer queues the same way we transfer buckets (see above)
13608         FOR queue_row in
13609                 SELECT id, name
13610                 FROM   vandelay.queue
13611                 WHERE  owner = src_usr
13612         LOOP
13613                 suffix := ' (' || src_usr || ')';
13614                 LOOP
13615                         BEGIN
13616                                 UPDATE  vandelay.queue
13617                                 SET     owner = dest_usr, name = name || suffix
13618                                 WHERE   id = queue_row.id;
13619                         EXCEPTION WHEN unique_violation THEN
13620                                 suffix := suffix || ' ';
13621                                 CONTINUE;
13622                         END;
13623                         EXIT;
13624                 END LOOP;
13625         END LOOP;
13626
13627     -- money.*
13628     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13629     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13630     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13631     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13632     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13633
13634     -- action.*
13635     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13636     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13637     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13638
13639     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13640     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13641     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13642     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13643
13644     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13645     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13646     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13647     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13648     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13649
13650     -- acq.*
13651     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13652         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13653
13654         -- transfer picklists the same way we transfer buckets (see above)
13655         FOR picklist_row in
13656                 SELECT id, name
13657                 FROM   acq.picklist
13658                 WHERE  owner = src_usr
13659         LOOP
13660                 suffix := ' (' || src_usr || ')';
13661                 LOOP
13662                         BEGIN
13663                                 UPDATE  acq.picklist
13664                                 SET     owner = dest_usr, name = name || suffix
13665                                 WHERE   id = picklist_row.id;
13666                         EXCEPTION WHEN unique_violation THEN
13667                                 suffix := suffix || ' ';
13668                                 CONTINUE;
13669                         END;
13670                         EXIT;
13671                 END LOOP;
13672         END LOOP;
13673
13674     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13675     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13676     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13677     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13678     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13679     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13680     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13681     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13682
13683     -- asset.*
13684     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13685     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13686     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13687     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13688     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13689     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13690
13691     -- serial.*
13692     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13693     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13694
13695     -- reporter.*
13696     -- It's not uncommon to define the reporter schema in a replica 
13697     -- DB only, so don't assume these tables exist in the write DB.
13698     BEGIN
13699         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13700     EXCEPTION WHEN undefined_table THEN
13701         -- do nothing
13702     END;
13703     BEGIN
13704         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13705     EXCEPTION WHEN undefined_table THEN
13706         -- do nothing
13707     END;
13708     BEGIN
13709         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13710     EXCEPTION WHEN undefined_table THEN
13711         -- do nothing
13712     END;
13713     BEGIN
13714                 -- transfer folders the same way we transfer buckets (see above)
13715                 FOR folder_row in
13716                         SELECT id, name
13717                         FROM   reporter.template_folder
13718                         WHERE  owner = src_usr
13719                 LOOP
13720                         suffix := ' (' || src_usr || ')';
13721                         LOOP
13722                                 BEGIN
13723                                         UPDATE  reporter.template_folder
13724                                         SET     owner = dest_usr, name = name || suffix
13725                                         WHERE   id = folder_row.id;
13726                                 EXCEPTION WHEN unique_violation THEN
13727                                         suffix := suffix || ' ';
13728                                         CONTINUE;
13729                                 END;
13730                                 EXIT;
13731                         END LOOP;
13732                 END LOOP;
13733     EXCEPTION WHEN undefined_table THEN
13734         -- do nothing
13735     END;
13736     BEGIN
13737                 -- transfer folders the same way we transfer buckets (see above)
13738                 FOR folder_row in
13739                         SELECT id, name
13740                         FROM   reporter.report_folder
13741                         WHERE  owner = src_usr
13742                 LOOP
13743                         suffix := ' (' || src_usr || ')';
13744                         LOOP
13745                                 BEGIN
13746                                         UPDATE  reporter.report_folder
13747                                         SET     owner = dest_usr, name = name || suffix
13748                                         WHERE   id = folder_row.id;
13749                                 EXCEPTION WHEN unique_violation THEN
13750                                         suffix := suffix || ' ';
13751                                         CONTINUE;
13752                                 END;
13753                                 EXIT;
13754                         END LOOP;
13755                 END LOOP;
13756     EXCEPTION WHEN undefined_table THEN
13757         -- do nothing
13758     END;
13759     BEGIN
13760                 -- transfer folders the same way we transfer buckets (see above)
13761                 FOR folder_row in
13762                         SELECT id, name
13763                         FROM   reporter.output_folder
13764                         WHERE  owner = src_usr
13765                 LOOP
13766                         suffix := ' (' || src_usr || ')';
13767                         LOOP
13768                                 BEGIN
13769                                         UPDATE  reporter.output_folder
13770                                         SET     owner = dest_usr, name = name || suffix
13771                                         WHERE   id = folder_row.id;
13772                                 EXCEPTION WHEN unique_violation THEN
13773                                         suffix := suffix || ' ';
13774                                         CONTINUE;
13775                                 END;
13776                                 EXIT;
13777                         END LOOP;
13778                 END LOOP;
13779     EXCEPTION WHEN undefined_table THEN
13780         -- do nothing
13781     END;
13782
13783     -- Finally, delete the source user
13784     DELETE FROM actor.usr WHERE id = src_usr;
13785
13786 END;
13787 $$ LANGUAGE plpgsql;
13788
13789 -- The "add" trigger functions should protect against existing NULLed values, just in case
13790 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13791 BEGIN
13792     IF NOT NEW.voided THEN
13793         UPDATE  money.materialized_billable_xact_summary
13794           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13795             last_billing_ts = NEW.billing_ts,
13796             last_billing_note = NEW.note,
13797             last_billing_type = NEW.billing_type,
13798             balance_owed = balance_owed + NEW.amount
13799           WHERE id = NEW.xact;
13800     END IF;
13801
13802     RETURN NEW;
13803 END;
13804 $$ LANGUAGE PLPGSQL;
13805
13806 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13807 BEGIN
13808     IF NOT NEW.voided THEN
13809         UPDATE  money.materialized_billable_xact_summary
13810           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13811             last_payment_ts = NEW.payment_ts,
13812             last_payment_note = NEW.note,
13813             last_payment_type = TG_ARGV[0],
13814             balance_owed = balance_owed - NEW.amount
13815           WHERE id = NEW.xact;
13816     END IF;
13817
13818     RETURN NEW;
13819 END;
13820 $$ LANGUAGE PLPGSQL;
13821
13822 -- Refresh the mat view with the corrected underlying view
13823 TRUNCATE money.materialized_billable_xact_summary;
13824 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13825
13826 -- Now redefine the view as a window onto the materialized view
13827 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13828     SELECT * FROM money.materialized_billable_xact_summary;
13829
13830 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13831     user_id    IN INTEGER,
13832     perm_code  IN TEXT
13833 )
13834 RETURNS SETOF INTEGER AS $$
13835 --
13836 -- Return a set of all the org units for which a given user has a given
13837 -- permission, granted directly (not through inheritance from a parent
13838 -- org unit).
13839 --
13840 -- The permissions apply to a minimum depth of the org unit hierarchy,
13841 -- for the org unit(s) to which the user is assigned.  (They also apply
13842 -- to the subordinates of those org units, but we don't report the
13843 -- subordinates here.)
13844 --
13845 -- For purposes of this function, the permission.usr_work_ou_map table
13846 -- defines which users belong to which org units.  I.e. we ignore the
13847 -- home_ou column of actor.usr.
13848 --
13849 -- The result set may contain duplicates, which should be eliminated
13850 -- by a DISTINCT clause.
13851 --
13852 DECLARE
13853     b_super       BOOLEAN;
13854     n_perm        INTEGER;
13855     n_min_depth   INTEGER;
13856     n_work_ou     INTEGER;
13857     n_curr_ou     INTEGER;
13858     n_depth       INTEGER;
13859     n_curr_depth  INTEGER;
13860 BEGIN
13861     --
13862     -- Check for superuser
13863     --
13864     SELECT INTO b_super
13865         super_user
13866     FROM
13867         actor.usr
13868     WHERE
13869         id = user_id;
13870     --
13871     IF NOT FOUND THEN
13872         return;             -- No user?  No permissions.
13873     ELSIF b_super THEN
13874         --
13875         -- Super user has all permissions everywhere
13876         --
13877         FOR n_work_ou IN
13878             SELECT
13879                 id
13880             FROM
13881                 actor.org_unit
13882             WHERE
13883                 parent_ou IS NULL
13884         LOOP
13885             RETURN NEXT n_work_ou;
13886         END LOOP;
13887         RETURN;
13888     END IF;
13889     --
13890     -- Translate the permission name
13891     -- to a numeric permission id
13892     --
13893     SELECT INTO n_perm
13894         id
13895     FROM
13896         permission.perm_list
13897     WHERE
13898         code = perm_code;
13899     --
13900     IF NOT FOUND THEN
13901         RETURN;               -- No such permission
13902     END IF;
13903     --
13904     -- Find the highest-level org unit (i.e. the minimum depth)
13905     -- to which the permission is applied for this user
13906     --
13907     -- This query is modified from the one in permission.usr_perms().
13908     --
13909     SELECT INTO n_min_depth
13910         min( depth )
13911     FROM    (
13912         SELECT depth
13913           FROM permission.usr_perm_map upm
13914          WHERE upm.usr = user_id
13915            AND (upm.perm = n_perm OR upm.perm = -1)
13916                     UNION
13917         SELECT  gpm.depth
13918           FROM  permission.grp_perm_map gpm
13919           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13920             AND gpm.grp IN (
13921                SELECT   (permission.grp_ancestors(
13922                     (SELECT profile FROM actor.usr WHERE id = user_id)
13923                 )).id
13924             )
13925                     UNION
13926         SELECT  p.depth
13927           FROM  permission.grp_perm_map p
13928           WHERE (p.perm = n_perm OR p.perm = -1)
13929             AND p.grp IN (
13930                 SELECT (permission.grp_ancestors(m.grp)).id
13931                 FROM   permission.usr_grp_map m
13932                 WHERE  m.usr = user_id
13933             )
13934     ) AS x;
13935     --
13936     IF NOT FOUND THEN
13937         RETURN;                -- No such permission for this user
13938     END IF;
13939     --
13940     -- Identify the org units to which the user is assigned.  Note that
13941     -- we pay no attention to the home_ou column in actor.usr.
13942     --
13943     FOR n_work_ou IN
13944         SELECT
13945             work_ou
13946         FROM
13947             permission.usr_work_ou_map
13948         WHERE
13949             usr = user_id
13950     LOOP            -- For each org unit to which the user is assigned
13951         --
13952         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13953         -- We take it on faith that this depth agrees with the actual hierarchy
13954         -- defined in actor.org_unit.
13955         --
13956         SELECT INTO n_depth
13957             type.depth
13958         FROM
13959             actor.org_unit_type type
13960                 INNER JOIN actor.org_unit ou
13961                     ON ( ou.ou_type = type.id )
13962         WHERE
13963             ou.id = n_work_ou;
13964         --
13965         IF NOT FOUND THEN
13966             CONTINUE;        -- Maybe raise exception?
13967         END IF;
13968         --
13969         -- Compare the depth of the work org unit to the
13970         -- minimum depth, and branch accordingly
13971         --
13972         IF n_depth = n_min_depth THEN
13973             --
13974             -- The org unit is at the right depth, so return it.
13975             --
13976             RETURN NEXT n_work_ou;
13977         ELSIF n_depth > n_min_depth THEN
13978             --
13979             -- Traverse the org unit tree toward the root,
13980             -- until you reach the minimum depth determined above
13981             --
13982             n_curr_depth := n_depth;
13983             n_curr_ou := n_work_ou;
13984             WHILE n_curr_depth > n_min_depth LOOP
13985                 SELECT INTO n_curr_ou
13986                     parent_ou
13987                 FROM
13988                     actor.org_unit
13989                 WHERE
13990                     id = n_curr_ou;
13991                 --
13992                 IF FOUND THEN
13993                     n_curr_depth := n_curr_depth - 1;
13994                 ELSE
13995                     --
13996                     -- This can happen only if the hierarchy defined in
13997                     -- actor.org_unit is corrupted, or out of sync with
13998                     -- the depths defined in actor.org_unit_type.
13999                     -- Maybe we should raise an exception here, instead
14000                     -- of silently ignoring the problem.
14001                     --
14002                     n_curr_ou = NULL;
14003                     EXIT;
14004                 END IF;
14005             END LOOP;
14006             --
14007             IF n_curr_ou IS NOT NULL THEN
14008                 RETURN NEXT n_curr_ou;
14009             END IF;
14010         ELSE
14011             --
14012             -- The permission applies only at a depth greater than the work org unit.
14013             -- Use connectby() to find all dependent org units at the specified depth.
14014             --
14015             FOR n_curr_ou IN
14016                 SELECT ou::INTEGER
14017                 FROM connectby(
14018                         'actor.org_unit',         -- table name
14019                         'id',                     -- key column
14020                         'parent_ou',              -- recursive foreign key
14021                         n_work_ou::TEXT,          -- id of starting point
14022                         (n_min_depth - n_depth)   -- max depth to search, relative
14023                     )                             --   to starting point
14024                     AS t(
14025                         ou text,            -- dependent org unit
14026                         parent_ou text,     -- (ignore)
14027                         level int           -- depth relative to starting point
14028                     )
14029                 WHERE
14030                     level = n_min_depth - n_depth
14031             LOOP
14032                 RETURN NEXT n_curr_ou;
14033             END LOOP;
14034         END IF;
14035         --
14036     END LOOP;
14037     --
14038     RETURN;
14039     --
14040 END;
14041 $$ LANGUAGE 'plpgsql';
14042
14043 ALTER TABLE acq.purchase_order
14044         ADD COLUMN cancel_reason INT
14045                 REFERENCES acq.cancel_reason( id )
14046             DEFERRABLE INITIALLY DEFERRED,
14047         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
14048
14049 -- Build the history table and lifecycle view
14050 -- for acq.purchase_order
14051
14052 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
14053
14054 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
14055
14056 ALTER TABLE acq.lineitem
14057         ADD COLUMN cancel_reason INT
14058                 REFERENCES acq.cancel_reason( id )
14059             DEFERRABLE INITIALLY DEFERRED,
14060         ADD COLUMN estimated_unit_price NUMERIC,
14061         ADD COLUMN claim_policy INT
14062                 REFERENCES acq.claim_policy
14063                 DEFERRABLE INITIALLY DEFERRED,
14064         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
14065
14066 -- Build the history table and lifecycle view
14067 -- for acq.lineitem
14068
14069 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
14070 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
14071
14072 ALTER TABLE acq.lineitem_detail
14073         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
14074                                             DEFERRABLE INITIALLY DEFERRED;
14075
14076 ALTER TABLE acq.lineitem_detail
14077         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
14078
14079 ALTER TABLE acq.lineitem_detail
14080         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14081                 ON DELETE CASCADE
14082                 DEFERRABLE INITIALLY DEFERRED;
14083
14084 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
14085
14086 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14087         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14088
14089 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14090         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14091
14092 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
14093
14094     use MARC::Record;
14095     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14096     use strict;
14097
14098     my $target_xml = shift;
14099     my $source_xml = shift;
14100     my $field_spec = shift;
14101     my $force_add = shift || 0;
14102
14103     my $target_r = MARC::Record->new_from_xml( $target_xml );
14104     my $source_r = MARC::Record->new_from_xml( $source_xml );
14105
14106     return $target_xml unless ($target_r && $source_r);
14107
14108     my @field_list = split(',', $field_spec);
14109
14110     my %fields;
14111     for my $f (@field_list) {
14112         $f =~ s/^\s*//; $f =~ s/\s*$//;
14113         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14114             my $field = $1;
14115             $field =~ s/\s+//;
14116             my $sf = $2;
14117             $sf =~ s/\s+//;
14118             my $match = $3;
14119             $match =~ s/^\s*//; $match =~ s/\s*$//;
14120             $fields{$field} = { sf => [ split('', $sf) ] };
14121             if ($match) {
14122                 my ($msf,$mre) = split('~', $match);
14123                 if (length($msf) > 0 and length($mre) > 0) {
14124                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14125                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14126                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14127                 }
14128             }
14129         }
14130     }
14131
14132     for my $f ( keys %fields) {
14133         if ( @{$fields{$f}{sf}} ) {
14134             for my $from_field ($source_r->field( $f )) {
14135                 my @tos = $target_r->field( $f );
14136                 if (!@tos) {
14137                     next if (exists($fields{$f}{match}) and !$force_add);
14138                     my @new_fields = map { $_->clone } $source_r->field( $f );
14139                     $target_r->insert_fields_ordered( @new_fields );
14140                 } else {
14141                     for my $to_field (@tos) {
14142                         if (exists($fields{$f}{match})) {
14143                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14144                         }
14145                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14146                         $to_field->add_subfields( @new_sf );
14147                     }
14148                 }
14149             }
14150         } else {
14151             my @new_fields = map { $_->clone } $source_r->field( $f );
14152             $target_r->insert_fields_ordered( @new_fields );
14153         }
14154     }
14155
14156     $target_xml = $target_r->as_xml_record;
14157     $target_xml =~ s/^<\?.+?\?>$//mo;
14158     $target_xml =~ s/\n//sgo;
14159     $target_xml =~ s/>\s+</></sgo;
14160
14161     return $target_xml;
14162
14163 $_$ LANGUAGE PLPERLU;
14164
14165 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14166     SELECT vandelay.add_field( $1, $2, $3, 0 );
14167 $_$ LANGUAGE SQL;
14168
14169 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14170
14171     use MARC::Record;
14172     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14173     use strict;
14174
14175     my $xml = shift;
14176     my $r = MARC::Record->new_from_xml( $xml );
14177
14178     return $xml unless ($r);
14179
14180     my $field_spec = shift;
14181     my @field_list = split(',', $field_spec);
14182
14183     my %fields;
14184     for my $f (@field_list) {
14185         $f =~ s/^\s*//; $f =~ s/\s*$//;
14186         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14187             my $field = $1;
14188             $field =~ s/\s+//;
14189             my $sf = $2;
14190             $sf =~ s/\s+//;
14191             my $match = $3;
14192             $match =~ s/^\s*//; $match =~ s/\s*$//;
14193             $fields{$field} = { sf => [ split('', $sf) ] };
14194             if ($match) {
14195                 my ($msf,$mre) = split('~', $match);
14196                 if (length($msf) > 0 and length($mre) > 0) {
14197                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14198                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14199                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14200                 }
14201             }
14202         }
14203     }
14204
14205     for my $f ( keys %fields) {
14206         for my $to_field ($r->field( $f )) {
14207             if (exists($fields{$f}{match})) {
14208                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14209             }
14210
14211             if ( @{$fields{$f}{sf}} ) {
14212                 $to_field->delete_subfield(code => $fields{$f}{sf});
14213             } else {
14214                 $r->delete_field( $to_field );
14215             }
14216         }
14217     }
14218
14219     $xml = $r->as_xml_record;
14220     $xml =~ s/^<\?.+?\?>$//mo;
14221     $xml =~ s/\n//sgo;
14222     $xml =~ s/>\s+</></sgo;
14223
14224     return $xml;
14225
14226 $_$ LANGUAGE PLPERLU;
14227
14228 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14229 DECLARE
14230     xml_output TEXT;
14231     parsed_target TEXT;
14232     curr_field TEXT;
14233 BEGIN
14234
14235     parsed_target := vandelay.strip_field( target_xml, ''); -- this dance normalizes the format of the xml for the IF below
14236
14237     FOR curr_field IN SELECT UNNEST( STRING_TO_ARRAY(field, ',') ) LOOP -- naive split, but it's the same we use in the perl
14238
14239         xml_output := vandelay.strip_field( parsed_target, curr_field);
14240
14241         IF xml_output <> parsed_target  AND curr_field ~ E'~' THEN
14242             -- we removed something, and there was a regexp restriction in the curr_field definition, so proceed
14243             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 1 );
14244         ELSIF curr_field !~ E'~' THEN
14245             -- No regexp restriction, add the curr_field
14246             xml_output := vandelay.add_field( xml_output, source_xml, curr_field, 0 );
14247         END IF;
14248
14249         parsed_target := xml_output; -- in prep for any following loop iterations
14250
14251     END LOOP;
14252
14253     RETURN xml_output;
14254 END;
14255 $_$ LANGUAGE PLPGSQL;
14256
14257 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14258     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14259 $_$ LANGUAGE SQL;
14260
14261 CREATE VIEW action.unfulfilled_hold_max_loop AS
14262         SELECT  hold,
14263                 max(count) AS max
14264         FROM    action.unfulfilled_hold_loops
14265         GROUP BY 1;
14266
14267 ALTER TABLE acq.lineitem_attr
14268         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14269
14270 ALTER TABLE acq.lineitem_attr
14271         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14272                 ON DELETE CASCADE
14273                 DEFERRABLE INITIALLY DEFERRED;
14274
14275 ALTER TABLE acq.po_note
14276         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14277
14278 CREATE TABLE vandelay.merge_profile (
14279     id              BIGSERIAL   PRIMARY KEY,
14280     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14281     name            TEXT        NOT NULL,
14282     add_spec        TEXT,
14283     replace_spec    TEXT,
14284     strip_spec      TEXT,
14285     preserve_spec   TEXT,
14286     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14287     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))
14288 );
14289
14290 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14291 DECLARE
14292     attr        RECORD;
14293     attr_def    RECORD;
14294     eg_rec      RECORD;
14295     id_value    TEXT;
14296     exact_id    BIGINT;
14297 BEGIN
14298
14299     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14300
14301     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14302
14303     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14304         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14305
14306         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14307             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14308             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14309             IF exact_id IS NOT NULL THEN
14310                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14311             END IF;
14312         END IF;
14313     END IF;
14314
14315     IF exact_id IS NULL THEN
14316         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
14317
14318             -- All numbers? check for an id match
14319             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14320                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14321                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14322                 END LOOP;
14323             END IF;
14324
14325             -- Looks like an ISBN? check for an isbn match
14326             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14327                 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
14328                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14329                     IF FOUND THEN
14330                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14331                     END IF;
14332                 END LOOP;
14333
14334                 -- subcheck for isbn-as-tcn
14335                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14336                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14337                 END LOOP;
14338             END IF;
14339
14340             -- check for an OCLC tcn_value match
14341             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14342                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14343                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14344                 END LOOP;
14345             END IF;
14346
14347             -- check for a direct tcn_value match
14348             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14349                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14350             END LOOP;
14351
14352             -- check for a direct item barcode match
14353             FOR eg_rec IN
14354                     SELECT  DISTINCT b.*
14355                       FROM  biblio.record_entry b
14356                             JOIN asset.call_number cn ON (cn.record = b.id)
14357                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14358                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14359             LOOP
14360                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14361             END LOOP;
14362
14363         END LOOP;
14364     END IF;
14365
14366     RETURN NULL;
14367 END;
14368 $func$ LANGUAGE PLPGSQL;
14369
14370 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 $_$
14371     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14372 $_$ LANGUAGE SQL;
14373
14374 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14375 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14376 DECLARE
14377     output              vandelay.compile_profile%ROWTYPE;
14378     profile             vandelay.merge_profile%ROWTYPE;
14379     profile_tmpl        TEXT;
14380     profile_tmpl_owner  TEXT;
14381     add_rule            TEXT := '';
14382     strip_rule          TEXT := '';
14383     replace_rule        TEXT := '';
14384     preserve_rule       TEXT := '';
14385
14386 BEGIN
14387
14388     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14389     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14390
14391     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14392         SELECT  p.* INTO profile
14393           FROM  vandelay.merge_profile p
14394                 JOIN actor.org_unit u ON (u.id = p.owner)
14395           WHERE p.name = profile_tmpl
14396                 AND u.shortname = profile_tmpl_owner;
14397
14398         IF profile.id IS NOT NULL THEN
14399             add_rule := COALESCE(profile.add_spec,'');
14400             strip_rule := COALESCE(profile.strip_spec,'');
14401             replace_rule := COALESCE(profile.replace_spec,'');
14402             preserve_rule := COALESCE(profile.preserve_spec,'');
14403         END IF;
14404     END IF;
14405
14406     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14407     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14408     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14409     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14410
14411     output.add_rule := BTRIM(add_rule,',');
14412     output.replace_rule := BTRIM(replace_rule,',');
14413     output.strip_rule := BTRIM(strip_rule,',');
14414     output.preserve_rule := BTRIM(preserve_rule,',');
14415
14416     RETURN output;
14417 END;
14418 $_$ LANGUAGE PLPGSQL;
14419
14420 -- Template-based marc munging functions
14421 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14422 DECLARE
14423     merge_profile   vandelay.merge_profile%ROWTYPE;
14424     dyn_profile     vandelay.compile_profile%ROWTYPE;
14425     editor_string   TEXT;
14426     editor_id       INT;
14427     source_marc     TEXT;
14428     target_marc     TEXT;
14429     eg_marc         TEXT;
14430     replace_rule    TEXT;
14431     match_count     INT;
14432 BEGIN
14433
14434     SELECT  b.marc INTO eg_marc
14435       FROM  biblio.record_entry b
14436       WHERE b.id = eg_id
14437       LIMIT 1;
14438
14439     IF eg_marc IS NULL OR v_marc IS NULL THEN
14440         -- RAISE NOTICE 'no marc for template or bib record';
14441         RETURN FALSE;
14442     END IF;
14443
14444     dyn_profile := vandelay.compile_profile( v_marc );
14445
14446     IF merge_profile_id IS NOT NULL THEN
14447         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14448         IF FOUND THEN
14449             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14450             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14451             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14452             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14453         END IF;
14454     END IF;
14455
14456     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14457         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14458         RETURN FALSE;
14459     END IF;
14460
14461     IF dyn_profile.replace_rule <> '' THEN
14462         source_marc = v_marc;
14463         target_marc = eg_marc;
14464         replace_rule = dyn_profile.replace_rule;
14465     ELSE
14466         source_marc = eg_marc;
14467         target_marc = v_marc;
14468         replace_rule = dyn_profile.preserve_rule;
14469     END IF;
14470
14471     UPDATE  biblio.record_entry
14472       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14473       WHERE id = eg_id;
14474
14475     IF NOT FOUND THEN
14476         -- RAISE NOTICE 'update of biblio.record_entry failed';
14477         RETURN FALSE;
14478     END IF;
14479
14480     RETURN TRUE;
14481
14482 END;
14483 $$ LANGUAGE PLPGSQL;
14484
14485 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14486     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14487 $$ LANGUAGE SQL;
14488
14489 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14490 DECLARE
14491     merge_profile   vandelay.merge_profile%ROWTYPE;
14492     dyn_profile     vandelay.compile_profile%ROWTYPE;
14493     editor_string   TEXT;
14494     editor_id       INT;
14495     source_marc     TEXT;
14496     target_marc     TEXT;
14497     eg_marc         TEXT;
14498     v_marc          TEXT;
14499     replace_rule    TEXT;
14500     match_count     INT;
14501 BEGIN
14502
14503     SELECT  q.marc INTO v_marc
14504       FROM  vandelay.queued_record q
14505             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14506       LIMIT 1;
14507
14508     IF v_marc IS NULL THEN
14509         -- RAISE NOTICE 'no marc for vandelay or bib record';
14510         RETURN FALSE;
14511     END IF;
14512
14513     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14514         UPDATE  vandelay.queued_bib_record
14515           SET   imported_as = eg_id,
14516                 import_time = NOW()
14517           WHERE id = import_id;
14518
14519         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14520
14521         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14522             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14523
14524             IF editor_id IS NULL THEN
14525                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14526             END IF;
14527
14528             IF editor_id IS NOT NULL THEN
14529                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14530             END IF;
14531         END IF;
14532
14533         RETURN TRUE;
14534     END IF;
14535
14536     -- RAISE NOTICE 'update of biblio.record_entry failed';
14537
14538     RETURN FALSE;
14539
14540 END;
14541 $$ LANGUAGE PLPGSQL;
14542
14543 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14544 DECLARE
14545     eg_id           BIGINT;
14546     match_count     INT;
14547     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14548 BEGIN
14549
14550     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14551
14552     IF FOUND THEN
14553         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14554         RETURN FALSE;
14555     END IF;
14556
14557     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14558
14559     IF match_count <> 1 THEN
14560         -- RAISE NOTICE 'not an exact match';
14561         RETURN FALSE;
14562     END IF;
14563
14564     SELECT  d.* INTO match_attr
14565       FROM  vandelay.bib_attr_definition d
14566             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14567             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14568       WHERE m.queued_record = import_id;
14569
14570     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14571         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14572         RETURN FALSE;
14573     END IF;
14574
14575     SELECT  m.eg_record INTO eg_id
14576       FROM  vandelay.bib_match m
14577       WHERE m.queued_record = import_id
14578       LIMIT 1;
14579
14580     IF eg_id IS NULL THEN
14581         RETURN FALSE;
14582     END IF;
14583
14584     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14585 END;
14586 $$ LANGUAGE PLPGSQL;
14587
14588 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14589 DECLARE
14590     queued_record   vandelay.queued_bib_record%ROWTYPE;
14591 BEGIN
14592
14593     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14594
14595         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14596             RETURN NEXT queued_record.id;
14597         END IF;
14598
14599     END LOOP;
14600
14601     RETURN;
14602
14603 END;
14604 $$ LANGUAGE PLPGSQL;
14605
14606 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14607     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14608 $$ LANGUAGE SQL;
14609
14610 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14611 DECLARE
14612     merge_profile   vandelay.merge_profile%ROWTYPE;
14613     dyn_profile     vandelay.compile_profile%ROWTYPE;
14614     source_marc     TEXT;
14615     target_marc     TEXT;
14616     eg_marc         TEXT;
14617     v_marc          TEXT;
14618     replace_rule    TEXT;
14619     match_count     INT;
14620 BEGIN
14621
14622     SELECT  b.marc INTO eg_marc
14623       FROM  authority.record_entry b
14624             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14625       LIMIT 1;
14626
14627     SELECT  q.marc INTO v_marc
14628       FROM  vandelay.queued_record q
14629             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14630       LIMIT 1;
14631
14632     IF eg_marc IS NULL OR v_marc IS NULL THEN
14633         -- RAISE NOTICE 'no marc for vandelay or authority record';
14634         RETURN FALSE;
14635     END IF;
14636
14637     dyn_profile := vandelay.compile_profile( v_marc );
14638
14639     IF merge_profile_id IS NOT NULL THEN
14640         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14641         IF FOUND THEN
14642             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14643             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14644             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14645             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14646         END IF;
14647     END IF;
14648
14649     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14650         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14651         RETURN FALSE;
14652     END IF;
14653
14654     IF dyn_profile.replace_rule <> '' THEN
14655         source_marc = v_marc;
14656         target_marc = eg_marc;
14657         replace_rule = dyn_profile.replace_rule;
14658     ELSE
14659         source_marc = eg_marc;
14660         target_marc = v_marc;
14661         replace_rule = dyn_profile.preserve_rule;
14662     END IF;
14663
14664     UPDATE  authority.record_entry
14665       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14666       WHERE id = eg_id;
14667
14668     IF FOUND THEN
14669         UPDATE  vandelay.queued_authority_record
14670           SET   imported_as = eg_id,
14671                 import_time = NOW()
14672           WHERE id = import_id;
14673         RETURN TRUE;
14674     END IF;
14675
14676     -- RAISE NOTICE 'update of authority.record_entry failed';
14677
14678     RETURN FALSE;
14679
14680 END;
14681 $$ LANGUAGE PLPGSQL;
14682
14683 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14684 DECLARE
14685     eg_id           BIGINT;
14686     match_count     INT;
14687 BEGIN
14688     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14689
14690     IF match_count <> 1 THEN
14691         -- RAISE NOTICE 'not an exact match';
14692         RETURN FALSE;
14693     END IF;
14694
14695     SELECT  m.eg_record INTO eg_id
14696       FROM  vandelay.authority_match m
14697       WHERE m.queued_record = import_id
14698       LIMIT 1;
14699
14700     IF eg_id IS NULL THEN
14701         RETURN FALSE;
14702     END IF;
14703
14704     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14705 END;
14706 $$ LANGUAGE PLPGSQL;
14707
14708 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14709 DECLARE
14710     queued_record   vandelay.queued_authority_record%ROWTYPE;
14711 BEGIN
14712
14713     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14714
14715         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14716             RETURN NEXT queued_record.id;
14717         END IF;
14718
14719     END LOOP;
14720
14721     RETURN;
14722
14723 END;
14724 $$ LANGUAGE PLPGSQL;
14725
14726 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14727     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14728 $$ LANGUAGE SQL;
14729
14730 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14731 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14732 DECLARE
14733     eg_tcn          TEXT;
14734     eg_tcn_source   TEXT;
14735     output          vandelay.tcn_data%ROWTYPE;
14736 BEGIN
14737
14738     -- 001/003
14739     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14740     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14741
14742         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14743         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14744             eg_tcn_source := 'System Local';
14745         END IF;
14746
14747         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14748
14749         IF NOT FOUND THEN
14750             output.used := FALSE;
14751         ELSE
14752             output.used := TRUE;
14753         END IF;
14754
14755         output.tcn := eg_tcn;
14756         output.tcn_source := eg_tcn_source;
14757         RETURN NEXT output;
14758
14759     END IF;
14760
14761     -- 901 ab
14762     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14763     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14764
14765         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14766         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14767             eg_tcn_source := 'System Local';
14768         END IF;
14769
14770         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14771
14772         IF NOT FOUND THEN
14773             output.used := FALSE;
14774         ELSE
14775             output.used := TRUE;
14776         END IF;
14777
14778         output.tcn := eg_tcn;
14779         output.tcn_source := eg_tcn_source;
14780         RETURN NEXT output;
14781
14782     END IF;
14783
14784     -- 039 ab
14785     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14786     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14787
14788         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14789         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14790             eg_tcn_source := 'System Local';
14791         END IF;
14792
14793         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14794
14795         IF NOT FOUND THEN
14796             output.used := FALSE;
14797         ELSE
14798             output.used := TRUE;
14799         END IF;
14800
14801         output.tcn := eg_tcn;
14802         output.tcn_source := eg_tcn_source;
14803         RETURN NEXT output;
14804
14805     END IF;
14806
14807     -- 020 a
14808     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14809     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14810
14811         eg_tcn_source := 'ISBN';
14812
14813         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14814
14815         IF NOT FOUND THEN
14816             output.used := FALSE;
14817         ELSE
14818             output.used := TRUE;
14819         END IF;
14820
14821         output.tcn := eg_tcn;
14822         output.tcn_source := eg_tcn_source;
14823         RETURN NEXT output;
14824
14825     END IF;
14826
14827     -- 022 a
14828     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14829     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14830
14831         eg_tcn_source := 'ISSN';
14832
14833         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14834
14835         IF NOT FOUND THEN
14836             output.used := FALSE;
14837         ELSE
14838             output.used := TRUE;
14839         END IF;
14840
14841         output.tcn := eg_tcn;
14842         output.tcn_source := eg_tcn_source;
14843         RETURN NEXT output;
14844
14845     END IF;
14846
14847     -- 010 a
14848     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14849     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14850
14851         eg_tcn_source := 'LCCN';
14852
14853         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14854
14855         IF NOT FOUND THEN
14856             output.used := FALSE;
14857         ELSE
14858             output.used := TRUE;
14859         END IF;
14860
14861         output.tcn := eg_tcn;
14862         output.tcn_source := eg_tcn_source;
14863         RETURN NEXT output;
14864
14865     END IF;
14866
14867     -- 035 a
14868     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@code="a"]/text()',xml))[1], $re$^.*?(\w+)$$re$, $re$\1$re$);
14869     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14870
14871         eg_tcn_source := 'System Legacy';
14872
14873         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14874
14875         IF NOT FOUND THEN
14876             output.used := FALSE;
14877         ELSE
14878             output.used := TRUE;
14879         END IF;
14880
14881         output.tcn := eg_tcn;
14882         output.tcn_source := eg_tcn_source;
14883         RETURN NEXT output;
14884
14885     END IF;
14886
14887     RETURN;
14888 END;
14889 $_$ LANGUAGE PLPGSQL;
14890
14891 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14892
14893 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);
14894
14895 -- remove invalid data ... there was no fkey before, boo
14896 DELETE FROM metabib.series_field_entry WHERE source NOT IN (SELECT id FROM biblio.record_entry);
14897 DELETE FROM metabib.series_field_entry WHERE field NOT IN (SELECT id FROM config.metabib_field);
14898
14899 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14900 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14901 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14902 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14903 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14904
14905 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14906 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14907 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14908 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14909
14910 ALTER TABLE metabib.series_field_entry
14911         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14912                 REFERENCES biblio.record_entry (id)
14913                 ON DELETE CASCADE
14914                 DEFERRABLE INITIALLY DEFERRED;
14915
14916 ALTER TABLE metabib.series_field_entry
14917         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14918                 REFERENCES config.metabib_field (id)
14919                 ON DELETE CASCADE
14920                 DEFERRABLE INITIALLY DEFERRED;
14921
14922 CREATE TABLE acq.claim_policy_action (
14923         id              SERIAL       PRIMARY KEY,
14924         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14925                                  ON DELETE CASCADE
14926                                      DEFERRABLE INITIALLY DEFERRED,
14927         action_interval INTERVAL     NOT NULL,
14928         action          INT          NOT NULL REFERENCES acq.claim_event_type
14929                                      DEFERRABLE INITIALLY DEFERRED,
14930         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14931 );
14932
14933 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14934 DECLARE
14935     value       TEXT; 
14936     atype       TEXT; 
14937     prov        INT;
14938     pos         INT;
14939     adef        RECORD;
14940     xpath_string    TEXT;
14941 BEGIN
14942     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14943     
14944         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14945       
14946         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14947             IF (atype = 'lineitem_provider_attr_definition') THEN
14948                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14949                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14950             END IF;
14951             
14952             IF (atype = 'lineitem_provider_attr_definition') THEN
14953                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14954             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14955                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14956             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14957                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14958             END IF;
14959       
14960             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14961
14962             IF (adef.code = 'title' OR adef.code = 'author') THEN
14963                 -- title and author should not be split
14964                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14965                 -- string-join in the xpath and remove this special case
14966                 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14967                 IF (value IS NOT NULL AND value <> '') THEN
14968                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14969                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14970                 END IF;
14971             ELSE
14972                 pos := 1;
14973
14974                 LOOP
14975                     SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14976       
14977                     IF (value IS NOT NULL AND value <> '') THEN
14978                         INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14979                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14980                     ELSE
14981                         EXIT;
14982                     END IF;
14983
14984                     pos := pos + 1;
14985                 END LOOP;
14986             END IF;
14987
14988         END IF;
14989
14990     END LOOP;
14991
14992     RETURN NULL;
14993 END;
14994 $function$ LANGUAGE PLPGSQL;
14995
14996 UPDATE config.metabib_field SET label = name;
14997 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14998
14999 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
15000          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
15001
15002 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
15003
15004 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
15005
15006 CREATE TABLE config.metabib_search_alias (
15007     alias       TEXT    PRIMARY KEY,
15008     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
15009     field       INT     REFERENCES config.metabib_field (id)
15010 );
15011
15012 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
15013 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
15014 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
15015 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
15016 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
15017 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
15018 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
15019 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
15020
15021 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
15022 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
15023 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
15024 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
15025 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
15026 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
15027 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
15028 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
15029 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
15030 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
15031 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
15032 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
15033 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
15034
15035 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
15036 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
15037 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
15038 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
15039 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
15040 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
15041 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
15042 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
15043
15044 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
15045 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
15046 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
15047 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
15048 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
15049
15050 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
15051 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
15052 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
15053
15054 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
15055 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;
15056 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;
15057 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;
15058 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;
15059
15060 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
15061 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
15062 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
15063 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
15064
15065 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
15066 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
15067 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
15068 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
15069 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
15070 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
15071 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
15072 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
15073 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
15074 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
15075 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
15076 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
15077 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
15078 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
15079
15080 CREATE TABLE asset.opac_visible_copies (
15081   id        BIGINT primary key, -- copy id
15082   record    BIGINT,
15083   circ_lib  INTEGER
15084 );
15085 COMMENT ON TABLE asset.opac_visible_copies IS $$
15086 Materialized view of copies that are visible in the OPAC, used by
15087 search.query_parser_fts() to speed up OPAC visibility checks on large
15088 databases.  Contents are maintained by a set of triggers.
15089 $$;
15090 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
15091
15092 CREATE OR REPLACE FUNCTION search.query_parser_fts (
15093
15094     param_search_ou INT,
15095     param_depth     INT,
15096     param_query     TEXT,
15097     param_statuses  INT[],
15098     param_locations INT[],
15099     param_offset    INT,
15100     param_check     INT,
15101     param_limit     INT,
15102     metarecord      BOOL,
15103     staff           BOOL
15104  
15105 ) RETURNS SETOF search.search_result AS $func$
15106 DECLARE
15107
15108     current_res         search.search_result%ROWTYPE;
15109     search_org_list     INT[];
15110
15111     check_limit         INT;
15112     core_limit          INT;
15113     core_offset         INT;
15114     tmp_int             INT;
15115
15116     core_result         RECORD;
15117     core_cursor         REFCURSOR;
15118     core_rel_query      TEXT;
15119
15120     total_count         INT := 0;
15121     check_count         INT := 0;
15122     deleted_count       INT := 0;
15123     visible_count       INT := 0;
15124     excluded_count      INT := 0;
15125
15126 BEGIN
15127
15128     check_limit := COALESCE( param_check, 1000 );
15129     core_limit  := COALESCE( param_limit, 25000 );
15130     core_offset := COALESCE( param_offset, 0 );
15131
15132     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15133
15134     IF param_search_ou > 0 THEN
15135         IF param_depth IS NOT NULL THEN
15136             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15137         ELSE
15138             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15139         END IF;
15140     ELSIF param_search_ou < 0 THEN
15141         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15142     ELSIF param_search_ou = 0 THEN
15143         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15144     END IF;
15145
15146     OPEN core_cursor FOR EXECUTE param_query;
15147
15148     LOOP
15149
15150         FETCH core_cursor INTO core_result;
15151         EXIT WHEN NOT FOUND;
15152         EXIT WHEN total_count >= core_limit;
15153
15154         total_count := total_count + 1;
15155
15156         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15157
15158         check_count := check_count + 1;
15159
15160         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15161         IF NOT FOUND THEN
15162             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15163             deleted_count := deleted_count + 1;
15164             CONTINUE;
15165         END IF;
15166
15167         PERFORM 1
15168           FROM  biblio.record_entry b
15169                 JOIN config.bib_source s ON (b.source = s.id)
15170           WHERE s.transcendant
15171                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15172
15173         IF FOUND THEN
15174             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15175             visible_count := visible_count + 1;
15176
15177             current_res.id = core_result.id;
15178             current_res.rel = core_result.rel;
15179
15180             tmp_int := 1;
15181             IF metarecord THEN
15182                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15183             END IF;
15184
15185             IF tmp_int = 1 THEN
15186                 current_res.record = core_result.records[1];
15187             ELSE
15188                 current_res.record = NULL;
15189             END IF;
15190
15191             RETURN NEXT current_res;
15192
15193             CONTINUE;
15194         END IF;
15195
15196         PERFORM 1
15197           FROM  asset.call_number cn
15198                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15199                 JOIN asset.uri uri ON (map.uri = uri.id)
15200           WHERE NOT cn.deleted
15201                 AND cn.label = '##URI##'
15202                 AND uri.active
15203                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15204                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15205                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15206           LIMIT 1;
15207
15208         IF FOUND THEN
15209             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15210             visible_count := visible_count + 1;
15211
15212             current_res.id = core_result.id;
15213             current_res.rel = core_result.rel;
15214
15215             tmp_int := 1;
15216             IF metarecord THEN
15217                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15218             END IF;
15219
15220             IF tmp_int = 1 THEN
15221                 current_res.record = core_result.records[1];
15222             ELSE
15223                 current_res.record = NULL;
15224             END IF;
15225
15226             RETURN NEXT current_res;
15227
15228             CONTINUE;
15229         END IF;
15230
15231         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15232
15233             PERFORM 1
15234               FROM  asset.call_number cn
15235                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15236               WHERE NOT cn.deleted
15237                     AND NOT cp.deleted
15238                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15239                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15240                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15241               LIMIT 1;
15242
15243             IF NOT FOUND THEN
15244                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15245                 excluded_count := excluded_count + 1;
15246                 CONTINUE;
15247             END IF;
15248
15249         END IF;
15250
15251         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15252
15253             PERFORM 1
15254               FROM  asset.call_number cn
15255                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15256               WHERE NOT cn.deleted
15257                     AND NOT cp.deleted
15258                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15259                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15260                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15261               LIMIT 1;
15262
15263             IF NOT FOUND THEN
15264                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15265                 excluded_count := excluded_count + 1;
15266                 CONTINUE;
15267             END IF;
15268
15269         END IF;
15270
15271         IF staff IS NULL OR NOT staff THEN
15272
15273             PERFORM 1
15274               FROM  asset.opac_visible_copies
15275               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15276                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15277               LIMIT 1;
15278
15279             IF NOT FOUND THEN
15280                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15281                 excluded_count := excluded_count + 1;
15282                 CONTINUE;
15283             END IF;
15284
15285         ELSE
15286
15287             PERFORM 1
15288               FROM  asset.call_number cn
15289                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15290                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15291               WHERE NOT cn.deleted
15292                     AND NOT cp.deleted
15293                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15294                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15295               LIMIT 1;
15296
15297             IF NOT FOUND THEN
15298
15299                 PERFORM 1
15300                   FROM  asset.call_number cn
15301                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15302                   LIMIT 1;
15303
15304                 IF FOUND THEN
15305                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15306                     excluded_count := excluded_count + 1;
15307                     CONTINUE;
15308                 END IF;
15309
15310             END IF;
15311
15312         END IF;
15313
15314         visible_count := visible_count + 1;
15315
15316         current_res.id = core_result.id;
15317         current_res.rel = core_result.rel;
15318
15319         tmp_int := 1;
15320         IF metarecord THEN
15321             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15322         END IF;
15323
15324         IF tmp_int = 1 THEN
15325             current_res.record = core_result.records[1];
15326         ELSE
15327             current_res.record = NULL;
15328         END IF;
15329
15330         RETURN NEXT current_res;
15331
15332         IF visible_count % 1000 = 0 THEN
15333             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15334         END IF;
15335
15336     END LOOP;
15337
15338     current_res.id = NULL;
15339     current_res.rel = NULL;
15340     current_res.record = NULL;
15341     current_res.total = total_count;
15342     current_res.checked = check_count;
15343     current_res.deleted = deleted_count;
15344     current_res.visible = visible_count;
15345     current_res.excluded = excluded_count;
15346
15347     CLOSE core_cursor;
15348
15349     RETURN NEXT current_res;
15350
15351 END;
15352 $func$ LANGUAGE PLPGSQL;
15353
15354 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15355 ALTER TABLE biblio.record_entry
15356          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15357          REFERENCES actor.org_unit (id)
15358          DEFERRABLE INITIALLY DEFERRED;
15359
15360 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15361
15362 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15363 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15364
15365 DROP VIEW auditor.biblio_record_entry_lifecycle;
15366
15367 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15368
15369 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15370         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15371 $$ LANGUAGE SQL STRICT IMMUTABLE;
15372
15373 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15374     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15375 $$ LANGUAGE SQL STRICT IMMUTABLE;
15376
15377 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15378     return lc(shift);
15379 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15380
15381 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15382     return uc(shift);
15383 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15384
15385 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15386     use Unicode::Normalize;
15387
15388     my $x = NFD(shift);
15389     $x =~ s/\pM+//go;
15390     return $x;
15391
15392 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15393
15394 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15395     use Unicode::Normalize;
15396
15397     my $x = NFC(shift);
15398     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15399     return $x;
15400
15401 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15402
15403 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15404 DECLARE
15405     setting RECORD;
15406     cur_org INT;
15407 BEGIN
15408     cur_org := org_id;
15409     LOOP
15410         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15411         IF FOUND THEN
15412             RETURN NEXT setting;
15413         END IF;
15414         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15415         EXIT WHEN cur_org IS NULL;
15416     END LOOP;
15417     RETURN;
15418 END;
15419 $$ LANGUAGE plpgsql STABLE;
15420
15421 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15422 DECLARE
15423     counter INT;
15424     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15425 BEGIN
15426
15427     SELECT  COUNT(*) INTO counter
15428       FROM  oils_xpath_table(
15429                 'id',
15430                 'marc',
15431                 'acq.lineitem',
15432                 '//*[@tag="' || tag || '"]',
15433                 'id=' || lineitem
15434             ) as t(i int,c text);
15435
15436     FOR i IN 1 .. counter LOOP
15437         FOR lida IN
15438             SELECT  *
15439               FROM  (   SELECT  id,i,t,v
15440                           FROM  oils_xpath_table(
15441                                     'id',
15442                                     'marc',
15443                                     'acq.lineitem',
15444                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15445                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15446                                     'id=' || lineitem
15447                                 ) as t(id int,t text,v text)
15448                     )x
15449         LOOP
15450             RETURN NEXT lida;
15451         END LOOP;
15452     END LOOP;
15453
15454     RETURN;
15455 END;
15456 $$ LANGUAGE PLPGSQL;
15457
15458 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15459 DECLARE
15460     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15461     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15462     result      config.i18n_core%ROWTYPE;
15463     fallback    TEXT;
15464     keyfield    TEXT := keyclass || '.' || keycol;
15465 BEGIN
15466
15467     -- Try the full locale
15468     SELECT  * INTO result
15469       FROM  config.i18n_core
15470       WHERE fq_field = keyfield
15471             AND identity_value = keyvalue
15472             AND translation = locale;
15473
15474     -- Try just the language
15475     IF NOT FOUND THEN
15476         SELECT  * INTO result
15477           FROM  config.i18n_core
15478           WHERE fq_field = keyfield
15479                 AND identity_value = keyvalue
15480                 AND translation = language;
15481     END IF;
15482
15483     -- Fall back to the string we passed in in the first place
15484     IF NOT FOUND THEN
15485     EXECUTE
15486             'SELECT ' ||
15487                 keycol ||
15488             ' FROM ' || keytable ||
15489             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15490                 INTO fallback;
15491         RETURN fallback;
15492     END IF;
15493
15494     RETURN result.string;
15495 END;
15496 $func$ LANGUAGE PLPGSQL STABLE;
15497
15498 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15499
15500 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15501
15502 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15503
15504 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15505     3, 1, 'delivered_but_lost',
15506     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15507
15508 CREATE TABLE config.global_flag (
15509     label   TEXT    NOT NULL
15510 ) INHERITS (config.internal_flag);
15511 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15512
15513 INSERT INTO config.global_flag (name, label, enabled)
15514     VALUES (
15515         'cat.bib.use_id_for_tcn',
15516         oils_i18n_gettext(
15517             'cat.bib.use_id_for_tcn',
15518             'Cat: Use Internal ID for TCN Value',
15519             'cgf', 
15520             'label'
15521         ),
15522         TRUE
15523     );
15524
15525 -- resolves performance issue noted by EG Indiana
15526
15527 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15528
15529 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15530
15531 -- 1.6 included stock indexes from 1 through 15, but didn't increase the
15532 -- sequence value to leave room for additional stock indexes in subsequent
15533 -- releases (hello!), so custom added indexes will conflict with these.
15534
15535 -- The following function changes the ID of an existing custom index
15536 -- (and any references to that index) to the target ID; if no target ID
15537 -- is supplied, then it adds 100 to the source ID. So this could break if a site
15538 -- has custom indexes at both 16 and 116, for example - but if that's the
15539 -- case anywhere, I'm throwing my hands up in surrender:
15540
15541 CREATE OR REPLACE FUNCTION config.modify_metabib_field(source INT, target INT) RETURNS INT AS $func$
15542 DECLARE
15543     f_class TEXT;
15544     check_id INT;
15545     target_id INT;
15546 BEGIN
15547     SELECT field_class INTO f_class FROM config.metabib_field WHERE id = source;
15548     IF NOT FOUND THEN
15549         RETURN 0;
15550     END IF;
15551     IF target IS NULL THEN
15552         target_id = source + 100;
15553     ELSE
15554         target_id = target;
15555     END IF;
15556     SELECT id FROM config.metabib_field INTO check_id WHERE id = target_id;
15557     IF FOUND THEN
15558         RAISE NOTICE 'Cannot bump config.metabib_field.id from % to %; the target ID already exists.', source, target_id;
15559         RETURN 0;
15560     END IF;
15561     UPDATE config.metabib_field SET id = target_id WHERE id = source;
15562     EXECUTE ' UPDATE metabib.' || f_class || '_field_entry SET field = ' || target_id || ' WHERE field = ' || source;
15563     UPDATE config.metabib_field_index_norm_map SET field = target_id WHERE field = source;
15564     UPDATE search.relevance_adjustment SET field = target_id WHERE field = source;
15565     RETURN 1;
15566 END;
15567 $func$ LANGUAGE PLPGSQL;
15568
15569 -- To avoid sequential scans against the large metabib.*_field_entry tables
15570 CREATE INDEX metabib_author_field_entry_field ON metabib.author_field_entry(field);
15571 CREATE INDEX metabib_keyword_field_entry_field ON metabib.keyword_field_entry(field);
15572 CREATE INDEX metabib_series_field_entry_field ON metabib.series_field_entry(field);
15573 CREATE INDEX metabib_subject_field_entry_field ON metabib.subject_field_entry(field);
15574 CREATE INDEX metabib_title_field_entry_field ON metabib.title_field_entry(field);
15575
15576 -- Now update those custom indexes
15577 SELECT config.modify_metabib_field(id, NULL)
15578     FROM config.metabib_field
15579     WHERE id > 15 AND id < 100 AND field_class || name <> 'subjectcomplete';
15580
15581 -- Ensure "subject|complete" is id = 16, if it exists
15582 SELECT config.modify_metabib_field(id, 16)
15583     FROM config.metabib_field
15584     WHERE id <> 16 AND field_class || name = 'subjectcomplete';
15585
15586 -- And bump the config.metabib_field sequence to a minimum of 100 to avoid problems in the future
15587 SELECT setval('config.metabib_field_id_seq', GREATEST(100, (SELECT MAX(id) + 1 FROM config.metabib_field)));
15588
15589 -- And drop the temporary indexes that we just created
15590 DROP INDEX metabib.metabib_author_field_entry_field;
15591 DROP INDEX metabib.metabib_keyword_field_entry_field;
15592 DROP INDEX metabib.metabib_series_field_entry_field;
15593 DROP INDEX metabib.metabib_subject_field_entry_field;
15594 DROP INDEX metabib.metabib_title_field_entry_field;
15595
15596 -- Now we can go ahead and insert the additional stock indexes
15597
15598 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath )
15599     SELECT  16, 'subject', 'complete', oils_i18n_gettext(16, 'All Subjects', 'cmf', 'label'), 'mods32', $$//mods32:mods/mods32:subject//text()$$
15600       WHERE NOT EXISTS (select id from config.metabib_field where field_class = 'subject' and name = 'complete'); -- in case it's already there
15601
15602 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15603     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15604 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15605     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15606 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15607     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15608 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15609     (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 );
15610 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15611     (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 );
15612 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15613     (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 );
15614 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15615     (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 );
15616 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15617     (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 );
15618 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15619     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15620
15621 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15622  
15623
15624 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15625
15626 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
15627 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15628 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15629 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15630 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15631 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15632 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15633
15634 CREATE TABLE metabib.identifier_field_entry (
15635         id              BIGSERIAL       PRIMARY KEY,
15636         source          BIGINT          NOT NULL,
15637         field           INT             NOT NULL,
15638         value           TEXT            NOT NULL,
15639         index_vector    tsvector        NOT NULL
15640 );
15641 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15642         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15643         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15644
15645 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15646 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15647     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15648 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15649
15650 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15651     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15652 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15653     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15654
15655 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15656     use Business::ISBN;
15657     use strict;
15658     use warnings;
15659
15660     # For each ISBN found in a single string containing a set of ISBNs:
15661     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15662     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15663
15664     my $input = shift;
15665     my $output = '';
15666
15667     foreach my $word (split(/\s/, $input)) {
15668         my $isbn = Business::ISBN->new($word);
15669
15670         # First check the checksum; if it is not valid, fix it and add the original
15671         # bad-checksum ISBN to the output
15672         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15673             $output .= $isbn->isbn() . " ";
15674             $isbn->fix_checksum();
15675         }
15676
15677         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15678         # and add the normalized original ISBN to the output
15679         if ($isbn && $isbn->is_valid()) {
15680             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15681             $output .= $isbn->isbn . " ";
15682
15683             # If we successfully converted the ISBN to its counterpart, add the
15684             # converted ISBN to the output as well
15685             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15686         }
15687     }
15688     return $output if $output;
15689
15690     # If there were no valid ISBNs, just return the raw input
15691     return $input;
15692 $func$ LANGUAGE PLPERLU;
15693
15694 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15695 /*
15696  * Copyright (C) 2010 Merrimack Valley Library Consortium
15697  * Jason Stephenson <jstephenson@mvlc.org>
15698  * Copyright (C) 2010 Laurentian University
15699  * Dan Scott <dscott@laurentian.ca>
15700  *
15701  * The translate_isbn1013 function takes an input ISBN and returns the
15702  * following in a single space-delimited string if the input ISBN is valid:
15703  *   - The normalized input ISBN (hyphens stripped)
15704  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15705  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15706  */
15707 $$;
15708
15709 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15710 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15711 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15712 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15713 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15714 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15715
15716 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15717         'ISBN 10/13 conversion',
15718         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15719         'translate_isbn1013',
15720         0
15721 );
15722
15723 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15724         'Replace',
15725         'Replace all occurences of first parameter in the string with the second parameter.',
15726         'replace',
15727         2
15728 );
15729
15730 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15731     SELECT  m.id, i.id, 1
15732       FROM  config.metabib_field m,
15733             config.index_normalizer i
15734       WHERE i.func IN ('first_word')
15735             AND m.id IN (18);
15736
15737 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15738     SELECT  m.id, i.id, 2
15739       FROM  config.metabib_field m,
15740             config.index_normalizer i
15741       WHERE i.func IN ('translate_isbn1013')
15742             AND m.id IN (18);
15743
15744 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15745     SELECT  m.id, i.id, $$['-','']$$
15746       FROM  config.metabib_field m,
15747             config.index_normalizer i
15748       WHERE i.func IN ('replace')
15749             AND m.id IN (19);
15750
15751 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15752     SELECT  m.id, i.id, $$[' ','']$$
15753       FROM  config.metabib_field m,
15754             config.index_normalizer i
15755       WHERE i.func IN ('replace')
15756             AND m.id IN (19);
15757
15758 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15759
15760 UPDATE  config.metabib_field_index_norm_map
15761   SET   params = REPLACE(params,E'\'','"')
15762   WHERE params IS NOT NULL AND params <> '';
15763
15764 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15765
15766 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15767
15768 ALTER TABLE config.circ_modifier
15769         ADD COLUMN avg_wait_time INTERVAL;
15770
15771 --CREATE TABLE actor.usr_password_reset (
15772 --  id SERIAL PRIMARY KEY,
15773 --  uuid TEXT NOT NULL, 
15774 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15775 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15776 --  has_been_reset BOOL NOT NULL DEFAULT false
15777 --);
15778 --COMMENT ON TABLE actor.usr_password_reset IS $$
15779 --/*
15780 -- * Copyright (C) 2010 Laurentian University
15781 -- * Dan Scott <dscott@laurentian.ca>
15782 -- *
15783 -- * Self-serve password reset requests
15784 -- *
15785 -- * ****
15786 -- *
15787 -- * This program is free software; you can redistribute it and/or
15788 -- * modify it under the terms of the GNU General Public License
15789 -- * as published by the Free Software Foundation; either version 2
15790 -- * of the License, or (at your option) any later version.
15791 -- *
15792 -- * This program is distributed in the hope that it will be useful,
15793 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15794 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15795 -- * GNU General Public License for more details.
15796 -- */
15797 --$$;
15798 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15799 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15800 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15801 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15802
15803 -- Use the identifier search class tsconfig
15804 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15805 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15806     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15807     FOR EACH ROW
15808     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15809
15810 INSERT INTO config.global_flag (name,label,enabled)
15811     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15812 INSERT INTO config.global_flag (name,label,enabled)
15813     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15814
15815 -- turn a JSON scalar into an SQL TEXT value
15816 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15817     use JSON::XS;                    
15818     my $json = shift();
15819     my $txt;
15820     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15821     return undef if ($@);
15822     return $txt
15823 $f$ LANGUAGE PLPERLU;
15824
15825 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15826 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15827 DECLARE
15828     c               action.circulation%ROWTYPE;
15829     view_age        INTERVAL;
15830     usr_view_age    actor.usr_setting%ROWTYPE;
15831     usr_view_start  actor.usr_setting%ROWTYPE;
15832 BEGIN
15833     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15834     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15835
15836     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15837         -- User opted in and supplied a retention age
15838         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15839             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15840         ELSE
15841             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15842         END IF;
15843     ELSIF usr_view_start.value IS NOT NULL THEN
15844         -- User opted in
15845         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15846     ELSE
15847         -- User did not opt in
15848         RETURN;
15849     END IF;
15850
15851     FOR c IN
15852         SELECT  *
15853           FROM  action.circulation
15854           WHERE usr = usr_id
15855                 AND parent_circ IS NULL
15856                 AND xact_start > NOW() - view_age
15857           ORDER BY xact_start
15858     LOOP
15859         RETURN NEXT c;
15860     END LOOP;
15861
15862     RETURN;
15863 END;
15864 $func$ LANGUAGE PLPGSQL;
15865
15866 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15867 DECLARE
15868     usr_keep_age    actor.usr_setting%ROWTYPE;
15869     usr_keep_start  actor.usr_setting%ROWTYPE;
15870     org_keep_age    INTERVAL;
15871     org_keep_count  INT;
15872
15873     keep_age        INTERVAL;
15874
15875     target_acp      RECORD;
15876     circ_chain_head action.circulation%ROWTYPE;
15877     circ_chain_tail action.circulation%ROWTYPE;
15878
15879     purge_position  INT;
15880     count_purged    INT;
15881 BEGIN
15882
15883     count_purged := 0;
15884
15885     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15886
15887     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15888     IF org_keep_count IS NULL THEN
15889         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15890     END IF;
15891
15892     -- First, find copies with more than keep_count non-renewal circs
15893     FOR target_acp IN
15894         SELECT  target_copy,
15895                 COUNT(*) AS total_real_circs
15896           FROM  action.circulation
15897           WHERE parent_circ IS NULL
15898                 AND xact_finish IS NOT NULL
15899           GROUP BY target_copy
15900           HAVING COUNT(*) > org_keep_count
15901     LOOP
15902         purge_position := 0;
15903         -- And, for those, select circs that are finished and older than keep_age
15904         FOR circ_chain_head IN
15905             SELECT  *
15906               FROM  action.circulation
15907               WHERE target_copy = target_acp.target_copy
15908                     AND parent_circ IS NULL
15909               ORDER BY xact_start
15910         LOOP
15911
15912             -- Stop once we've purged enough circs to hit org_keep_count
15913             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15914
15915             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15916             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15917
15918             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15919             usr_keep_age.value := NULL;
15920             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15921
15922             usr_keep_start.value := NULL;
15923             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15924
15925             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15926                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15927                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15928                 ELSE
15929                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15930                 END IF;
15931             ELSIF usr_keep_start.value IS NOT NULL THEN
15932                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15933             ELSE
15934                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15935             END IF;
15936
15937             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15938
15939             -- We've passed the purging tests, purge the circ chain starting at the end
15940             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15941             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15942                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15943                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15944             END LOOP;
15945
15946             count_purged := count_purged + 1;
15947             purge_position := purge_position + 1;
15948
15949         END LOOP;
15950     END LOOP;
15951 END;
15952 $func$ LANGUAGE PLPGSQL;
15953
15954 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15955 DECLARE
15956     h               action.hold_request%ROWTYPE;
15957     view_age        INTERVAL;
15958     view_count      INT;
15959     usr_view_count  actor.usr_setting%ROWTYPE;
15960     usr_view_age    actor.usr_setting%ROWTYPE;
15961     usr_view_start  actor.usr_setting%ROWTYPE;
15962 BEGIN
15963     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15964     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15965     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15966
15967     FOR h IN
15968         SELECT  *
15969           FROM  action.hold_request
15970           WHERE usr = usr_id
15971                 AND fulfillment_time IS NULL
15972                 AND cancel_time IS NULL
15973           ORDER BY request_time DESC
15974     LOOP
15975         RETURN NEXT h;
15976     END LOOP;
15977
15978     IF usr_view_start.value IS NULL THEN
15979         RETURN;
15980     END IF;
15981
15982     IF usr_view_age.value IS NOT NULL THEN
15983         -- User opted in and supplied a retention age
15984         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15985             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15986         ELSE
15987             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15988         END IF;
15989     ELSE
15990         -- User opted in
15991         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15992     END IF;
15993
15994     IF usr_view_count.value IS NOT NULL THEN
15995         view_count := oils_json_to_text(usr_view_count.value)::INT;
15996     ELSE
15997         view_count := 1000;
15998     END IF;
15999
16000     -- show some fulfilled/canceled holds
16001     FOR h IN
16002         SELECT  *
16003           FROM  action.hold_request
16004           WHERE usr = usr_id
16005                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
16006                 AND request_time > NOW() - view_age
16007           ORDER BY request_time DESC
16008           LIMIT view_count
16009     LOOP
16010         RETURN NEXT h;
16011     END LOOP;
16012
16013     RETURN;
16014 END;
16015 $func$ LANGUAGE PLPGSQL;
16016
16017 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
16018
16019 DROP TABLE IF EXISTS serial.index_summary CASCADE;
16020
16021 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
16022
16023 DROP TABLE IF EXISTS serial.issuance CASCADE;
16024
16025 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
16026
16027 DROP TABLE IF EXISTS serial.subscription CASCADE;
16028
16029 CREATE TABLE asset.copy_template (
16030         id             SERIAL   PRIMARY KEY,
16031         owning_lib     INT      NOT NULL
16032                                 REFERENCES actor.org_unit (id)
16033                                 DEFERRABLE INITIALLY DEFERRED,
16034         creator        BIGINT   NOT NULL
16035                                 REFERENCES actor.usr (id)
16036                                 DEFERRABLE INITIALLY DEFERRED,
16037         editor         BIGINT   NOT NULL
16038                                 REFERENCES actor.usr (id)
16039                                 DEFERRABLE INITIALLY DEFERRED,
16040         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16041         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16042         name           TEXT     NOT NULL,
16043         -- columns above this point are attributes of the template itself
16044         -- columns after this point are attributes of the copy this template modifies/creates
16045         circ_lib       INT      REFERENCES actor.org_unit (id)
16046                                 DEFERRABLE INITIALLY DEFERRED,
16047         status         INT      REFERENCES config.copy_status (id)
16048                                 DEFERRABLE INITIALLY DEFERRED,
16049         location       INT      REFERENCES asset.copy_location (id)
16050                                 DEFERRABLE INITIALLY DEFERRED,
16051         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
16052                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
16053         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
16054                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
16055         age_protect    INT,
16056         circulate      BOOL,
16057         deposit        BOOL,
16058         ref            BOOL,
16059         holdable       BOOL,
16060         deposit_amount NUMERIC(6,2),
16061         price          NUMERIC(8,2),
16062         circ_modifier  TEXT,
16063         circ_as_type   TEXT,
16064         alert_message  TEXT,
16065         opac_visible   BOOL,
16066         floating       BOOL,
16067         mint_condition BOOL
16068 );
16069
16070 CREATE TABLE serial.subscription (
16071         id                     SERIAL       PRIMARY KEY,
16072         owning_lib             INT          NOT NULL DEFAULT 1
16073                                             REFERENCES actor.org_unit (id)
16074                                             ON DELETE SET NULL
16075                                             DEFERRABLE INITIALLY DEFERRED,
16076         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
16077         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
16078         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
16079                                             ON DELETE SET NULL
16080                                             DEFERRABLE INITIALLY DEFERRED,
16081         expected_date_offset   INTERVAL
16082         -- acquisitions/business-side tables link to here
16083 );
16084 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
16085 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
16086
16087 --at least one distribution per org_unit holding issues
16088 CREATE TABLE serial.distribution (
16089         id                    SERIAL  PRIMARY KEY,
16090         record_entry          BIGINT  REFERENCES serial.record_entry (id)
16091                                       ON DELETE SET NULL
16092                                       DEFERRABLE INITIALLY DEFERRED,
16093         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
16094                                           summary_method IS NULL
16095                                           OR summary_method IN ( 'add_to_sre',
16096                                           'merge_with_sre', 'use_sre_only',
16097                                           'use_sdist_only')),
16098         subscription          INT     NOT NULL
16099                                       REFERENCES serial.subscription (id)
16100                                                                   ON DELETE CASCADE
16101                                                                   DEFERRABLE INITIALLY DEFERRED,
16102         holding_lib           INT     NOT NULL
16103                                       REFERENCES actor.org_unit (id)
16104                                                                   DEFERRABLE INITIALLY DEFERRED,
16105         label                 TEXT    NOT NULL,
16106         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
16107                                       DEFERRABLE INITIALLY DEFERRED,
16108         receive_unit_template INT     REFERENCES asset.copy_template (id)
16109                                       DEFERRABLE INITIALLY DEFERRED,
16110         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
16111                                       DEFERRABLE INITIALLY DEFERRED,
16112         bind_unit_template    INT     REFERENCES asset.copy_template (id)
16113                                       DEFERRABLE INITIALLY DEFERRED,
16114         unit_label_prefix     TEXT,
16115         unit_label_suffix     TEXT
16116 );
16117 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
16118 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
16119
16120 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
16121
16122 CREATE TABLE serial.stream (
16123         id              SERIAL  PRIMARY KEY,
16124         distribution    INT     NOT NULL
16125                                 REFERENCES serial.distribution (id)
16126                                 ON DELETE CASCADE
16127                                 DEFERRABLE INITIALLY DEFERRED,
16128         routing_label   TEXT
16129 );
16130 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
16131
16132 CREATE UNIQUE INDEX label_once_per_dist
16133         ON serial.stream (distribution, routing_label)
16134         WHERE routing_label IS NOT NULL;
16135
16136 CREATE TABLE serial.routing_list_user (
16137         id             SERIAL       PRIMARY KEY,
16138         stream         INT          NOT NULL
16139                                     REFERENCES serial.stream
16140                                     ON DELETE CASCADE
16141                                     DEFERRABLE INITIALLY DEFERRED,
16142         pos            INT          NOT NULL DEFAULT 1,
16143         reader         INT          REFERENCES actor.usr
16144                                     ON DELETE CASCADE
16145                                     DEFERRABLE INITIALLY DEFERRED,
16146         department     TEXT,
16147         note           TEXT,
16148         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
16149         CONSTRAINT reader_or_dept CHECK
16150         (
16151             -- Recipient is a person or a department, but not both
16152                 (reader IS NOT NULL AND department IS NULL) OR
16153                 (reader IS NULL AND department IS NOT NULL)
16154         )
16155 );
16156 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
16157 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
16158
16159 CREATE TABLE serial.caption_and_pattern (
16160         id           SERIAL       PRIMARY KEY,
16161         subscription INT          NOT NULL REFERENCES serial.subscription (id)
16162                                   ON DELETE CASCADE
16163                                   DEFERRABLE INITIALLY DEFERRED,
16164         type         TEXT         NOT NULL
16165                                   CONSTRAINT cap_type CHECK ( type in
16166                                   ( 'basic', 'supplement', 'index' )),
16167         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
16168         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
16169         end_date     TIMESTAMP WITH TIME ZONE,
16170         active       BOOL         NOT NULL DEFAULT FALSE,
16171         pattern_code TEXT         NOT NULL,       -- must contain JSON
16172         enum_1       TEXT,
16173         enum_2       TEXT,
16174         enum_3       TEXT,
16175         enum_4       TEXT,
16176         enum_5       TEXT,
16177         enum_6       TEXT,
16178         chron_1      TEXT,
16179         chron_2      TEXT,
16180         chron_3      TEXT,
16181         chron_4      TEXT,
16182         chron_5      TEXT
16183 );
16184 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
16185
16186 CREATE TABLE serial.issuance (
16187         id              SERIAL    PRIMARY KEY,
16188         creator         INT       NOT NULL
16189                                   REFERENCES actor.usr (id)
16190                                                           DEFERRABLE INITIALLY DEFERRED,
16191         editor          INT       NOT NULL
16192                                   REFERENCES actor.usr (id)
16193                                   DEFERRABLE INITIALLY DEFERRED,
16194         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16195         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16196         subscription    INT       NOT NULL
16197                                   REFERENCES serial.subscription (id)
16198                                   ON DELETE CASCADE
16199                                   DEFERRABLE INITIALLY DEFERRED,
16200         label           TEXT,
16201         date_published  TIMESTAMP WITH TIME ZONE,
16202         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16203                               DEFERRABLE INITIALLY DEFERRED,
16204         holding_code    TEXT,
16205         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16206                                   (
16207                                       holding_type IS NULL
16208                                       OR holding_type IN ('basic','supplement','index')
16209                                   ),
16210         holding_link_id INT
16211         -- TODO: add columns for separate enumeration/chronology values
16212 );
16213 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16214 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16215 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16216
16217 CREATE TABLE serial.unit (
16218         label           TEXT,
16219         label_sort_key  TEXT,
16220         contents        TEXT    NOT NULL
16221 ) INHERITS (asset.copy);
16222 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16223 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16224 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16225 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16226 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16227
16228 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16229
16230 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16231
16232 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16233
16234 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16235
16236 CREATE TABLE serial.item (
16237         id              SERIAL  PRIMARY KEY,
16238         creator         INT     NOT NULL
16239                                 REFERENCES actor.usr (id)
16240                                 DEFERRABLE INITIALLY DEFERRED,
16241         editor          INT     NOT NULL
16242                                 REFERENCES actor.usr (id)
16243                                 DEFERRABLE INITIALLY DEFERRED,
16244         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16245         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16246         issuance        INT     NOT NULL
16247                                 REFERENCES serial.issuance (id)
16248                                 ON DELETE CASCADE
16249                                 DEFERRABLE INITIALLY DEFERRED,
16250         stream          INT     NOT NULL
16251                                 REFERENCES serial.stream (id)
16252                                 ON DELETE CASCADE
16253                                 DEFERRABLE INITIALLY DEFERRED,
16254         unit            INT     REFERENCES serial.unit (id)
16255                                 ON DELETE SET NULL
16256                                 DEFERRABLE INITIALLY DEFERRED,
16257         uri             INT     REFERENCES asset.uri (id)
16258                                 ON DELETE SET NULL
16259                                 DEFERRABLE INITIALLY DEFERRED,
16260         date_expected   TIMESTAMP WITH TIME ZONE,
16261         date_received   TIMESTAMP WITH TIME ZONE,
16262         status          TEXT    CONSTRAINT valid_status CHECK (
16263                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16264                                'Expected', 'Not Held', 'Not Published', 'Received'))
16265                             DEFAULT 'Expected',
16266         shadowed        BOOL    NOT NULL DEFAULT FALSE
16267 );
16268 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16269 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16270 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16271 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16272 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16273 CREATE INDEX serial_item_status_idx ON serial.item (status);
16274
16275 CREATE TABLE serial.item_note (
16276         id          SERIAL  PRIMARY KEY,
16277         item        INT     NOT NULL
16278                             REFERENCES serial.item (id)
16279                             ON DELETE CASCADE
16280                             DEFERRABLE INITIALLY DEFERRED,
16281         creator     INT     NOT NULL
16282                             REFERENCES actor.usr (id)
16283                             DEFERRABLE INITIALLY DEFERRED,
16284         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16285         pub         BOOL    NOT NULL    DEFAULT FALSE,
16286         title       TEXT    NOT NULL,
16287         value       TEXT    NOT NULL
16288 );
16289 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16290
16291 CREATE TABLE serial.basic_summary (
16292         id                  SERIAL  PRIMARY KEY,
16293         distribution        INT     NOT NULL
16294                                     REFERENCES serial.distribution (id)
16295                                     ON DELETE CASCADE
16296                                     DEFERRABLE INITIALLY DEFERRED,
16297         generated_coverage  TEXT    NOT NULL,
16298         textual_holdings    TEXT,
16299         show_generated      BOOL    NOT NULL DEFAULT TRUE
16300 );
16301 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16302
16303 CREATE TABLE serial.supplement_summary (
16304         id                  SERIAL  PRIMARY KEY,
16305         distribution        INT     NOT NULL
16306                                     REFERENCES serial.distribution (id)
16307                                     ON DELETE CASCADE
16308                                     DEFERRABLE INITIALLY DEFERRED,
16309         generated_coverage  TEXT    NOT NULL,
16310         textual_holdings    TEXT,
16311         show_generated      BOOL    NOT NULL DEFAULT TRUE
16312 );
16313 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16314
16315 CREATE TABLE serial.index_summary (
16316         id                  SERIAL  PRIMARY KEY,
16317         distribution        INT     NOT NULL
16318                                     REFERENCES serial.distribution (id)
16319                                     ON DELETE CASCADE
16320                                     DEFERRABLE INITIALLY DEFERRED,
16321         generated_coverage  TEXT    NOT NULL,
16322         textual_holdings    TEXT,
16323         show_generated      BOOL    NOT NULL DEFAULT TRUE
16324 );
16325 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16326
16327 -- 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.
16328
16329 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16330 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16331
16332 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16333 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;
16334
16335 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16336 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16337
16338 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16339 RETURNS INTEGER AS $$
16340 BEGIN
16341         RETURN EXTRACT( EPOCH FROM interval_val );
16342 END;
16343 $$ LANGUAGE plpgsql;
16344
16345 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16346 RETURNS INTEGER AS $$
16347 BEGIN
16348         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16349 END;
16350 $$ LANGUAGE plpgsql;
16351
16352 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16353     'temp',
16354     oils_i18n_gettext(
16355         'temp',
16356         'Temporary bucket which gets deleted after use.',
16357         'cbrebt',
16358         'label'
16359     )
16360 );
16361
16362 -- 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.
16363
16364 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16365 BEGIN
16366
16367     IF xml_is_well_formed(NEW.marc) THEN
16368         RETURN NEW;
16369     ELSE
16370         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16371     END IF;
16372     
16373 END;
16374 $func$ LANGUAGE PLPGSQL;
16375
16376 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();
16377
16378 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();
16379
16380 ALTER TABLE serial.record_entry
16381         ALTER COLUMN marc DROP NOT NULL;
16382
16383 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16384 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16385 <xsl:stylesheet
16386     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16387     xmlns:marc="http://www.loc.gov/MARC21/slim"
16388     version="1.0">
16389 <!--
16390 Copyright (C) 2010  Equinox Software, Inc.
16391 Galen Charlton <gmc@esilibrary.cOM.
16392
16393 This program is free software; you can redistribute it and/or
16394 modify it under the terms of the GNU General Public License
16395 as published by the Free Software Foundation; either version 2
16396 of the License, or (at your option) any later version.
16397
16398 This program is distributed in the hope that it will be useful,
16399 but WITHOUT ANY WARRANTY; without even the implied warranty of
16400 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16401 GNU General Public License for more details.
16402
16403 marc21_expand_880.xsl - stylesheet used during indexing to
16404                         map alternative graphical representations
16405                         of MARC fields stored in 880 fields
16406                         to the corresponding tag name and value.
16407
16408 For example, if a MARC record for a Chinese book has
16409
16410 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16411 880.00 $6 245-01/$1 $a八十三年短篇小說選
16412
16413 this stylesheet will transform it to the equivalent of
16414
16415 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16416 245.00 $6 245-01/$1 $a八十三年短篇小說選
16417
16418 -->
16419     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16420
16421     <xsl:template match="@*|node()">
16422         <xsl:copy>
16423             <xsl:apply-templates select="@*|node()"/>
16424         </xsl:copy>
16425     </xsl:template>
16426
16427     <xsl:template match="//marc:datafield[@tag='880']">
16428         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16429             <marc:datafield>
16430                 <xsl:attribute name="tag">
16431                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16432                 </xsl:attribute>
16433                 <xsl:attribute name="ind1">
16434                     <xsl:value-of select="@ind1" />
16435                 </xsl:attribute>
16436                 <xsl:attribute name="ind2">
16437                     <xsl:value-of select="@ind2" />
16438                 </xsl:attribute>
16439                 <xsl:apply-templates />
16440             </marc:datafield>
16441         </xsl:if>
16442     </xsl:template>
16443     
16444 </xsl:stylesheet>$$);
16445
16446 -- fix broken prefix and namespace URI for the
16447 -- mods32 transform found in some databases
16448 -- that started out at version 1.2 or earlier
16449 UPDATE config.xml_transform
16450 SET namespace_uri = 'http://www.loc.gov/mods/v3'
16451 WHERE name = 'mods32'
16452 AND namespace_uri = 'http://www.loc.gov/mods/'
16453 AND xslt LIKE '%xmlns="http://www.loc.gov/mods/v3"%';
16454
16455 UPDATE config.xml_transform
16456 SET prefix = 'mods32'
16457 WHERE name = 'mods32'
16458 AND prefix = 'mods'
16459 AND EXISTS (SELECT xpath FROM config.metabib_field WHERE xpath ~ 'mods32:');
16460
16461 -- Splitting the ingest trigger up into little bits
16462
16463 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16464     flag INTEGER PRIMARY KEY
16465 ) ON COMMIT DROP;
16466 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16467
16468 -- cause failure if either of the tables we want to drop have rows
16469 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16470 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16471
16472 DROP TABLE IF EXISTS asset.copy_transparency_map;
16473 DROP TABLE IF EXISTS asset.copy_transparency;
16474
16475 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16476
16477 -- We won't necessarily use all of these, but they are here for completeness.
16478 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16479 -- Values are the EDI code value + 1000
16480
16481 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16482 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16483 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16484 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16485 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16486 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16487 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16488 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16489 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16490 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16491 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16492 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16493 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16494 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16495 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16496 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16497 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16498 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16499 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16500 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16501 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16502 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16503 ('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).'),
16504 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16505 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16506 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16507 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16508 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16509 ('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.'),
16510 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16511 ('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.'),
16512 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16513 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16514 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16515 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16516 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16517 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16518 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16519 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16520 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16521 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16522 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16523 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16524 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16525 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16526 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16527 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16528 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16529 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16530 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16531 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16532 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16533 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16534 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16535 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16536 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16537 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16538 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16539 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16540 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16541 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16542 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16543 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16544 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16545 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16546 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16547 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16548 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16549 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16550 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16551 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16552 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16553 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16554 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16555 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16556 ('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).'),
16557 ('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).'),
16558 ('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).'),
16559 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16560 ('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).'),
16561 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16562 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16563 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16564 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16565 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16566 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16567 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16568 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16569 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16570 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16571 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16572 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16573 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16574 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16575 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16576 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16577 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16578 ('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.'),
16579 ('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.'),
16580 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16581 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16582 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16583 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16584 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16585 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16586 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16587 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16588 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16589 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16590 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16591 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16592 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16593 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16594 ('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.'),
16595 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16596 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16597
16598 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16599     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16600  
16601 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16602  
16603 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16604         'Remove Parenthesized Substring',
16605         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16606         'remove_paren_substring',
16607         0
16608 );
16609
16610 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16611         'Trim Surrounding Space',
16612         'Trim leading and trailing spaces from extracted text.',
16613         'btrim',
16614         0
16615 );
16616
16617 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16618     SELECT  m.id,
16619             i.id,
16620             -2
16621       FROM  config.metabib_field m,
16622             config.index_normalizer i
16623       WHERE i.func IN ('remove_paren_substring')
16624             AND m.id IN (26);
16625
16626 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16627     SELECT  m.id,
16628             i.id,
16629             -1
16630       FROM  config.metabib_field m,
16631             config.index_normalizer i
16632       WHERE i.func IN ('btrim')
16633             AND m.id IN (26);
16634
16635 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16636 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16637 DECLARE
16638     dyn_profile     vandelay.compile_profile%ROWTYPE;
16639     replace_rule    TEXT;
16640     tmp_marc        TEXT;
16641     trgt_marc        TEXT;
16642     tmpl_marc        TEXT;
16643     match_count     INT;
16644 BEGIN
16645
16646     IF target_marc IS NULL OR template_marc IS NULL THEN
16647         -- RAISE NOTICE 'no marc for target or template record';
16648         RETURN NULL;
16649     END IF;
16650
16651     dyn_profile := vandelay.compile_profile( template_marc );
16652
16653     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16654         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16655         RETURN NULL;
16656     END IF;
16657
16658     IF dyn_profile.replace_rule <> '' THEN
16659         trgt_marc = target_marc;
16660         tmpl_marc = template_marc;
16661         replace_rule = dyn_profile.replace_rule;
16662     ELSE
16663         tmp_marc = target_marc;
16664         trgt_marc = template_marc;
16665         tmpl_marc = tmp_marc;
16666         replace_rule = dyn_profile.preserve_rule;
16667     END IF;
16668
16669     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16670
16671 END;
16672 $$ LANGUAGE PLPGSQL;
16673
16674 -- Function to generate an ephemeral overlay template from an authority record
16675 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16676
16677     use MARC::Record;
16678     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16679
16680     my $xml = shift;
16681     my $r = MARC::Record->new_from_xml( $xml );
16682
16683     return undef unless ($r);
16684
16685     my $id = shift() || $r->subfield( '901' => 'c' );
16686     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16687     return undef unless ($id); # We need an ID!
16688
16689     my $tmpl = MARC::Record->new();
16690     $tmpl->encoding( 'UTF-8' );
16691
16692     my @rule_fields;
16693     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16694
16695         my $tag = $field->tag;
16696         my $i1 = $field->indicator(1);
16697         my $i2 = $field->indicator(2);
16698         my $sf = join '', map { $_->[0] } $field->subfields;
16699         my @data = map { @$_ } $field->subfields;
16700
16701         my @replace_them;
16702
16703         # Map the authority field to bib fields it can control.
16704         if ($tag >= 100 and $tag <= 111) {       # names
16705             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16706         } elsif ($tag eq '130') {                # uniform title
16707             @replace_them = qw/130 240 440 730 830/;
16708         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16709             @replace_them = ($tag + 500);
16710         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16711             @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/;
16712         } else {
16713             next;
16714         }
16715
16716         # Dummy up the bib-side data
16717         $tmpl->append_fields(
16718             map {
16719                 MARC::Field->new( $_, $i1, $i2, @data )
16720             } @replace_them
16721         );
16722
16723         # Construct some 'replace' rules
16724         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16725     }
16726
16727     # Insert the replace rules into the template
16728     $tmpl->append_fields(
16729         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16730     );
16731
16732     $xml = $tmpl->as_xml_record;
16733     $xml =~ s/^<\?.+?\?>$//mo;
16734     $xml =~ s/\n//sgo;
16735     $xml =~ s/>\s+</></sgo;
16736
16737     return $xml;
16738
16739 $func$ LANGUAGE PLPERLU;
16740
16741 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16742     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16743 $func$ LANGUAGE SQL;
16744
16745 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16746     SELECT authority.generate_overlay_template( $1, NULL );
16747 $func$ LANGUAGE SQL;
16748
16749 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16750 DELETE FROM config.metabib_field WHERE id = 26;
16751
16752 -- Making this a global_flag (UI accessible) instead of an internal_flag
16753 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16754     VALUES (
16755         'ingest.disable_authority_linking',
16756         oils_i18n_gettext(
16757             'ingest.disable_authority_linking',
16758             'Authority Automation: Disable bib-authority link tracking',
16759             'cgf', 
16760             'label'
16761         )
16762     );
16763 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16764 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16765
16766 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16767     VALUES (
16768         'ingest.disable_authority_auto_update',
16769         oils_i18n_gettext(
16770             'ingest.disable_authority_auto_update',
16771             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16772             'cgf', 
16773             'label'
16774         )
16775     );
16776
16777 -- Enable automated ingest of authority records; just insert the row into
16778 -- authority.record_entry and authority.full_rec will automatically be populated
16779
16780 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16781     UPDATE  biblio.record_entry
16782       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16783       WHERE id = $2;
16784     SELECT $1;
16785 $func$ LANGUAGE SQL;
16786
16787 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16788     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16789 $func$ LANGUAGE SQL;
16790
16791 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16792
16793 use MARC::Record;
16794 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16795
16796 my $xml = shift;
16797 my $r = MARC::Record->new_from_xml( $xml );
16798
16799 return_next( { tag => 'LDR', value => $r->leader } );
16800
16801 for my $f ( $r->fields ) {
16802     if ($f->is_control_field) {
16803         return_next({ tag => $f->tag, value => $f->data });
16804     } else {
16805         for my $s ($f->subfields) {
16806             return_next({
16807                 tag      => $f->tag,
16808                 ind1     => $f->indicator(1),
16809                 ind2     => $f->indicator(2),
16810                 subfield => $s->[0],
16811                 value    => $s->[1]
16812             });
16813
16814         }
16815     }
16816 }
16817
16818 return undef;
16819
16820 $func$ LANGUAGE PLPERLU;
16821
16822 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16823 DECLARE
16824     auth    authority.record_entry%ROWTYPE;
16825     output    authority.full_rec%ROWTYPE;
16826     field    RECORD;
16827 BEGIN
16828     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16829
16830     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16831         output.record := rid;
16832         output.ind1 := field.ind1;
16833         output.ind2 := field.ind2;
16834         output.tag := field.tag;
16835         output.subfield := field.subfield;
16836         IF field.subfield IS NOT NULL THEN
16837             output.value := naco_normalize(field.value, field.subfield);
16838         ELSE
16839             output.value := field.value;
16840         END IF;
16841
16842         CONTINUE WHEN output.value IS NULL;
16843
16844         RETURN NEXT output;
16845     END LOOP;
16846 END;
16847 $func$ LANGUAGE PLPGSQL;
16848
16849 -- authority.rec_descriptor appears to be unused currently
16850 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16851 BEGIN
16852     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16853 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16854 --        SELECT  auth_id, ;
16855
16856     RETURN;
16857 END;
16858 $func$ LANGUAGE PLPGSQL;
16859
16860 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16861 BEGIN
16862     DELETE FROM authority.full_rec WHERE record = auth_id;
16863     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16864         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16865
16866     RETURN;
16867 END;
16868 $func$ LANGUAGE PLPGSQL;
16869
16870 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16871 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16872 BEGIN
16873
16874     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16875         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16876         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16877           -- Should remove matching $0 from controlled fields at the same time?
16878         RETURN NEW; -- and we're done
16879     END IF;
16880
16881     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16882         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16883
16884         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16885             RETURN NEW;
16886         END IF;
16887     END IF;
16888
16889     -- Flatten and insert the afr data
16890     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16891     IF NOT FOUND THEN
16892         PERFORM authority.reingest_authority_full_rec(NEW.id);
16893 -- authority.rec_descriptor is not currently used
16894 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16895 --        IF NOT FOUND THEN
16896 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16897 --        END IF;
16898     END IF;
16899
16900     RETURN NEW;
16901 END;
16902 $func$ LANGUAGE PLPGSQL;
16903
16904 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 ();
16905
16906 -- Some records manage to get XML namespace declarations into each element,
16907 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16908 -- This broke the old maintain_901(), so we'll make the regex more robust
16909
16910 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16911 BEGIN
16912     -- Remove any existing 901 fields before we insert the authoritative one
16913     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16914     IF TG_TABLE_SCHEMA = 'biblio' THEN
16915         NEW.marc := REGEXP_REPLACE(
16916             NEW.marc,
16917             E'(</(?:[^:]*?:)?record>)',
16918             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16919                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16920                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16921                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16922                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16923                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16924                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16925              E'</datafield>\\1'
16926         );
16927     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16928         NEW.marc := REGEXP_REPLACE(
16929             NEW.marc,
16930             E'(</(?:[^:]*?:)?record>)',
16931             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16932                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16933                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16934              E'</datafield>\\1'
16935         );
16936     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16937         NEW.marc := REGEXP_REPLACE(
16938             NEW.marc,
16939             E'(</(?:[^:]*?:)?record>)',
16940             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16941                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16942                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16943                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16944                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16945              E'</datafield>\\1'
16946         );
16947     ELSE
16948         NEW.marc := REGEXP_REPLACE(
16949             NEW.marc,
16950             E'(</(?:[^:]*?:)?record>)',
16951             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16952                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16953                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16954              E'</datafield>\\1'
16955         );
16956     END IF;
16957
16958     RETURN NEW;
16959 END;
16960 $func$ LANGUAGE PLPGSQL;
16961
16962 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16963 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16964 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16965  
16966 -- In booking, elbow room defines:
16967 --  a) how far in the future you must make a reservation on a given item if
16968 --      that item will have to transit somewhere to fulfill the reservation.
16969 --  b) how soon a reservation must be starting for the reserved item to
16970 --      be op-captured by the checkin interface.
16971
16972 -- We don't want to clobber any default_elbow room at any level:
16973
16974 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16975 DECLARE
16976     existing    actor.org_unit_setting%ROWTYPE;
16977 BEGIN
16978     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16979     IF NOT FOUND THEN
16980         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16981             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16982             'circ.booking_reservation.default_elbow_room',
16983             '"1 day"'
16984         );
16985         RETURN 1;
16986     END IF;
16987     RETURN 0;
16988 END;
16989 $$ LANGUAGE plpgsql;
16990
16991 SELECT pg_temp.default_elbow();
16992
16993 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16994
16995 -- returns the distinct set of target copy IDs from a user's visible circulation history
16996 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16997     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16998 $$ LANGUAGE SQL;
16999
17000 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
17001 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
17002 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
17003 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
17004
17005 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
17006
17007 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
17008 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
17009
17010 INSERT INTO config.global_flag (name, label, enabled)
17011     VALUES (
17012         'cat.maintain_control_numbers',
17013         oils_i18n_gettext(
17014             'cat.maintain_control_numbers',
17015             'Cat: Maintain 001/003/035 according to the MARC21 specification',
17016             'cgf', 
17017             'label'
17018         ),
17019         TRUE
17020     );
17021
17022 INSERT INTO config.global_flag (name, label, enabled)
17023     VALUES (
17024         'circ.holds.empty_issuance_ok',
17025         oils_i18n_gettext(
17026             'circ.holds.empty_issuance_ok',
17027             'Holds: Allow holds on empty issuances',
17028             'cgf',
17029             'label'
17030         ),
17031         TRUE
17032     );
17033
17034 INSERT INTO config.global_flag (name, label, enabled)
17035     VALUES (
17036         'circ.holds.usr_not_requestor',
17037         oils_i18n_gettext(
17038             'circ.holds.usr_not_requestor',
17039             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
17040             'cgf',
17041             'label'
17042         ),
17043         TRUE
17044     );
17045
17046 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
17047 use strict;
17048 use MARC::Record;
17049 use MARC::File::XML (BinaryEncoding => 'UTF-8');
17050 use Encode;
17051 use Unicode::Normalize;
17052
17053 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
17054 my $schema = $_TD->{table_schema};
17055 my $rec_id = $_TD->{new}{id};
17056
17057 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
17058 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
17059 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
17060     return;
17061 }
17062
17063 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
17064 my $ou_cni = 'EVRGRN';
17065
17066 my $owner;
17067 if ($schema eq 'serial') {
17068     $owner = $_TD->{new}{owning_lib};
17069 } else {
17070     # are.owner and bre.owner can be null, so fall back to the consortial setting
17071     $owner = $_TD->{new}{owner} || 1;
17072 }
17073
17074 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
17075 if ($ous_rv->{processed}) {
17076     $ou_cni = $ous_rv->{rows}[0]->{value};
17077     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
17078 } else {
17079     # Fall back to the shortname of the OU if there was no OU setting
17080     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
17081     if ($ous_rv->{processed}) {
17082         $ou_cni = $ous_rv->{rows}[0]->{shortname};
17083     }
17084 }
17085
17086 my ($create, $munge) = (0, 0);
17087
17088 my @scns = $record->field('035');
17089
17090 foreach my $id_field ('001', '003') {
17091     my $spec_value;
17092     my @controls = $record->field($id_field);
17093
17094     if ($id_field eq '001') {
17095         $spec_value = $rec_id;
17096     } else {
17097         $spec_value = $ou_cni;
17098     }
17099
17100     # Create the 001/003 if none exist
17101     if (scalar(@controls) == 1) {
17102         # Only one field; check to see if we need to munge it
17103         unless (grep $_->data() eq $spec_value, @controls) {
17104             $munge = 1;
17105         }
17106     } else {
17107         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
17108         foreach my $control (@controls) {
17109             unless ($control->data() eq $spec_value) {
17110                 $record->delete_field($control);
17111             }
17112         }
17113         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
17114         $create = 1;
17115     }
17116 }
17117
17118 # Now, if we need to munge the 001, we will first push the existing 001/003
17119 # into the 035; but if the record did not have one (and one only) 001 and 003
17120 # to begin with, skip this process
17121 if ($munge and not $create) {
17122     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
17123
17124     # Do not create duplicate 035 fields
17125     unless (grep $_->subfield('a') eq $scn, @scns) {
17126         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
17127     }
17128 }
17129
17130 # Set the 001/003 and update the MARC
17131 if ($create or $munge) {
17132     $record->field('001')->data($rec_id);
17133     $record->field('003')->data($ou_cni);
17134
17135     my $xml = $record->as_xml_record();
17136     $xml =~ s/\n//sgo;
17137     $xml =~ s/^<\?xml.+\?\s*>//go;
17138     $xml =~ s/>\s+</></go;
17139     $xml =~ s/\p{Cc}//go;
17140
17141     # Embed a version of OpenILS::Application::AppUtils->entityize()
17142     # to avoid having to set PERL5LIB for PostgreSQL as well
17143
17144     # If we are going to convert non-ASCII characters to XML entities,
17145     # we had better be dealing with a UTF8 string to begin with
17146     $xml = decode_utf8($xml);
17147
17148     $xml = NFC($xml);
17149
17150     # Convert raw ampersands to entities
17151     $xml =~ s/&(?!\S+;)/&amp;/gso;
17152
17153     # Convert Unicode characters to entities
17154     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
17155
17156     $xml =~ s/[\x00-\x1f]//go;
17157     $_TD->{new}{marc} = $xml;
17158
17159     return "MODIFY";
17160 }
17161
17162 return;
17163 $func$ LANGUAGE PLPERLU;
17164
17165 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17166 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17167 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17168
17169 INSERT INTO metabib.facet_entry (source, field, value)
17170     SELECT source, field, value FROM (
17171         SELECT * FROM metabib.author_field_entry
17172             UNION ALL
17173         SELECT * FROM metabib.keyword_field_entry
17174             UNION ALL
17175         SELECT * FROM metabib.identifier_field_entry
17176             UNION ALL
17177         SELECT * FROM metabib.title_field_entry
17178             UNION ALL
17179         SELECT * FROM metabib.subject_field_entry
17180             UNION ALL
17181         SELECT * FROM metabib.series_field_entry
17182         )x
17183     WHERE x.index_vector = '';
17184         
17185 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
17186 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
17187 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
17188 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
17189 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
17190 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
17191
17192 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
17193 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
17194 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
17195
17196 -- copy OPAC visibility materialized view
17197 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
17198
17199     TRUNCATE TABLE asset.opac_visible_copies;
17200
17201     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17202     SELECT  cp.id, cp.circ_lib, cn.record
17203     FROM  asset.copy cp
17204         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17205         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17206         JOIN asset.copy_location cl ON (cp.location = cl.id)
17207         JOIN config.copy_status cs ON (cp.status = cs.id)
17208         JOIN biblio.record_entry b ON (cn.record = b.id)
17209     WHERE NOT cp.deleted
17210         AND NOT cn.deleted
17211         AND NOT b.deleted
17212         AND cs.opac_visible
17213         AND cl.opac_visible
17214         AND cp.opac_visible
17215         AND a.opac_visible;
17216
17217 $$ LANGUAGE SQL;
17218 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17219 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17220 $$;
17221
17222 -- and actually populate the table
17223 SELECT asset.refresh_opac_visible_copies_mat_view();
17224
17225 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17226 DECLARE
17227     add_query       TEXT;
17228     remove_query    TEXT;
17229     do_add          BOOLEAN := false;
17230     do_remove       BOOLEAN := false;
17231 BEGIN
17232     add_query := $$
17233             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17234                 SELECT  cp.id, cp.circ_lib, cn.record
17235                   FROM  asset.copy cp
17236                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17237                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17238                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17239                         JOIN config.copy_status cs ON (cp.status = cs.id)
17240                         JOIN biblio.record_entry b ON (cn.record = b.id)
17241                   WHERE NOT cp.deleted
17242                         AND NOT cn.deleted
17243                         AND NOT b.deleted
17244                         AND cs.opac_visible
17245                         AND cl.opac_visible
17246                         AND cp.opac_visible
17247                         AND a.opac_visible
17248     $$;
17249  
17250     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17251
17252     IF TG_OP = 'INSERT' THEN
17253
17254         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17255             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17256             EXECUTE add_query;
17257         END IF;
17258
17259         RETURN NEW;
17260
17261     END IF;
17262
17263     -- handle items first, since with circulation activity
17264     -- their statuses change frequently
17265     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17266
17267         IF OLD.location    <> NEW.location OR
17268            OLD.call_number <> NEW.call_number OR
17269            OLD.status      <> NEW.status OR
17270            OLD.circ_lib    <> NEW.circ_lib THEN
17271             -- any of these could change visibility, but
17272             -- we'll save some queries and not try to calculate
17273             -- the change directly
17274             do_remove := true;
17275             do_add := true;
17276         ELSE
17277
17278             IF OLD.deleted <> NEW.deleted THEN
17279                 IF NEW.deleted THEN
17280                     do_remove := true;
17281                 ELSE
17282                     do_add := true;
17283                 END IF;
17284             END IF;
17285
17286             IF OLD.opac_visible <> NEW.opac_visible THEN
17287                 IF OLD.opac_visible THEN
17288                     do_remove := true;
17289                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17290                                         -- is also marked opac_visible
17291                     do_add := true;
17292                 END IF;
17293             END IF;
17294
17295         END IF;
17296
17297         IF do_remove THEN
17298             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17299         END IF;
17300         IF do_add THEN
17301             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17302             EXECUTE add_query;
17303         END IF;
17304
17305         RETURN NEW;
17306
17307     END IF;
17308
17309     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17310  
17311         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17312
17313             RETURN NEW;
17314  
17315         ELSIF NEW.deleted THEN -- remove rows
17316  
17317             IF TG_TABLE_NAME = 'call_number' THEN
17318                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17319             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17320                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17321             END IF;
17322  
17323             RETURN NEW;
17324  
17325         ELSIF OLD.deleted THEN -- add rows
17326  
17327             IF TG_TABLE_NAME IN ('copy','unit') THEN
17328                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17329             ELSIF TG_TABLE_NAME = 'call_number' THEN
17330                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17331             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17332                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17333             END IF;
17334  
17335             EXECUTE add_query;
17336             RETURN NEW;
17337  
17338         END IF;
17339  
17340     END IF;
17341
17342     IF TG_TABLE_NAME = 'call_number' THEN
17343
17344         IF OLD.record <> NEW.record THEN
17345             -- call number is linked to different bib
17346             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17347             EXECUTE remove_query;
17348             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17349             EXECUTE add_query;
17350         END IF;
17351
17352         RETURN NEW;
17353
17354     END IF;
17355
17356     IF TG_TABLE_NAME IN ('record_entry') THEN
17357         RETURN NEW; -- don't have 'opac_visible'
17358     END IF;
17359
17360     -- actor.org_unit, asset.copy_location, asset.copy_status
17361     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17362
17363         RETURN NEW;
17364
17365     ELSIF NEW.opac_visible THEN -- add rows
17366
17367         IF TG_TABLE_NAME = 'org_unit' THEN
17368             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17369         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17370             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17371         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17372             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17373         END IF;
17374  
17375         EXECUTE add_query;
17376  
17377     ELSE -- delete rows
17378
17379         IF TG_TABLE_NAME = 'org_unit' THEN
17380             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17381         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17382             remove_query := remove_query || 'location = ' || NEW.id || ');';
17383         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17384             remove_query := remove_query || 'status = ' || NEW.id || ');';
17385         END IF;
17386  
17387         EXECUTE remove_query;
17388  
17389     END IF;
17390  
17391     RETURN NEW;
17392 END;
17393 $func$ LANGUAGE PLPGSQL;
17394 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17395 Trigger function to update the copy OPAC visiblity cache.
17396 $$;
17397 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();
17398 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17399 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();
17400 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();
17401 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17402 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();
17403 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();
17404
17405 -- must create this rule explicitly; it is not inherited from asset.copy
17406 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;
17407
17408 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);
17409
17410 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17411 DECLARE
17412     moved_objects INT := 0;
17413     bib_id        INT := 0;
17414     bib_rec       biblio.record_entry%ROWTYPE;
17415     auth_link     authority.bib_linking%ROWTYPE;
17416     ingest_same   boolean;
17417 BEGIN
17418
17419     -- Defining our terms:
17420     -- "target record" = the record that will survive the merge
17421     -- "source record" = the record that is sacrifing its existence and being
17422     --   replaced by the target record
17423
17424     -- 1. Update all bib records with the ID from target_record in their $0
17425     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre
17426       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17427       WHERE abl.authority = source_record LOOP
17428
17429         UPDATE biblio.record_entry
17430           SET marc = REGEXP_REPLACE(marc,
17431             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17432             E'\\1' || target_record || '<', 'g')
17433           WHERE id = bib_rec.id;
17434
17435           moved_objects := moved_objects + 1;
17436     END LOOP;
17437
17438     -- 2. Grab the current value of reingest on same MARC flag
17439     SELECT enabled INTO ingest_same
17440       FROM config.internal_flag
17441       WHERE name = 'ingest.reingest.force_on_same_marc'
17442     ;
17443
17444     -- 3. Temporarily set reingest on same to TRUE
17445     UPDATE config.internal_flag
17446       SET enabled = TRUE
17447       WHERE name = 'ingest.reingest.force_on_same_marc'
17448     ;
17449
17450     -- 4. Make a harmless update to target_record to trigger auto-update
17451     --    in linked bibliographic records
17452     UPDATE authority.record_entry
17453       SET deleted = FALSE
17454       WHERE id = target_record;
17455
17456     -- 5. "Delete" source_record
17457     DELETE FROM authority.record_entry
17458       WHERE id = source_record;
17459
17460     -- 6. Set "reingest on same MARC" flag back to initial value
17461     UPDATE config.internal_flag
17462       SET enabled = ingest_same
17463       WHERE name = 'ingest.reingest.force_on_same_marc'
17464     ;
17465
17466     RETURN moved_objects;
17467 END;
17468 $func$ LANGUAGE plpgsql;
17469
17470 -- serial.record_entry already had an owner column spelled "owning_lib"
17471 -- Adjust the table and affected functions accordingly
17472
17473 ALTER TABLE serial.record_entry DROP COLUMN owner;
17474
17475 CREATE TABLE actor.usr_saved_search (
17476     id              SERIAL          PRIMARY KEY,
17477         owner           INT             NOT NULL REFERENCES actor.usr (id)
17478                                         ON DELETE CASCADE
17479                                         DEFERRABLE INITIALLY DEFERRED,
17480         name            TEXT            NOT NULL,
17481         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17482         query_text      TEXT            NOT NULL,
17483         query_type      TEXT            NOT NULL
17484                                         CONSTRAINT valid_query_text CHECK (
17485                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17486                                         -- we may add other types someday
17487         target          TEXT            NOT NULL
17488                                         CONSTRAINT valid_target CHECK (
17489                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17490         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17491 );
17492
17493 -- Apply Dan Wells' changes to the serial schema, from the
17494 -- seials-integration branch
17495
17496 CREATE TABLE serial.subscription_note (
17497         id           SERIAL PRIMARY KEY,
17498         subscription INT    NOT NULL
17499                             REFERENCES serial.subscription (id)
17500                             ON DELETE CASCADE
17501                             DEFERRABLE INITIALLY DEFERRED,
17502         creator      INT    NOT NULL
17503                             REFERENCES actor.usr (id)
17504                             DEFERRABLE INITIALLY DEFERRED,
17505         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17506         pub          BOOL   NOT NULL DEFAULT FALSE,
17507         title        TEXT   NOT NULL,
17508         value        TEXT   NOT NULL
17509 );
17510 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17511
17512 CREATE TABLE serial.distribution_note (
17513         id           SERIAL PRIMARY KEY,
17514         distribution INT    NOT NULL
17515                             REFERENCES serial.distribution (id)
17516                             ON DELETE CASCADE
17517                             DEFERRABLE INITIALLY DEFERRED,
17518         creator      INT    NOT NULL
17519                             REFERENCES actor.usr (id)
17520                             DEFERRABLE INITIALLY DEFERRED,
17521         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17522         pub          BOOL   NOT NULL DEFAULT FALSE,
17523         title        TEXT   NOT NULL,
17524         value        TEXT   NOT NULL
17525 );
17526 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17527
17528 ------- Begin surgery on serial.unit
17529
17530 ALTER TABLE serial.unit
17531         DROP COLUMN label;
17532
17533 ALTER TABLE serial.unit
17534         RENAME COLUMN label_sort_key TO sort_key;
17535
17536 ALTER TABLE serial.unit
17537         RENAME COLUMN contents TO detailed_contents;
17538
17539 ALTER TABLE serial.unit
17540         ADD COLUMN summary_contents TEXT;
17541
17542 UPDATE serial.unit
17543 SET summary_contents = detailed_contents;
17544
17545 ALTER TABLE serial.unit
17546         ALTER column summary_contents SET NOT NULL;
17547
17548 ------- End surgery on serial.unit
17549
17550 -- 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' );
17551
17552 -- Now rebuild the constraints dropped via cascade.
17553 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17554 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17555 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17556
17557 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17558
17559 DELETE FROM config.metabib_field_index_norm_map
17560     WHERE norm IN (
17561         SELECT id 
17562             FROM config.index_normalizer
17563             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17564     )
17565     AND field = 18
17566 ;
17567
17568 -- We won't necessarily use all of these, but they are here for completeness.
17569 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17570 -- Values are the EDI code value + 1200
17571
17572 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17573 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17574 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17575 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17576 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17577 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17578 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17579 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17580 (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.'),
17581 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17582 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17583 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17584 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17585 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17586 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17587 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17588 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17589 (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.'),
17590 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17591 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17592 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17593 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17594 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17595 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17596 (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.'),
17597 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17598 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17599 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17600 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17601 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17602 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17603 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17604 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17605 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17606 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17607 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17608 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17609 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17610 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17611 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17612 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17613 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17614 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17615 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17616 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17617 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17618 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17619 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17620 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17621 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17622 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17623 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17624 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17625 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17626 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17627 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17628 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17629 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17630 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17631 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17632 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17633 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17634 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17635 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17636 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17637 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17638 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17639 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17640 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17641 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17642 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17643 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17644 (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.'),
17645 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17646 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17647 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17648 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17649 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17650 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17651 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17652 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17653 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17654 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17655 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17656 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17657 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17658 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17659 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17660 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17661 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17662 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17663 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17664 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17665 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17666 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17667 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17668 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17669 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17670 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17671 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17672 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17673 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17674 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17675 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17676 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17677 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17678 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17679 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17680 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17681 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17682 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17683 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17684 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17685 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17686 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17687 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17688 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17689 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17690 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17691 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17692 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17693 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17694 (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.'),
17695 (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.'),
17696 (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.'),
17697 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17698 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17699 (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.'),
17700 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17701 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17702 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17703 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17704 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17705 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17706 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17707 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17708 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17709 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17710 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17711 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17712 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17713 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17714 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17715 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17716 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17717 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17718 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17719 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17720 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17721 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17722 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17723 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17724 (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.'),
17725 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17726 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17727 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17728 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17729 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17730 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17731 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17732 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17733 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17734 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17735 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17736 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17737 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17738 (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.'),
17739 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17740 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17741 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17742 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17743 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17744 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17745 (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.'),
17746 (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.'),
17747 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17748 (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.'),
17749 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17750 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17751 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17752 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17753 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17754 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17755 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17756 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17757 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17758 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17759 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17760 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17761 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17762 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17763 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17764 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17765 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17766 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17767 (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.'),
17768 (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.'),
17769 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17770 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17771 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17772 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17773 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17774 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17775 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17776 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17777 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17778 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17779 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17780 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17781 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17782 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17783 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17784 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17785 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17786 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17787 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17788 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17789 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17790 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17791 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17792 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17793 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17794 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17795 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17796 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17797 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17798 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17799 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17800 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17801 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17802 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17803 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17804 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17805 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17806 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17807 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17808 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17809 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17810 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17811 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17812 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17813 (1, 't', 1442, 'Number of months', 'The number of months.'),
17814 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17815 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17816 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17817 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17818 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17819 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17820 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17821 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17822 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17823 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17824 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17825 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17826 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17827 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17828 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17829 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17830 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17831 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17832 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17833 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17834 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17835 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17836 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17837 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17838 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17839 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17840 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17841 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17842 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17843 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17844 (1, 't', 1473, 'Agents', 'The number of agents.'),
17845 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17846 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17847 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17848 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17849 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17850 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17851 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17852 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17853 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17854 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17855 (1, 't', 1484, 'Departments', 'The number of departments.'),
17856 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17857 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17858 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17859 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17860 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17861 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17862 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17863 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17864 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17865 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17866 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17867 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17868 (1, 't', 1497, 'Executives', 'The number of executives.'),
17869 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17870 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17871 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17872 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17873 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17874 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17875 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17876 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17877 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17878 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17879 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17880 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17881 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17882 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17883 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17884 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17885 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17886 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17887 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17888 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17889 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17890 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17891 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17892 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17893 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17894 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17895 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17896 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17897 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17898 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17899 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17900 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17901 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17902 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17903 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17904 (1, 't', 1533, 'Seats',        'The number of seats.'),
17905 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17906 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17907 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17908 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17909 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17910 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17911 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17912 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17913 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17914 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17915 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17916 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17917 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17918 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17919 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17920 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17921 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17922 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17923 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17924 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17925 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17926 (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.'),
17927 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17928 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17929 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17930 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17931 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17932 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17933 (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.'),
17934 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17935 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17936 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17937 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17938 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17939 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17940 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17941 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17942 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17943 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17944 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17945 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17946 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17947 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17948 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17949 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17950 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17951 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17952 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17953 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17954 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17955 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17956 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17957 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17958 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17959 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17960 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17961 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17962 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17963 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17964 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17965 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17966 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17967 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17968 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17969 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17970 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17971 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17972 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17973 (1, 't', 1602, 'Patients',         'Number of patients.'),
17974 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17975 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17976 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17977 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17978 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17979 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17980 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17981 (1, 't', 1610, 'Operators',        'Number of operators.'),
17982 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17983 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17984 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17985 (1, 't', 1614, 'Machines',         'Number of machines.'),
17986 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17987 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17988 (1, 't', 1617, 'Directors',        'Number of directors.'),
17989 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17990 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17991 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17992 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17993 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17994 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17995 (1, 't', 1624, 'Beds', 'Number of beds.'),
17996 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17997 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17998 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17999 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
18000 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
18001 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
18002 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
18003 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
18004 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
18005 (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.'),
18006 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
18007 (1, 't', 1636, 'Professor', 'The number of professors.'),
18008 (1, 't', 1637, 'Seller',    'The number of sellers.'),
18009 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
18010 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
18011 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
18012 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
18013 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
18014 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
18015 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
18016 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
18017 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
18018 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
18019 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
18020 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
18021 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
18022 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
18023 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
18024 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
18025 (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.'),
18026 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
18027 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
18028 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
18029 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
18030 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
18031 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
18032 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
18033 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
18034 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
18035 (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.'),
18036 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
18037 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
18038 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
18039 (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.'),
18040 (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.'),
18041 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
18042 (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.'),
18043 (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.'),
18044 (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.'),
18045 (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.'),
18046 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
18047 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
18048 (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.'),
18049 (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.'),
18050 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
18051 (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.'),
18052 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
18053 (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.'),
18054 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
18055 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
18056 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
18057 (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).'),
18058 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
18059 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
18060 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
18061 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
18062 ;
18063 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
18064
18065 CREATE TABLE acq.serial_claim (
18066     id     SERIAL           PRIMARY KEY,
18067     type   INT              NOT NULL REFERENCES acq.claim_type
18068                                      DEFERRABLE INITIALLY DEFERRED,
18069     item    BIGINT          NOT NULL REFERENCES serial.item
18070                                      DEFERRABLE INITIALLY DEFERRED
18071 );
18072
18073 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
18074
18075 CREATE TABLE acq.serial_claim_event (
18076     id             BIGSERIAL        PRIMARY KEY,
18077     type           INT              NOT NULL REFERENCES acq.claim_event_type
18078                                              DEFERRABLE INITIALLY DEFERRED,
18079     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
18080                                              DEFERRABLE INITIALLY DEFERRED,
18081     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
18082     creator        INT              NOT NULL REFERENCES actor.usr
18083                                              DEFERRABLE INITIALLY DEFERRED,
18084     note           TEXT
18085 );
18086
18087 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
18088
18089 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
18090
18091 -- now what about the auditor.*_lifecycle views??
18092
18093 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
18094     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
18095 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
18096     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
18097 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
18098 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
18099
18100 CREATE TABLE asset.call_number_class (
18101     id             bigserial     PRIMARY KEY,
18102     name           TEXT          NOT NULL,
18103     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
18104     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18105 );
18106
18107 COMMENT ON TABLE asset.call_number_class IS $$
18108 Defines the call number normalization database functions in the "normalizer"
18109 column and the tag/subfield combinations to use to lookup the call number in
18110 the "field" column for a given classification scheme. Tag/subfield combinations
18111 are delimited by commas.
18112 $$;
18113
18114 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
18115     ('Generic', 'asset.label_normalizer_generic'),
18116     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
18117     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
18118 ;
18119
18120 -- Generic fields
18121 UPDATE asset.call_number_class
18122     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18123     WHERE id = 1
18124 ;
18125
18126 -- Dewey fields
18127 UPDATE asset.call_number_class
18128     SET field = '080ab,082ab'
18129     WHERE id = 2
18130 ;
18131
18132 -- LC fields
18133 UPDATE asset.call_number_class
18134     SET field = '050ab,055ab'
18135     WHERE id = 3
18136 ;
18137  
18138 ALTER TABLE asset.call_number
18139         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
18140                 REFERENCES asset.call_number_class(id)
18141                 DEFERRABLE INITIALLY DEFERRED;
18142
18143 ALTER TABLE asset.call_number
18144         ADD COLUMN label_sortkey TEXT;
18145
18146 CREATE INDEX asset_call_number_label_sortkey
18147         ON asset.call_number(oils_text_as_bytea(label_sortkey));
18148
18149 ALTER TABLE auditor.asset_call_number_history
18150         ADD COLUMN label_class BIGINT;
18151
18152 ALTER TABLE auditor.asset_call_number_history
18153         ADD COLUMN label_sortkey TEXT;
18154
18155 -- Pick up the new columns in dependent views
18156
18157 DROP VIEW auditor.asset_call_number_lifecycle;
18158
18159 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18160
18161 DROP VIEW auditor.asset_call_number_lifecycle;
18162
18163 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18164
18165 DROP VIEW IF EXISTS stats.fleshed_call_number;
18166
18167 CREATE VIEW stats.fleshed_call_number AS
18168         SELECT  cn.*,
18169             CAST(cn.create_date AS DATE) AS create_date_day,
18170         CAST(cn.edit_date AS DATE) AS edit_date_day,
18171         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
18172         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
18173             rd.item_lang,
18174                 rd.item_type,
18175                 rd.item_form
18176         FROM    asset.call_number cn
18177                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
18178
18179 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
18180 DECLARE
18181     sortkey        TEXT := '';
18182 BEGIN
18183     sortkey := NEW.label_sortkey;
18184
18185     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
18186        quote_literal( NEW.label ) || ')'
18187        FROM asset.call_number_class acnc
18188        WHERE acnc.id = NEW.label_class
18189        INTO sortkey;
18190
18191     NEW.label_sortkey = sortkey;
18192
18193     RETURN NEW;
18194 END;
18195 $func$ LANGUAGE PLPGSQL;
18196
18197 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
18198     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
18199     # thus could probably be considered a derived work, although nothing was
18200     # directly copied - but to err on the safe side of providing attribution:
18201     # Copyright (C) 2007 LibLime
18202     # Licensed under the GPL v2 or later
18203
18204     use strict;
18205     use warnings;
18206
18207     # Converts the callnumber to uppercase
18208     # Strips spaces from start and end of the call number
18209     # Converts anything other than letters, digits, and periods into underscores
18210     # Collapses multiple underscores into a single underscore
18211     my $callnum = uc(shift);
18212     $callnum =~ s/^\s//g;
18213     $callnum =~ s/\s$//g;
18214     $callnum =~ s/[^A-Z0-9_.]/_/g;
18215     $callnum =~ s/_{2,}/_/g;
18216
18217     return $callnum;
18218 $func$ LANGUAGE PLPERLU;
18219
18220 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
18221     # Derived from the Koha C4::ClassSortRoutine::Dewey module
18222     # Copyright (C) 2007 LibLime
18223     # Licensed under the GPL v2 or later
18224
18225     use strict;
18226     use warnings;
18227
18228     my $init = uc(shift);
18229     $init =~ s/^\s+//;
18230     $init =~ s/\s+$//;
18231     $init =~ s!/!!g;
18232     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18233     my @tokens = split /\.|\s+/, $init;
18234     my $digit_group_count = 0;
18235     for (my $i = 0; $i <= $#tokens; $i++) {
18236         if ($tokens[$i] =~ /^\d+$/) {
18237             $digit_group_count++;
18238             if (2 == $digit_group_count) {
18239                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18240                 $tokens[$i] =~ tr/ /0/;
18241             }
18242         }
18243     }
18244     my $key = join("_", @tokens);
18245     $key =~ s/[^\p{IsAlnum}_]//g;
18246
18247     return $key;
18248
18249 $func$ LANGUAGE PLPERLU;
18250
18251 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18252     use strict;
18253     use warnings;
18254
18255     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18256     # The author hopes to upload it to CPAN some day, which would make our lives easier
18257     use Library::CallNumber::LC;
18258
18259     my $callnum = Library::CallNumber::LC->new(shift);
18260     return $callnum->normalize();
18261
18262 $func$ LANGUAGE PLPERLU;
18263
18264 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$
18265 DECLARE
18266     ans RECORD;
18267     trans INT;
18268 BEGIN
18269     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;
18270
18271     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
18272         RETURN QUERY
18273         SELECT  ans.depth,
18274                 ans.id,
18275                 COUNT( av.id ),
18276                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18277                 COUNT( av.id ),
18278                 trans
18279           FROM
18280                 actor.org_unit_descendants(ans.id) d
18281                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18282                 JOIN asset.copy cp ON (cp.id = av.id)
18283           GROUP BY 1,2,6;
18284
18285         IF NOT FOUND THEN
18286             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18287         END IF;
18288
18289     END LOOP;
18290
18291     RETURN;
18292 END;
18293 $f$ LANGUAGE PLPGSQL;
18294
18295 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$
18296 DECLARE
18297     ans RECORD;
18298     trans INT;
18299 BEGIN
18300     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;
18301
18302     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18303         RETURN QUERY
18304         SELECT  -1,
18305                 ans.id,
18306                 COUNT( av.id ),
18307                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18308                 COUNT( av.id ),
18309                 trans
18310           FROM
18311                 actor.org_unit_descendants(ans.id) d
18312                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18313                 JOIN asset.copy cp ON (cp.id = av.id)
18314           GROUP BY 1,2,6;
18315
18316         IF NOT FOUND THEN
18317             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18318         END IF;
18319
18320     END LOOP;
18321
18322     RETURN;
18323 END;
18324 $f$ LANGUAGE PLPGSQL;
18325
18326 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$
18327 DECLARE
18328     ans RECORD;
18329     trans INT;
18330 BEGIN
18331     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;
18332
18333     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
18334         RETURN QUERY
18335         SELECT  ans.depth,
18336                 ans.id,
18337                 COUNT( cp.id ),
18338                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18339                 COUNT( cp.id ),
18340                 trans
18341           FROM
18342                 actor.org_unit_descendants(ans.id) d
18343                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18344                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18345           GROUP BY 1,2,6;
18346
18347         IF NOT FOUND THEN
18348             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18349         END IF;
18350
18351     END LOOP;
18352
18353     RETURN;
18354 END;
18355 $f$ LANGUAGE PLPGSQL;
18356
18357 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$
18358 DECLARE
18359     ans RECORD;
18360     trans INT;
18361 BEGIN
18362     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;
18363
18364     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18365         RETURN QUERY
18366         SELECT  -1,
18367                 ans.id,
18368                 COUNT( cp.id ),
18369                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18370                 COUNT( cp.id ),
18371                 trans
18372           FROM
18373                 actor.org_unit_descendants(ans.id) d
18374                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18375                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18376           GROUP BY 1,2,6;
18377
18378         IF NOT FOUND THEN
18379             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18380         END IF;
18381
18382     END LOOP;
18383
18384     RETURN;
18385 END;
18386 $f$ LANGUAGE PLPGSQL;
18387
18388 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$
18389 BEGIN
18390     IF staff IS TRUE THEN
18391         IF place > 0 THEN
18392             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18393         ELSE
18394             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18395         END IF;
18396     ELSE
18397         IF place > 0 THEN
18398             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18399         ELSE
18400             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18401         END IF;
18402     END IF;
18403
18404     RETURN;
18405 END;
18406 $f$ LANGUAGE PLPGSQL;
18407
18408 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$
18409 DECLARE
18410     ans RECORD;
18411     trans INT;
18412 BEGIN
18413     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;
18414
18415     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
18416         RETURN QUERY
18417         SELECT  ans.depth,
18418                 ans.id,
18419                 COUNT( av.id ),
18420                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18421                 COUNT( av.id ),
18422                 trans
18423           FROM
18424                 actor.org_unit_descendants(ans.id) d
18425                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18426                 JOIN asset.copy cp ON (cp.id = av.id)
18427                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18428           GROUP BY 1,2,6;
18429
18430         IF NOT FOUND THEN
18431             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18432         END IF;
18433
18434     END LOOP;
18435
18436     RETURN;
18437 END;
18438 $f$ LANGUAGE PLPGSQL;
18439
18440 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$
18441 DECLARE
18442     ans RECORD;
18443     trans INT;
18444 BEGIN
18445     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;
18446
18447     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18448         RETURN QUERY
18449         SELECT  -1,
18450                 ans.id,
18451                 COUNT( av.id ),
18452                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18453                 COUNT( av.id ),
18454                 trans
18455           FROM
18456                 actor.org_unit_descendants(ans.id) d
18457                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18458                 JOIN asset.copy cp ON (cp.id = av.id)
18459                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18460           GROUP BY 1,2,6;
18461
18462         IF NOT FOUND THEN
18463             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18464         END IF;
18465
18466     END LOOP;
18467
18468     RETURN;
18469 END;
18470 $f$ LANGUAGE PLPGSQL;
18471
18472 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$
18473 DECLARE
18474     ans RECORD;
18475     trans INT;
18476 BEGIN
18477     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;
18478
18479     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
18480         RETURN QUERY
18481         SELECT  ans.depth,
18482                 ans.id,
18483                 COUNT( cp.id ),
18484                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18485                 COUNT( cp.id ),
18486                 trans
18487           FROM
18488                 actor.org_unit_descendants(ans.id) d
18489                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18490                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18491                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18492           GROUP BY 1,2,6;
18493
18494         IF NOT FOUND THEN
18495             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18496         END IF;
18497
18498     END LOOP;
18499
18500     RETURN;
18501 END;
18502 $f$ LANGUAGE PLPGSQL;
18503
18504 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$
18505 DECLARE
18506     ans RECORD;
18507     trans INT;
18508 BEGIN
18509     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;
18510
18511     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18512         RETURN QUERY
18513         SELECT  -1,
18514                 ans.id,
18515                 COUNT( cp.id ),
18516                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18517                 COUNT( cp.id ),
18518                 trans
18519           FROM
18520                 actor.org_unit_descendants(ans.id) d
18521                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18522                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18523                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18524           GROUP BY 1,2,6;
18525
18526         IF NOT FOUND THEN
18527             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18528         END IF;
18529
18530     END LOOP;
18531
18532     RETURN;
18533 END;
18534 $f$ LANGUAGE PLPGSQL;
18535
18536 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$
18537 BEGIN
18538     IF staff IS TRUE THEN
18539         IF place > 0 THEN
18540             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18541         ELSE
18542             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18543         END IF;
18544     ELSE
18545         IF place > 0 THEN
18546             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18547         ELSE
18548             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18549         END IF;
18550     END IF;
18551
18552     RETURN;
18553 END;
18554 $f$ LANGUAGE PLPGSQL;
18555
18556 -- No transaction is required
18557
18558 -- Triggers on the vandelay.queued_*_record tables delete entries from
18559 -- the associated vandelay.queued_*_record_attr tables based on the record's
18560 -- ID; create an index on that column to avoid sequential scans for each
18561 -- queued record that is deleted
18562 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18563 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18564
18565 -- Avoid sequential scans for queue retrieval operations by providing an
18566 -- index on the queue column
18567 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18568 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18569
18570 -- Start picking up call number label prefixes and suffixes
18571 -- from asset.copy_location
18572 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18573 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18574
18575 DROP VIEW auditor.asset_copy_lifecycle;
18576
18577 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18578
18579 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18580
18581 -- Let's not break existing reports
18582 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18583 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18584
18585 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18586 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18587 SELECT  r.id,
18588     r.fingerprint,
18589     r.quality,
18590     r.tcn_source,
18591     r.tcn_value,
18592     FIRST(title.value) AS title,
18593     FIRST(author.value) AS author,
18594     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18595     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18596     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18597     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18598   FROM  biblio.record_entry r
18599     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18600     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18601     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18602     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18603     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18604     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18605   GROUP BY 1,2,3,4,5;
18606
18607 -- Correct the ISSN array definition for reporter.simple_record
18608
18609 CREATE OR REPLACE VIEW reporter.simple_record AS
18610 SELECT  r.id,
18611         s.metarecord,
18612         r.fingerprint,
18613         r.quality,
18614         r.tcn_source,
18615         r.tcn_value,
18616         title.value AS title,
18617         uniform_title.value AS uniform_title,
18618         author.value AS author,
18619         publisher.value AS publisher,
18620         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18621         series_title.value AS series_title,
18622         series_statement.value AS series_statement,
18623         summary.value AS summary,
18624         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18625         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18626         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18627         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18628         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18629         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18630         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18631         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
18632   FROM  biblio.record_entry r
18633         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18634         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18635         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18636         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18637         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18638         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18639         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18640         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18641         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')
18642         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18643         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18644   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18645
18646 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18647     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
18648 $$ LANGUAGE SQL;
18649
18650 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
18651
18652     DELETE FROM reporter.materialized_simple_record;
18653
18654     INSERT INTO reporter.materialized_simple_record
18655         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
18656         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
18657
18658     CREATE TRIGGER bbb_simple_rec_trigger
18659         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
18660         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
18661
18662 $$ LANGUAGE SQL;
18663
18664 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18665 BEGIN
18666     IF TG_OP = 'DELETE' THEN
18667         PERFORM reporter.simple_rec_delete(NEW.id);
18668     ELSE
18669         PERFORM reporter.simple_rec_update(NEW.id);
18670     END IF;
18671
18672     RETURN NEW;
18673 END;
18674 $func$ LANGUAGE PLPGSQL;
18675
18676 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 ();
18677
18678 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18679
18680 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18681
18682 UPDATE config.org_unit_setting_type
18683     SET view_perm = (SELECT id FROM permission.perm_list
18684         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18685     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18686
18687 UPDATE config.org_unit_setting_type
18688     SET update_perm = (SELECT id FROM permission.perm_list
18689         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18690     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18691
18692 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18693     VALUES (
18694         'opac.fully_compressed_serial_holdings',
18695         'OPAC: Use fully compressed serial holdings',
18696         'Show fully compressed serial holdings for all libraries at and below
18697         the current context unit',
18698         'bool'
18699     );
18700
18701 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18702     use strict;
18703     use warnings;
18704
18705     use utf8;
18706     use MARC::Record;
18707     use MARC::File::XML (BinaryEncoding => 'UTF8');
18708     use UUID::Tiny ':std';
18709
18710     my $xml = shift() or return undef;
18711
18712     my $r;
18713
18714     # Prevent errors in XML parsing from blowing out ungracefully
18715     eval {
18716         $r = MARC::Record->new_from_xml( $xml );
18717         1;
18718     } or do {
18719        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18720     };
18721
18722     if (!$r) {
18723        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18724     }
18725
18726     # From http://www.loc.gov/standards/sourcelist/subject.html
18727     my $thes_code_map = {
18728         a => 'lcsh',
18729         b => 'lcshac',
18730         c => 'mesh',
18731         d => 'nal',
18732         k => 'cash',
18733         n => 'notapplicable',
18734         r => 'aat',
18735         s => 'sears',
18736         v => 'rvm',
18737     };
18738
18739     # Default to "No attempt to code" if the leader is horribly broken
18740     my $fixed_field = $r->field('008');
18741     my $thes_char = '|';
18742     if ($fixed_field) { 
18743         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18744     }
18745
18746     my $thes_code = 'UNDEFINED';
18747
18748     if ($thes_char eq 'z') {
18749         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18750         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18751     } elsif ($thes_code_map->{$thes_char}) {
18752         $thes_code = $thes_code_map->{$thes_char};
18753     }
18754
18755     my $auth_txt = '';
18756     my $head = $r->field('1..');
18757     if ($head) {
18758         # Concatenate all of these subfields together, prefixed by their code
18759         # to prevent collisions along the lines of "Fiction, North Carolina"
18760         foreach my $sf ($head->subfields()) {
18761             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18762         }
18763     }
18764     
18765     if ($auth_txt) {
18766         my $stmt = spi_prepare('SELECT public.naco_normalize($1) AS norm_text', 'TEXT');
18767         my $result = spi_exec_prepared($stmt, $auth_txt);
18768         my $norm_txt = $result->{rows}[0]->{norm_text};
18769         spi_freeplan($stmt);
18770         undef($stmt);
18771         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18772     }
18773
18774     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18775 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18776
18777 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18778 /**
18779 * Extract the authority heading, thesaurus, and NACO-normalized values
18780 * from an authority record. The primary purpose is to build a unique
18781 * index to defend against duplicated authority records from the same
18782 * thesaurus.
18783 */
18784 $$;
18785
18786 DROP INDEX authority.authority_record_unique_tcn;
18787 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18788 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18789
18790 ALTER TABLE acq.provider_contact
18791         ALTER COLUMN name SET NOT NULL;
18792
18793 ALTER TABLE actor.stat_cat
18794         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18795
18796 -- Per Robert Soulliere, it can be necessary in some cases to clean out bad
18797 -- data from action.reservation_transit_copy before applying the missing
18798 -- fkeys below.
18799 -- https://bugs.launchpad.net/evergreen/+bug/721450
18800 DELETE FROM action.reservation_transit_copy
18801     WHERE target_copy NOT IN (SELECT id FROM booking.resource);
18802 -- In the same spirit as the above delete, this can only fix bad data.
18803 UPDATE action.reservation_transit_copy
18804     SET reservation = NULL
18805     WHERE reservation NOT IN (SELECT id FROM booking.reservation);
18806
18807 -- Recreate some foreign keys that were somehow dropped, probably
18808 -- by some kind of cascade from an inherited table:
18809
18810 ALTER TABLE action.reservation_transit_copy
18811         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18812                 REFERENCES booking.resource(id)
18813                 ON DELETE CASCADE
18814                 DEFERRABLE INITIALLY DEFERRED,
18815         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18816                 REFERENCES booking.reservation(id)
18817                 ON DELETE SET NULL
18818                 DEFERRABLE INITIALLY DEFERRED;
18819
18820 CREATE INDEX user_bucket_item_target_user_idx
18821         ON container.user_bucket_item ( target_user );
18822
18823 CREATE INDEX m_c_t_collector_idx
18824         ON money.collections_tracker ( collector );
18825
18826 CREATE INDEX aud_actor_usr_address_hist_id_idx
18827         ON auditor.actor_usr_address_history ( id );
18828
18829 CREATE INDEX aud_actor_usr_hist_id_idx
18830         ON auditor.actor_usr_history ( id );
18831
18832 CREATE INDEX aud_asset_cn_hist_creator_idx
18833         ON auditor.asset_call_number_history ( creator );
18834
18835 CREATE INDEX aud_asset_cn_hist_editor_idx
18836         ON auditor.asset_call_number_history ( editor );
18837
18838 CREATE INDEX aud_asset_cp_hist_creator_idx
18839         ON auditor.asset_copy_history ( creator );
18840
18841 CREATE INDEX aud_asset_cp_hist_editor_idx
18842         ON auditor.asset_copy_history ( editor );
18843
18844 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18845         ON auditor.biblio_record_entry_history ( creator );
18846
18847 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18848         ON auditor.biblio_record_entry_history ( editor );
18849
18850 CREATE TABLE action.hold_request_note (
18851
18852     id     BIGSERIAL PRIMARY KEY,
18853     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18854                               ON DELETE CASCADE
18855                               DEFERRABLE INITIALLY DEFERRED,
18856     title  TEXT      NOT NULL,
18857     body   TEXT      NOT NULL,
18858     slip   BOOL      NOT NULL DEFAULT FALSE,
18859     pub    BOOL      NOT NULL DEFAULT FALSE,
18860     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18861
18862 );
18863 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18864
18865 -- Tweak a constraint to add a CASCADE
18866
18867 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18868
18869 ALTER TABLE action.hold_notification
18870         ADD CONSTRAINT hold_notification_hold_fkey
18871                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18872                 ON DELETE CASCADE
18873                 DEFERRABLE INITIALLY DEFERRED;
18874
18875 CREATE TRIGGER asset_label_sortkey_trigger
18876     BEFORE UPDATE OR INSERT ON asset.call_number
18877     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18878
18879 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18880 RETURNS VOID AS $$
18881 --
18882 -- Delete expired circulation bucket items for all users that have
18883 -- a setting for patron.max_reading_list_interval.
18884 --
18885 DECLARE
18886     today        TIMESTAMP WITH TIME ZONE;
18887     threshold    TIMESTAMP WITH TIME ZONE;
18888         usr_setting  RECORD;
18889 BEGIN
18890         SELECT date_trunc( 'day', now() ) INTO today;
18891         --
18892         FOR usr_setting in
18893                 SELECT
18894                         usr,
18895                         value
18896                 FROM
18897                         actor.usr_setting
18898                 WHERE
18899                         name = 'patron.max_reading_list_interval'
18900         LOOP
18901                 --
18902                 -- Make sure the setting is a valid interval
18903                 --
18904                 BEGIN
18905                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18906                 EXCEPTION
18907                         WHEN OTHERS THEN
18908                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18909                                         usr_setting.usr, usr_setting.value;
18910                                 CONTINUE;
18911                 END;
18912                 --
18913                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18914                 --
18915         DELETE FROM container.copy_bucket_item
18916         WHERE
18917                 bucket IN
18918                 (
18919                     SELECT
18920                         id
18921                     FROM
18922                         container.copy_bucket
18923                     WHERE
18924                         owner = usr_setting.usr
18925                         AND btype = 'circ_history'
18926                 )
18927                 AND create_time < threshold;
18928         END LOOP;
18929         --
18930 END;
18931 $$ LANGUAGE plpgsql;
18932
18933 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18934 /*
18935  * Delete expired circulation bucket items for all users that have
18936  * a setting for patron.max_reading_list_interval.
18937 */
18938 $$;
18939
18940 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18941          ac_usr IN INTEGER
18942 ) RETURNS VOID AS $$
18943 --
18944 -- Delete old circulation bucket items for a specified user.
18945 -- "Old" means older than the interval specified by a
18946 -- user-level setting, if it is so specified.
18947 --
18948 DECLARE
18949     threshold TIMESTAMP WITH TIME ZONE;
18950 BEGIN
18951         -- Sanity check
18952         IF ac_usr IS NULL THEN
18953                 RETURN;
18954         END IF;
18955         -- Determine the threshold date that defines "old".  Subtract the
18956         -- interval from the system date, then truncate to midnight.
18957         SELECT
18958                 date_trunc( 
18959                         'day',
18960                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18961                 )
18962         INTO
18963                 threshold
18964         FROM
18965                 actor.usr_setting
18966         WHERE
18967                 usr = ac_usr
18968                 AND name = 'patron.max_reading_list_interval';
18969         --
18970         IF threshold is null THEN
18971                 -- No interval defined; don't delete anything
18972                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18973                 return;
18974         END IF;
18975         --
18976         -- RAISE NOTICE 'Date threshold: %', threshold;
18977         --
18978         -- Threshold found; do the delete
18979         delete from container.copy_bucket_item
18980         where
18981                 bucket in
18982                 (
18983                         select
18984                                 id
18985                         from
18986                                 container.copy_bucket
18987                         where
18988                                 owner = ac_usr
18989                                 and btype = 'circ_history'
18990                 )
18991                 and create_time < threshold;
18992         --
18993         RETURN;
18994 END;
18995 $$ LANGUAGE plpgsql;
18996
18997 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18998 /*
18999  * Delete old circulation bucket items for a specified user.
19000  * "Old" means older than the interval specified by a
19001  * user-level setting, if it is so specified.
19002 */
19003 $$;
19004
19005 CREATE OR REPLACE VIEW reporter.hold_request_record AS
19006 SELECT  id,
19007     target,
19008     hold_type,
19009     CASE
19010         WHEN hold_type = 'T'
19011             THEN target
19012         WHEN hold_type = 'I'
19013             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
19014         WHEN hold_type = 'V'
19015             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
19016         WHEN hold_type IN ('C','R','F')
19017             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
19018         WHEN hold_type = 'M'
19019             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
19020     END AS bib_record
19021   FROM  action.hold_request ahr;
19022
19023 UPDATE  metabib.rec_descriptor
19024   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
19025         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
19026
19027 -- Change some ints to bigints:
19028
19029 ALTER TABLE container.biblio_record_entry_bucket_item
19030         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
19031
19032 ALTER TABLE vandelay.queued_bib_record
19033         ALTER COLUMN imported_as SET DATA TYPE bigint;
19034
19035 ALTER TABLE action.hold_copy_map
19036         ALTER COLUMN id SET DATA TYPE bigint;
19037
19038 -- Make due times get pushed to 23:59:59 on insert OR update
19039 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
19040 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
19041
19042 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
19043 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
19044 WHERE NOT EXISTS (
19045     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
19046 );  
19047
19048 -- '@@' auto-placeholder barcode support
19049 CREATE OR REPLACE FUNCTION asset.autogenerate_placeholder_barcode ( ) RETURNS TRIGGER AS $f$
19050 BEGIN
19051         IF NEW.barcode LIKE '@@%' THEN
19052                 NEW.barcode := '@@' || NEW.id;
19053         END IF;
19054         RETURN NEW;
19055 END;
19056 $f$ LANGUAGE PLPGSQL;
19057
19058 CREATE TRIGGER autogenerate_placeholder_barcode
19059         BEFORE INSERT OR UPDATE ON asset.copy
19060         FOR EACH ROW EXECUTE PROCEDURE asset.autogenerate_placeholder_barcode();
19061
19062 COMMIT;
19063
19064 -- Some operations go outside of the transaction, because they may
19065 -- legitimately fail.
19066
19067 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
19068 \qecho doesn't exist; ignore those errors if they occur.
19069
19070 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
19071
19072 ALTER TABLE auditor.action_hold_request_history
19073 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
19074
19075 ALTER TABLE auditor.action_hold_request_history
19076 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
19077
19078 \qecho Outside of the transaction: adding indexes that may or may not exist.
19079 \qecho If any of these CREATE INDEX statements fails because the index already
19080 \qecho exists, ignore the failure.
19081
19082 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
19083 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
19084 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
19085 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
19086 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
19087 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
19088 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
19089 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
19090 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
19091 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
19092 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
19093 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
19094 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
19095 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
19096 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
19097 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
19098 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
19099 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
19100 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
19101 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
19102 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
19103 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
19104 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
19105 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
19106 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
19107 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
19108 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
19109 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
19110 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
19111 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
19112 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
19113 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
19114 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
19115
19116 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
19117
19118 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
19119
19120 \qecho If the following CREATE INDEX fails, It will be necessary to do some
19121 \qecho data cleanup as described in the comments.
19122
19123 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
19124     ON authority.record_entry (authority.normalize_heading(marc))
19125         WHERE deleted IS FALSE or deleted = FALSE;
19126
19127 -- If the unique index fails, uncomment the following to create
19128 -- a regular index that will help find the duplicates in a hurry:
19129 --CREATE INDEX by_heading_and_thesaurus
19130 --    ON authority.record_entry (authority.normalize_heading(marc))
19131 --    WHERE deleted IS FALSE or deleted = FALSE
19132 --;
19133
19134 -- Then find the duplicates like so to get an idea of how much
19135 -- pain you're looking at to clean things up:
19136 --SELECT id, authority.normalize_heading(marc)
19137 --    FROM authority.record_entry
19138 --    WHERE authority.normalize_heading(marc) IN (
19139 --        SELECT authority.normalize_heading(marc)
19140 --        FROM authority.record_entry
19141 --        GROUP BY authority.normalize_heading(marc)
19142 --        HAVING COUNT(*) > 1
19143 --    )
19144 --;
19145
19146 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
19147 -- statement succeeds, drop the temporary index to avoid unnecessary
19148 -- duplication:
19149 -- DROP INDEX authority.by_heading_and_thesaurus;
19150
19151 -- 0448.data.trigger.circ.staff_age_to_lost.sql
19152
19153 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
19154     (   'circ.staff_age_to_lost',
19155         'circ', 
19156         oils_i18n_gettext(
19157             'circ.staff_age_to_lost',
19158             'An overdue circulation should be aged to a Lost status.',
19159             'ath',
19160             'description'
19161         ), 
19162         TRUE
19163     )
19164 ;
19165
19166 INSERT INTO action_trigger.event_definition (
19167         id,
19168         active,
19169         owner,
19170         name,
19171         hook,
19172         validator,
19173         reactor,
19174         delay_field
19175     ) VALUES (
19176         36,
19177         FALSE,
19178         1,
19179         'circ.staff_age_to_lost',
19180         'circ.staff_age_to_lost',
19181         'CircIsOverdue',
19182         'MarkItemLost',
19183         'due_date'
19184     )
19185 ;
19186
19187 -- Speed up item-age browse axis (new books feed)
19188 CREATE INDEX cp_create_date  ON asset.copy (create_date);
19189
19190 -- Speed up call number browsing
19191 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;
19192
19193 \qecho Upgrade script completed.
19194 \qecho But wait, there's more: please run reingest-1.6-2.0.pl
19195 \qecho in order to create an SQL script to run to partially reindex 
19196 \qecho the bib records; this is required to make the new facet
19197 \qecho sidebar in OPAC search results work and to upgrade the keyword 
19198 \qecho indexes to use the revised NACO normalization routine.