]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/1.6.1-2.0-upgrade-db.sql
Address the call number browsing performance problem raised in LP 690242
[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 ('0469');
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 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     use Unicode::Normalize;
7007     use Encode;
7008
7009     # When working with Unicode data, the first step is to decode it to
7010     # a byte string; after that, lowercasing is safe
7011     my $txt = lc(decode_utf8(shift));
7012     my $sf = shift;
7013
7014     $txt = NFD($txt);
7015     $txt =~ s/\pM+//go; # Remove diacritics
7016
7017     # remove non-combining diacritics
7018     # this list of characters follows the NACO normalization spec,
7019     # but a looser but more comprehensive version might be
7020     # $txt =~ s/\pLm+//go;
7021     $txt =~ tr/\x{02B9}\x{02BA}\x{02BB}\x{02BC}//d;
7022
7023     $txt =~ s/\xE6/AE/go;   # Convert ae digraph
7024     $txt =~ s/\x{153}/OE/go;# Convert oe digraph
7025     $txt =~ s/\xFE/TH/go;   # Convert Icelandic thorn
7026
7027     $txt =~ tr/\x{2070}\x{2071}\x{2072}\x{2073}\x{2074}\x{2075}\x{2076}\x{2077}\x{2078}\x{2079}\x{207A}\x{207B}/0123456789+-/;# Convert superscript numbers
7028     $txt =~ tr/\x{2080}\x{2081}\x{2082}\x{2083}\x{2084}\x{2085}\x{2086}\x{2087}\x{2088}\x{2089}\x{208A}\x{208B}/0123456889+-/;# Convert subscript numbers
7029
7030     $txt =~ tr/\x{0251}\x{03B1}\x{03B2}\x{0262}\x{03B3}/AABGG/;     # Convert Latin and Greek
7031     $txt =~ tr/\x{2113}\xF0\x{111}\!\"\(\)\-\{\}\<\>\;\:\.\?\xA1\xBF\/\\\@\*\%\=\xB1\+\xAE\xA9\x{2117}\$\xA3\x{FFE1}\xB0\^\_\~\`/LDD /; # Convert Misc
7032     $txt =~ tr/\'\[\]\|//d;                         # Remove Misc
7033
7034     if ($sf && $sf =~ /^a/o) {
7035         my $commapos = index($txt,',');
7036         if ($commapos > -1) {
7037             if ($commapos != length($txt) - 1) {
7038                 my @list = split /,/, $txt;
7039                 my $first = shift @list;
7040                 $txt = $first . ',' . join(' ', @list);
7041             } else {
7042                 $txt =~ s/,/ /go;
7043             }
7044         }
7045     } else {
7046         $txt =~ s/,/ /go;
7047     }
7048
7049     $txt =~ s/\s+/ /go; # Compress multiple spaces
7050     $txt =~ s/^\s+//o;  # Remove leading space
7051     $txt =~ s/\s+$//o;  # Remove trailing space
7052
7053     # Encoding the outgoing string is good practice, but not strictly
7054     # necessary in this case because we've stripped everything from it
7055     return encode_utf8($txt);
7056 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7057
7058 -- Some handy functions, based on existing ones, to provide optional ingest normalization
7059
7060 CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7061         SELECT SUBSTRING($1,$2);
7062 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7063
7064 CREATE OR REPLACE FUNCTION public.right_trunc( TEXT, INT ) RETURNS TEXT AS $func$
7065         SELECT SUBSTRING($1,1,$2);
7066 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7067
7068 CREATE OR REPLACE FUNCTION public.naco_normalize_keep_comma( TEXT ) RETURNS TEXT AS $func$
7069         SELECT public.naco_normalize($1,'a');
7070 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7071
7072 CREATE OR REPLACE FUNCTION public.split_date_range( TEXT ) RETURNS TEXT AS $func$
7073         SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );
7074 $func$ LANGUAGE SQL STRICT IMMUTABLE;
7075
7076 -- And ... a table in which to register them
7077
7078 CREATE TABLE config.index_normalizer (
7079         id              SERIAL  PRIMARY KEY,
7080         name            TEXT    UNIQUE NOT NULL,
7081         description     TEXT,
7082         func            TEXT    NOT NULL,
7083         param_count     INT     NOT NULL DEFAULT 0
7084 );
7085
7086 CREATE TABLE config.metabib_field_index_norm_map (
7087         id      SERIAL  PRIMARY KEY,
7088         field   INT     NOT NULL REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7089         norm    INT     NOT NULL REFERENCES config.index_normalizer (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
7090         params  TEXT,
7091         pos     INT     NOT NULL DEFAULT 0
7092 );
7093
7094 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7095         'NACO Normalize',
7096         'Apply NACO normalization rules to the extracted text.  See http://www.loc.gov/catdir/pcc/naco/normrule-2.html for details.',
7097         'naco_normalize',
7098         0
7099 );
7100
7101 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7102         'Normalize date range',
7103         'Split date ranges in the form of "XXXX-YYYY" into "XXXX YYYY" for proper index.',
7104         'split_date_range',
7105         1
7106 );
7107
7108 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7109         'NACO Normalize -- retain first comma',
7110         '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.',
7111         'naco_normalize_keep_comma',
7112         0
7113 );
7114
7115 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7116         'Strip Diacritics',
7117         'Convert text to NFD form and remove non-spacing combining marks.',
7118         'remove_diacritics',
7119         0
7120 );
7121
7122 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7123         'Up-case',
7124         'Convert text upper case.',
7125         'uppercase',
7126         0
7127 );
7128
7129 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7130         'Down-case',
7131         'Convert text lower case.',
7132         'lowercase',
7133         0
7134 );
7135
7136 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7137         'Extract Dewey-like number',
7138         'Extract a string of numeric characters ther resembles a DDC number.',
7139         'call_number_dewey',
7140         0
7141 );
7142
7143 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7144         'Left truncation',
7145         'Discard the specified number of characters from the left side of the string.',
7146         'left_trunc',
7147         1
7148 );
7149
7150 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7151         'Right truncation',
7152         'Include only the specified number of characters from the left side of the string.',
7153         'right_trunc',
7154         1
7155 );
7156
7157 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
7158         'First word',
7159         'Include only the first space-separated word of a string.',
7160         'first_word',
7161         0
7162 );
7163
7164 INSERT INTO config.metabib_field_index_norm_map (field,norm)
7165         SELECT  m.id,
7166                 i.id
7167           FROM  config.metabib_field m,
7168                 config.index_normalizer i
7169           WHERE i.func IN ('naco_normalize','split_date_range');
7170
7171 CREATE OR REPLACE FUNCTION oils_tsearch2 () RETURNS TRIGGER AS $$
7172 DECLARE
7173     normalizer      RECORD;
7174     value           TEXT := '';
7175 BEGIN
7176
7177     value := NEW.value;
7178
7179     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7180         FOR normalizer IN
7181             SELECT  n.func AS func,
7182                     n.param_count AS param_count,
7183                     m.params AS params
7184               FROM  config.index_normalizer n
7185                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7186               WHERE field = NEW.field AND m.pos < 0
7187               ORDER BY m.pos LOOP
7188                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7189                     quote_literal( value ) ||
7190                     CASE
7191                         WHEN normalizer.param_count > 0
7192                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7193                             ELSE ''
7194                         END ||
7195                     ')' INTO value;
7196
7197         END LOOP;
7198
7199         NEW.value := value;
7200     END IF;
7201
7202     IF NEW.index_vector = ''::tsvector THEN
7203         RETURN NEW;
7204     END IF;
7205
7206     IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
7207         FOR normalizer IN
7208             SELECT  n.func AS func,
7209                     n.param_count AS param_count,
7210                     m.params AS params
7211               FROM  config.index_normalizer n
7212                     JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
7213               WHERE field = NEW.field AND m.pos >= 0
7214               ORDER BY m.pos LOOP
7215                 EXECUTE 'SELECT ' || normalizer.func || '(' ||
7216                     quote_literal( value ) ||
7217                     CASE
7218                         WHEN normalizer.param_count > 0
7219                             THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
7220                             ELSE ''
7221                         END ||
7222                     ')' INTO value;
7223
7224         END LOOP;
7225     END IF;
7226
7227     IF REGEXP_REPLACE(VERSION(),E'^.+?(\\d+\\.\\d+).*?$',E'\\1')::FLOAT > 8.2 THEN
7228         NEW.index_vector = to_tsvector((TG_ARGV[0])::regconfig, value);
7229     ELSE
7230         NEW.index_vector = to_tsvector(TG_ARGV[0], value);
7231     END IF;
7232
7233     RETURN NEW;
7234 END;
7235 $$ LANGUAGE PLPGSQL;
7236
7237 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML, $3 )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7238
7239 CREATE OR REPLACE FUNCTION oils_xpath ( TEXT, TEXT ) RETURNS TEXT[] AS 'SELECT XPATH( $1, $2::XML )::TEXT[];' LANGUAGE SQL IMMUTABLE;
7240
7241 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7242     SELECT  ARRAY_TO_STRING(
7243                 oils_xpath(
7244                     $1 ||
7245                         CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
7246                     $2,
7247                     $4
7248                 ),
7249                 $3
7250             );
7251 $func$ LANGUAGE SQL IMMUTABLE;
7252
7253 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
7254     SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );
7255 $func$ LANGUAGE SQL IMMUTABLE;
7256
7257 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT, ANYARRAY ) RETURNS TEXT AS $func$
7258     SELECT oils_xpath_string( $1, $2, '', $3 );
7259 $func$ LANGUAGE SQL IMMUTABLE;
7260
7261 CREATE OR REPLACE FUNCTION oils_xpath_string ( TEXT, TEXT ) RETURNS TEXT AS $func$
7262     SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );
7263 $func$ LANGUAGE SQL IMMUTABLE;
7264
7265 CREATE TYPE metabib.field_entry_template AS (
7266         field_class     TEXT,
7267         field           INT,
7268         source          BIGINT,
7269         value           TEXT
7270 );
7271
7272 CREATE OR REPLACE FUNCTION oils_xslt_process(TEXT, TEXT) RETURNS TEXT AS $func$
7273   use strict;
7274
7275   use XML::LibXSLT;
7276   use XML::LibXML;
7277
7278   my $doc = shift;
7279   my $xslt = shift;
7280
7281   # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
7282   # methods of parsing XML documents and stylesheets, in the hopes of broader
7283   # compatibility with distributions
7284   my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();
7285
7286   # Cache the XML parser, if we do not already have one
7287   $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
7288     unless ($_SHARED{'_xslt_process'}{parsers}{xml});
7289
7290   my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();
7291
7292   # Cache the XSLT processor, if we do not already have one
7293   $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
7294     unless ($_SHARED{'_xslt_process'}{parsers}{xslt});
7295
7296   my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
7297     $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );
7298
7299   $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
7300     unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});
7301
7302   return $stylesheet->output_string(
7303     $stylesheet->transform(
7304       $parser->parse_string($doc)
7305     )
7306   );
7307
7308 $func$ LANGUAGE 'plperlu' STRICT IMMUTABLE;
7309
7310 -- Add two columns so that the following function will compile.
7311 -- Eventually the label column will be NOT NULL, but not yet.
7312 ALTER TABLE config.metabib_field ADD COLUMN label TEXT;
7313 ALTER TABLE config.metabib_field ADD COLUMN facet_xpath TEXT;
7314
7315 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
7316 DECLARE
7317     bib     biblio.record_entry%ROWTYPE;
7318     idx     config.metabib_field%ROWTYPE;
7319     xfrm        config.xml_transform%ROWTYPE;
7320     prev_xfrm   TEXT;
7321     transformed_xml TEXT;
7322     xml_node    TEXT;
7323     xml_node_list   TEXT[];
7324     facet_text  TEXT;
7325     raw_text    TEXT;
7326     curr_text   TEXT;
7327     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
7328     output_row  metabib.field_entry_template%ROWTYPE;
7329 BEGIN
7330
7331     -- Get the record
7332     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7333
7334     -- Loop over the indexing entries
7335     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
7336
7337         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
7338
7339         -- See if we can skip the XSLT ... it's expensive
7340         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
7341             -- Can't skip the transform
7342             IF xfrm.xslt <> '---' THEN
7343                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
7344             ELSE
7345                 transformed_xml := bib.marc;
7346             END IF;
7347
7348             prev_xfrm := xfrm.name;
7349         END IF;
7350
7351         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7352
7353         raw_text := NULL;
7354         FOR xml_node IN SELECT x FROM explode_array(xml_node_list) AS x LOOP
7355             CONTINUE WHEN xml_node !~ E'^\\s*<';
7356
7357             curr_text := ARRAY_TO_STRING(
7358                 oils_xpath( '//text()',
7359                     REGEXP_REPLACE( -- This escapes all &s not followed by "amp;".  Data ise returned from oils_xpath (above) in UTF-8, not entity encoded
7360                         REGEXP_REPLACE( -- This escapes embeded <s
7361                             xml_node,
7362                             $re$(>[^<]+)(<)([^>]+<)$re$,
7363                             E'\\1&lt;\\3',
7364                             'g'
7365                         ),
7366                         '&(?!amp;)',
7367                         '&amp;',
7368                         'g'
7369                     )
7370                 ),
7371                 ' '
7372             );
7373
7374             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
7375
7376             IF raw_text IS NOT NULL THEN
7377                 raw_text := raw_text || joiner;
7378             END IF;
7379
7380             raw_text := COALESCE(raw_text,'') || curr_text;
7381
7382             -- insert raw node text for faceting
7383             IF idx.facet_field THEN
7384
7385                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
7386                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
7387                 ELSE
7388                     facet_text := curr_text;
7389                 END IF;
7390
7391                 output_row.field_class = idx.field_class;
7392                 output_row.field = -1 * idx.id;
7393                 output_row.source = rid;
7394                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
7395
7396                 RETURN NEXT output_row;
7397             END IF;
7398
7399         END LOOP;
7400
7401         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
7402
7403         -- insert combined node text for searching
7404         IF idx.search_field THEN
7405             output_row.field_class = idx.field_class;
7406             output_row.field = idx.id;
7407             output_row.source = rid;
7408             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
7409
7410             RETURN NEXT output_row;
7411         END IF;
7412
7413     END LOOP;
7414
7415 END;
7416 $func$ LANGUAGE PLPGSQL;
7417
7418 -- default to a space joiner
7419 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( BIGINT ) RETURNS SETOF metabib.field_entry_template AS $func$
7420         SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
7421 $func$ LANGUAGE SQL;
7422
7423 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
7424
7425 use MARC::Record;
7426 use MARC::File::XML (BinaryEncoding => 'UTF-8');
7427
7428 my $xml = shift;
7429 my $r = MARC::Record->new_from_xml( $xml );
7430
7431 return_next( { tag => 'LDR', value => $r->leader } );
7432
7433 for my $f ( $r->fields ) {
7434     if ($f->is_control_field) {
7435         return_next({ tag => $f->tag, value => $f->data });
7436     } else {
7437         for my $s ($f->subfields) {
7438             return_next({
7439                 tag      => $f->tag,
7440                 ind1     => $f->indicator(1),
7441                 ind2     => $f->indicator(2),
7442                 subfield => $s->[0],
7443                 value    => $s->[1]
7444             });
7445
7446             if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
7447                 my $trim = $f->indicator(2) || 0;
7448                 return_next({
7449                     tag      => 'tnf',
7450                     ind1     => $f->indicator(1),
7451                     ind2     => $f->indicator(2),
7452                     subfield => 'a',
7453                     value    => substr( $s->[1], $trim )
7454                 });
7455             }
7456         }
7457     }
7458 }
7459
7460 return undef;
7461
7462 $func$ LANGUAGE PLPERLU;
7463
7464 CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
7465 DECLARE
7466     bib biblio.record_entry%ROWTYPE;
7467     output  metabib.full_rec%ROWTYPE;
7468     field   RECORD;
7469 BEGIN
7470     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
7471
7472     FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
7473         output.record := rid;
7474         output.ind1 := field.ind1;
7475         output.ind2 := field.ind2;
7476         output.tag := field.tag;
7477         output.subfield := field.subfield;
7478         IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
7479             output.value := naco_normalize(field.value, field.subfield);
7480         ELSE
7481             output.value := field.value;
7482         END IF;
7483
7484         CONTINUE WHEN output.value IS NULL;
7485
7486         RETURN NEXT output;
7487     END LOOP;
7488 END;
7489 $func$ LANGUAGE PLPGSQL;
7490
7491 -- functions to create auditor objects
7492
7493 CREATE FUNCTION auditor.create_auditor_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7494 BEGIN
7495     EXECUTE $$
7496         CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
7497     $$;
7498         RETURN TRUE;
7499 END;
7500 $creator$ LANGUAGE 'plpgsql';
7501
7502 CREATE FUNCTION auditor.create_auditor_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7503 BEGIN
7504     EXECUTE $$
7505         CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
7506             audit_id    BIGINT                          PRIMARY KEY,
7507             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
7508             audit_action        TEXT                            NOT NULL,
7509             LIKE $$ || sch || $$.$$ || tbl || $$
7510         );
7511     $$;
7512         RETURN TRUE;
7513 END;
7514 $creator$ LANGUAGE 'plpgsql';
7515
7516 CREATE FUNCTION auditor.create_auditor_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7517 BEGIN
7518     EXECUTE $$
7519         CREATE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
7520         RETURNS TRIGGER AS $func$
7521         BEGIN
7522             INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history
7523                 SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
7524                     now(),
7525                     SUBSTR(TG_OP,1,1),
7526                     OLD.*;
7527             RETURN NULL;
7528         END;
7529         $func$ LANGUAGE 'plpgsql';
7530     $$;
7531         RETURN TRUE;
7532 END;
7533 $creator$ LANGUAGE 'plpgsql';
7534
7535 CREATE FUNCTION auditor.create_auditor_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7536 BEGIN
7537     EXECUTE $$
7538         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
7539             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
7540             EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
7541     $$;
7542         RETURN TRUE;
7543 END;
7544 $creator$ LANGUAGE 'plpgsql';
7545
7546 CREATE FUNCTION auditor.create_auditor_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7547 BEGIN
7548     EXECUTE $$
7549         CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
7550             SELECT      -1, now() as audit_time, '-' as audit_action, *
7551               FROM      $$ || sch || $$.$$ || tbl || $$
7552                 UNION ALL
7553             SELECT      *
7554               FROM      auditor.$$ || sch || $$_$$ || tbl || $$_history;
7555     $$;
7556         RETURN TRUE;
7557 END;
7558 $creator$ LANGUAGE 'plpgsql';
7559
7560 DROP FUNCTION IF EXISTS auditor.create_auditor (TEXT, TEXT);
7561
7562 -- The main event
7563
7564 CREATE FUNCTION auditor.create_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
7565 BEGIN
7566     PERFORM auditor.create_auditor_seq(sch, tbl);
7567     PERFORM auditor.create_auditor_history(sch, tbl);
7568     PERFORM auditor.create_auditor_func(sch, tbl);
7569     PERFORM auditor.create_auditor_update_trigger(sch, tbl);
7570     PERFORM auditor.create_auditor_lifecycle(sch, tbl);
7571     RETURN TRUE;
7572 END;
7573 $creator$ LANGUAGE 'plpgsql';
7574
7575 ALTER TABLE action.hold_request ADD COLUMN cut_in_line BOOL;
7576
7577 ALTER TABLE action.hold_request
7578 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
7579
7580 ALTER TABLE action.hold_request
7581 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
7582
7583 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_current_copy_fkey;
7584
7585 ALTER TABLE action.hold_request DROP CONSTRAINT hold_request_hold_type_check;
7586
7587 UPDATE config.index_normalizer SET param_count = 0 WHERE func = 'split_date_range';
7588
7589 CREATE INDEX actor_usr_usrgroup_idx ON actor.usr (usrgroup);
7590
7591 -- Add claims_never_checked_out_count to actor.usr, related history
7592
7593 ALTER TABLE actor.usr ADD COLUMN
7594         claims_never_checked_out_count  INT         NOT NULL DEFAULT 0;
7595
7596 ALTER TABLE AUDITOR.actor_usr_history ADD COLUMN 
7597         claims_never_checked_out_count INT;
7598
7599 DROP VIEW IF EXISTS auditor.actor_usr_lifecycle;
7600
7601 SELECT auditor.create_auditor_lifecycle( 'actor', 'usr' );
7602
7603 -----------
7604
7605 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
7606 BEGIN
7607         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
7608                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
7609                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
7610                 END IF;
7611                 IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
7612                         UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
7613                 END IF;
7614                 IF NEW.stop_fines = 'LOST' THEN
7615                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
7616                 END IF;
7617         END IF;
7618         RETURN NEW;
7619 END;
7620 $$ LANGUAGE 'plpgsql';
7621
7622 -- Create new table acq.fund_allocation_percent
7623 -- Populate it from acq.fund_allocation
7624 -- Convert all percentages to amounts in acq.fund_allocation
7625
7626 CREATE TABLE acq.fund_allocation_percent
7627 (
7628     id                   SERIAL            PRIMARY KEY,
7629     funding_source       INT               NOT NULL REFERENCES acq.funding_source
7630                                                DEFERRABLE INITIALLY DEFERRED,
7631     org                  INT               NOT NULL REFERENCES actor.org_unit
7632                                                DEFERRABLE INITIALLY DEFERRED,
7633     fund_code            TEXT,
7634     percent              NUMERIC           NOT NULL,
7635     allocator            INTEGER           NOT NULL REFERENCES actor.usr
7636                                                DEFERRABLE INITIALLY DEFERRED,
7637     note                 TEXT,
7638     create_time          TIMESTAMPTZ       NOT NULL DEFAULT now(),
7639     CONSTRAINT logical_key UNIQUE( funding_source, org, fund_code ),
7640     CONSTRAINT percentage_range CHECK( percent >= 0 AND percent <= 100 )
7641 );
7642
7643 -- Trigger function to validate combination of org_unit and fund_code
7644
7645 CREATE OR REPLACE FUNCTION acq.fund_alloc_percent_val()
7646 RETURNS TRIGGER AS $$
7647 --
7648 DECLARE
7649 --
7650 dummy int := 0;
7651 --
7652 BEGIN
7653     SELECT
7654         1
7655     INTO
7656         dummy
7657     FROM
7658         acq.fund
7659     WHERE
7660         org = NEW.org
7661         AND code = NEW.fund_code
7662         LIMIT 1;
7663     --
7664     IF dummy = 1 then
7665         RETURN NEW;
7666     ELSE
7667         RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
7668     END IF;
7669 END;
7670 $$ LANGUAGE plpgsql;
7671
7672 CREATE TRIGGER acq_fund_alloc_percent_val_trig
7673     BEFORE INSERT OR UPDATE ON acq.fund_allocation_percent
7674     FOR EACH ROW EXECUTE PROCEDURE acq.fund_alloc_percent_val();
7675
7676 CREATE OR REPLACE FUNCTION acq.fap_limit_100()
7677 RETURNS TRIGGER AS $$
7678 DECLARE
7679 --
7680 total_percent numeric;
7681 --
7682 BEGIN
7683     SELECT
7684         sum( percent )
7685     INTO
7686         total_percent
7687     FROM
7688         acq.fund_allocation_percent AS fap
7689     WHERE
7690         fap.funding_source = NEW.funding_source;
7691     --
7692     IF total_percent > 100 THEN
7693         RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
7694             NEW.funding_source;
7695     ELSE
7696         RETURN NEW;
7697     END IF;
7698 END;
7699 $$ LANGUAGE plpgsql;
7700
7701 CREATE TRIGGER acqfap_limit_100_trig
7702     AFTER INSERT OR UPDATE ON acq.fund_allocation_percent
7703     FOR EACH ROW EXECUTE PROCEDURE acq.fap_limit_100();
7704
7705 -- Populate new table from acq.fund_allocation
7706
7707 INSERT INTO acq.fund_allocation_percent
7708 (
7709     funding_source,
7710     org,
7711     fund_code,
7712     percent,
7713     allocator,
7714     note,
7715     create_time
7716 )
7717     SELECT
7718         fa.funding_source,
7719         fund.org,
7720         fund.code,
7721         fa.percent,
7722         fa.allocator,
7723         fa.note,
7724         fa.create_time
7725     FROM
7726         acq.fund_allocation AS fa
7727             INNER JOIN acq.fund AS fund
7728                 ON ( fa.fund = fund.id )
7729     WHERE
7730         fa.percent is not null
7731     ORDER BY
7732         fund.org;
7733
7734 -- Temporary function to convert percentages to amounts in acq.fund_allocation
7735
7736 -- Algorithm to apply to each funding source:
7737
7738 -- 1. Add up the credits.
7739 -- 2. Add up the percentages.
7740 -- 3. Multiply the sum of the percentages times the sum of the credits.  Drop any
7741 --    fractional cents from the result.  This is the total amount to be allocated.
7742 -- 4. For each allocation: multiply the percentage by the total allocation.  Drop any
7743 --    fractional cents to get a preliminary amount.
7744 -- 5. Add up the preliminary amounts for all the allocations.
7745 -- 6. Subtract the results of step 5 from the result of step 3.  The difference is the
7746 --    number of residual cents (resulting from having dropped fractional cents) that
7747 --    must be distributed across the funds in order to make the total of the amounts
7748 --    match the total allocation.
7749 -- 7. Make a second pass through the allocations, in decreasing order of the fractional
7750 --    cents that were dropped from their amounts in step 4.  Add one cent to the amount
7751 --    for each successive fund, until all the residual cents have been exhausted.
7752
7753 -- Result: the sum of the individual allocations now equals the total to be allocated,
7754 -- to the penny.  The individual amounts match the percentages as closely as possible,
7755 -- given the constraint that the total must match.
7756
7757 CREATE OR REPLACE FUNCTION acq.apply_percents()
7758 RETURNS VOID AS $$
7759 declare
7760 --
7761 tot              RECORD;
7762 fund             RECORD;
7763 tot_cents        INTEGER;
7764 src              INTEGER;
7765 id               INTEGER[];
7766 curr_id          INTEGER;
7767 pennies          NUMERIC[];
7768 curr_amount      NUMERIC;
7769 i                INTEGER;
7770 total_of_floors  INTEGER;
7771 total_percent    NUMERIC;
7772 total_allocation INTEGER;
7773 residue          INTEGER;
7774 --
7775 begin
7776         RAISE NOTICE 'Applying percents';
7777         FOR tot IN
7778                 SELECT
7779                         fsrc.funding_source,
7780                         sum( fsrc.amount ) AS total
7781                 FROM
7782                         acq.funding_source_credit AS fsrc
7783                 WHERE fsrc.funding_source IN
7784                         ( SELECT DISTINCT fa.funding_source
7785                           FROM acq.fund_allocation AS fa
7786                           WHERE fa.percent IS NOT NULL )
7787                 GROUP BY
7788                         fsrc.funding_source
7789         LOOP
7790                 tot_cents = floor( tot.total * 100 );
7791                 src = tot.funding_source;
7792                 RAISE NOTICE 'Funding source % total %',
7793                         src, tot_cents;
7794                 i := 0;
7795                 total_of_floors := 0;
7796                 total_percent := 0;
7797                 --
7798                 FOR fund in
7799                         SELECT
7800                                 fa.id,
7801                                 fa.percent,
7802                                 floor( fa.percent * tot_cents / 100 ) as floor_pennies
7803                         FROM
7804                                 acq.fund_allocation AS fa
7805                         WHERE
7806                                 fa.funding_source = src
7807                                 AND fa.percent IS NOT NULL
7808                         ORDER BY
7809                                 mod( fa.percent * tot_cents / 100, 1 ),
7810                                 fa.fund,
7811                                 fa.id
7812                 LOOP
7813                         RAISE NOTICE '   %: %',
7814                                 fund.id,
7815                                 fund.floor_pennies;
7816                         i := i + 1;
7817                         id[i] = fund.id;
7818                         pennies[i] = fund.floor_pennies;
7819                         total_percent := total_percent + fund.percent;
7820                         total_of_floors := total_of_floors + pennies[i];
7821                 END LOOP;
7822                 total_allocation := floor( total_percent * tot_cents /100 );
7823                 RAISE NOTICE 'Total before distributing residue: %', total_of_floors;
7824                 residue := total_allocation - total_of_floors;
7825                 RAISE NOTICE 'Residue: %', residue;
7826                 --
7827                 -- Post the calculated amounts, revising as needed to
7828                 -- distribute the rounding error
7829                 --
7830                 WHILE i > 0 LOOP
7831                         IF residue > 0 THEN
7832                                 pennies[i] = pennies[i] + 1;
7833                                 residue := residue - 1;
7834                         END IF;
7835                         --
7836                         -- Post amount
7837                         --
7838                         curr_id     := id[i];
7839                         curr_amount := trunc( pennies[i] / 100, 2 );
7840                         --
7841                         UPDATE
7842                                 acq.fund_allocation AS fa
7843                         SET
7844                                 amount = curr_amount,
7845                                 percent = NULL
7846                         WHERE
7847                                 fa.id = curr_id;
7848                         --
7849                         RAISE NOTICE '   ID % and amount %',
7850                                 curr_id,
7851                                 curr_amount;
7852                         i = i - 1;
7853                 END LOOP;
7854         END LOOP;
7855 end;
7856 $$ LANGUAGE 'plpgsql';
7857
7858 -- Run the temporary function
7859
7860 select * from acq.apply_percents();
7861
7862 -- Drop the temporary function now that we're done with it
7863
7864 DROP FUNCTION IF EXISTS acq.apply_percents();
7865
7866 -- Eliminate acq.fund_allocation.percent, which has been moved to the acq.fund_allocation_percent table.
7867
7868 -- If the following step fails, it's probably because there are still some non-null percent values in
7869 -- acq.fund_allocation.  They should have all been converted to amounts, and then set to null, by a
7870 -- previous upgrade step based on 0049.schema.acq_funding_allocation_percent.sql.  If there are any
7871 -- non-null values, then either that step didn't run, or it didn't work, or some non-null values
7872 -- slipped in afterwards.
7873
7874 -- To convert any remaining percents to amounts: create, run, and then drop the temporary stored
7875 -- procedure acq.apply_percents() as defined above.
7876
7877 ALTER TABLE acq.fund_allocation
7878 ALTER COLUMN amount SET NOT NULL;
7879
7880 CREATE OR REPLACE VIEW acq.fund_allocation_total AS
7881     SELECT  fund,
7882             SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
7883     FROM acq.fund_allocation a
7884          JOIN acq.fund f ON (a.fund = f.id)
7885          JOIN acq.funding_source s ON (a.funding_source = s.id)
7886     GROUP BY 1;
7887
7888 CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
7889     SELECT  funding_source,
7890             SUM(a.amount)::NUMERIC(100,2) AS amount
7891     FROM  acq.fund_allocation a
7892     GROUP BY 1;
7893
7894 ALTER TABLE acq.fund_allocation
7895 DROP COLUMN percent;
7896
7897 CREATE TABLE asset.copy_location_order
7898 (
7899         id              SERIAL           PRIMARY KEY,
7900         location        INT              NOT NULL
7901                                              REFERENCES asset.copy_location
7902                                              ON DELETE CASCADE
7903                                              DEFERRABLE INITIALLY DEFERRED,
7904         org             INT              NOT NULL
7905                                              REFERENCES actor.org_unit
7906                                              ON DELETE CASCADE
7907                                              DEFERRABLE INITIALLY DEFERRED,
7908         position        INT              NOT NULL DEFAULT 0,
7909         CONSTRAINT acplo_once_per_org UNIQUE ( location, org )
7910 );
7911
7912 ALTER TABLE money.credit_card_payment ADD COLUMN cc_processor TEXT;
7913
7914 -- If you ran this before its most recent incarnation:
7915 -- delete from config.upgrade_log where version = '0328';
7916 -- alter table money.credit_card_payment drop column cc_name;
7917
7918 ALTER TABLE money.credit_card_payment ADD COLUMN cc_first_name TEXT;
7919 ALTER TABLE money.credit_card_payment ADD COLUMN cc_last_name TEXT;
7920
7921 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$
7922 DECLARE
7923     current_group    permission.grp_tree%ROWTYPE;
7924     user_object    actor.usr%ROWTYPE;
7925     item_object    asset.copy%ROWTYPE;
7926     cn_object    asset.call_number%ROWTYPE;
7927     rec_descriptor    metabib.rec_descriptor%ROWTYPE;
7928     current_mp    config.circ_matrix_matchpoint%ROWTYPE;
7929     matchpoint    config.circ_matrix_matchpoint%ROWTYPE;
7930 BEGIN
7931     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
7932     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
7933     SELECT INTO cn_object * FROM asset.call_number WHERE id = item_object.call_number;
7934     SELECT INTO rec_descriptor r.* FROM metabib.rec_descriptor r JOIN asset.call_number c USING (record) WHERE c.id = item_object.call_number;
7935     SELECT INTO current_group * FROM permission.grp_tree WHERE id = user_object.profile;
7936
7937     LOOP 
7938         -- for each potential matchpoint for this ou and group ...
7939         FOR current_mp IN
7940             SELECT  m.*
7941               FROM  config.circ_matrix_matchpoint m
7942                     JOIN actor.org_unit_ancestors( context_ou ) d ON (m.org_unit = d.id)
7943                     LEFT JOIN actor.org_unit_proximity p ON (p.from_org = context_ou AND p.to_org = d.id)
7944               WHERE m.grp = current_group.id
7945                     AND m.active
7946                     AND (m.copy_owning_lib IS NULL OR cn_object.owning_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_owning_lib) ))
7947                     AND (m.copy_circ_lib   IS NULL OR item_object.circ_lib IN ( SELECT id FROM actor.org_unit_descendants(m.copy_circ_lib)   ))
7948               ORDER BY    CASE WHEN p.prox        IS NULL THEN 999 ELSE p.prox END,
7949                     CASE WHEN m.copy_owning_lib IS NOT NULL
7950                         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 )
7951                         ELSE 0
7952                     END +
7953                     CASE WHEN m.copy_circ_lib IS NOT NULL
7954                         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 )
7955                         ELSE 0
7956                     END +
7957                     CASE WHEN m.is_renewal = renewal        THEN 128 ELSE 0 END +
7958                     CASE WHEN m.juvenile_flag    IS NOT NULL THEN 64 ELSE 0 END +
7959                     CASE WHEN m.circ_modifier    IS NOT NULL THEN 32 ELSE 0 END +
7960                     CASE WHEN m.marc_type        IS NOT NULL THEN 16 ELSE 0 END +
7961                     CASE WHEN m.marc_form        IS NOT NULL THEN 8 ELSE 0 END +
7962                     CASE WHEN m.marc_vr_format    IS NOT NULL THEN 4 ELSE 0 END +
7963                     CASE WHEN m.ref_flag        IS NOT NULL THEN 2 ELSE 0 END +
7964                     CASE WHEN m.usr_age_lower_bound    IS NOT NULL THEN 0.5 ELSE 0 END +
7965                     CASE WHEN m.usr_age_upper_bound    IS NOT NULL THEN 0.5 ELSE 0 END DESC LOOP
7966
7967             IF current_mp.is_renewal IS NOT NULL THEN
7968                 CONTINUE WHEN current_mp.is_renewal <> renewal;
7969             END IF;
7970
7971             IF current_mp.circ_modifier IS NOT NULL THEN
7972                 CONTINUE WHEN current_mp.circ_modifier <> item_object.circ_modifier OR item_object.circ_modifier IS NULL;
7973             END IF;
7974
7975             IF current_mp.marc_type IS NOT NULL THEN
7976                 IF item_object.circ_as_type IS NOT NULL THEN
7977                     CONTINUE WHEN current_mp.marc_type <> item_object.circ_as_type;
7978                 ELSE
7979                     CONTINUE WHEN current_mp.marc_type <> rec_descriptor.item_type;
7980                 END IF;
7981             END IF;
7982
7983             IF current_mp.marc_form IS NOT NULL THEN
7984                 CONTINUE WHEN current_mp.marc_form <> rec_descriptor.item_form;
7985             END IF;
7986
7987             IF current_mp.marc_vr_format IS NOT NULL THEN
7988                 CONTINUE WHEN current_mp.marc_vr_format <> rec_descriptor.vr_format;
7989             END IF;
7990
7991             IF current_mp.ref_flag IS NOT NULL THEN
7992                 CONTINUE WHEN current_mp.ref_flag <> item_object.ref;
7993             END IF;
7994
7995             IF current_mp.juvenile_flag IS NOT NULL THEN
7996                 CONTINUE WHEN current_mp.juvenile_flag <> user_object.juvenile;
7997             END IF;
7998
7999             IF current_mp.usr_age_lower_bound IS NOT NULL THEN
8000                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_lower_bound < age(user_object.dob);
8001             END IF;
8002
8003             IF current_mp.usr_age_upper_bound IS NOT NULL THEN
8004                 CONTINUE WHEN user_object.dob IS NULL OR current_mp.usr_age_upper_bound > age(user_object.dob);
8005             END IF;
8006
8007
8008             -- everything was undefined or matched
8009             matchpoint = current_mp;
8010
8011             EXIT WHEN matchpoint.id IS NOT NULL;
8012         END LOOP;
8013
8014         EXIT WHEN current_group.parent IS NULL OR matchpoint.id IS NOT NULL;
8015
8016         SELECT INTO current_group * FROM permission.grp_tree WHERE id = current_group.parent;
8017     END LOOP;
8018
8019     RETURN matchpoint;
8020 END;
8021 $func$ LANGUAGE plpgsql;
8022
8023 CREATE TYPE action.hold_stats AS (
8024     hold_count              INT,
8025     copy_count              INT,
8026     available_count         INT,
8027     total_copy_ratio        FLOAT,
8028     available_copy_ratio    FLOAT
8029 );
8030
8031 CREATE OR REPLACE FUNCTION action.copy_related_hold_stats (copy_id INT) RETURNS action.hold_stats AS $func$
8032 DECLARE
8033     output          action.hold_stats%ROWTYPE;
8034     hold_count      INT := 0;
8035     copy_count      INT := 0;
8036     available_count INT := 0;
8037     hold_map_data   RECORD;
8038 BEGIN
8039
8040     output.hold_count := 0;
8041     output.copy_count := 0;
8042     output.available_count := 0;
8043
8044     SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
8045       FROM  action.hold_copy_map m
8046             JOIN action.hold_request h ON (m.hold = h.id)
8047       WHERE m.target_copy = copy_id
8048             AND NOT h.frozen;
8049
8050     output.hold_count := hold_count;
8051
8052     IF output.hold_count > 0 THEN
8053         FOR hold_map_data IN
8054             SELECT  DISTINCT m.target_copy,
8055                     acp.status
8056               FROM  action.hold_copy_map m
8057                     JOIN asset.copy acp ON (m.target_copy = acp.id)
8058                     JOIN action.hold_request h ON (m.hold = h.id)
8059               WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
8060         LOOP
8061             output.copy_count := output.copy_count + 1;
8062             IF hold_map_data.status IN (0,7,12) THEN
8063                 output.available_count := output.available_count + 1;
8064             END IF;
8065         END LOOP;
8066         output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
8067         output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;
8068
8069     END IF;
8070
8071     RETURN output;
8072
8073 END;
8074 $func$ LANGUAGE PLPGSQL;
8075
8076 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN total_copy_hold_ratio FLOAT;
8077 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN available_copy_hold_ratio FLOAT;
8078
8079 ALTER TABLE config.circ_matrix_matchpoint DROP CONSTRAINT ep_once_per_grp_loc_mod_marc;
8080
8081 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_circ_lib   INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8082 ALTER TABLE config.circ_matrix_matchpoint ADD COLUMN copy_owning_lib INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
8083
8084 ALTER TABLE config.circ_matrix_matchpoint ADD CONSTRAINT ep_once_per_grp_loc_mod_marc UNIQUE (
8085     grp, org_unit, circ_modifier, marc_type, marc_form, marc_vr_format, ref_flag,
8086     juvenile_flag, usr_age_lower_bound, usr_age_upper_bound, is_renewal, copy_circ_lib,
8087     copy_owning_lib
8088 );
8089
8090 -- Return the correct fail_part when the item can't be found
8091 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$
8092 DECLARE
8093     user_object        actor.usr%ROWTYPE;
8094     standing_penalty    config.standing_penalty%ROWTYPE;
8095     item_object        asset.copy%ROWTYPE;
8096     item_status_object    config.copy_status%ROWTYPE;
8097     item_location_object    asset.copy_location%ROWTYPE;
8098     result            action.matrix_test_result;
8099     circ_test        config.circ_matrix_matchpoint%ROWTYPE;
8100     out_by_circ_mod        config.circ_matrix_circ_mod_test%ROWTYPE;
8101     circ_mod_map        config.circ_matrix_circ_mod_test_map%ROWTYPE;
8102     hold_ratio          action.hold_stats%ROWTYPE;
8103     penalty_type         TEXT;
8104     tmp_grp         INT;
8105     items_out        INT;
8106     context_org_list        INT[];
8107     done            BOOL := FALSE;
8108 BEGIN
8109     result.success := TRUE;
8110
8111     -- Fail if the user is BARRED
8112     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8113
8114     -- Fail if we couldn't find the user 
8115     IF user_object.id IS NULL THEN
8116         result.fail_part := 'no_user';
8117         result.success := FALSE;
8118         done := TRUE;
8119         RETURN NEXT result;
8120         RETURN;
8121     END IF;
8122
8123     SELECT INTO item_object * FROM asset.copy WHERE id = match_item;
8124
8125     -- Fail if we couldn't find the item 
8126     IF item_object.id IS NULL THEN
8127         result.fail_part := 'no_item';
8128         result.success := FALSE;
8129         done := TRUE;
8130         RETURN NEXT result;
8131         RETURN;
8132     END IF;
8133
8134     SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, match_item, match_user, renewal);
8135     result.matchpoint := circ_test.id;
8136
8137     -- Fail if we couldn't find a matchpoint
8138     IF result.matchpoint IS NULL THEN
8139         result.fail_part := 'no_matchpoint';
8140         result.success := FALSE;
8141         done := TRUE;
8142         RETURN NEXT result;
8143     END IF;
8144
8145     IF user_object.barred IS TRUE THEN
8146         result.fail_part := 'actor.usr.barred';
8147         result.success := FALSE;
8148         done := TRUE;
8149         RETURN NEXT result;
8150     END IF;
8151
8152     -- Fail if the item can't circulate
8153     IF item_object.circulate IS FALSE THEN
8154         result.fail_part := 'asset.copy.circulate';
8155         result.success := FALSE;
8156         done := TRUE;
8157         RETURN NEXT result;
8158     END IF;
8159
8160     -- Fail if the item isn't in a circulateable status on a non-renewal
8161     IF NOT renewal AND item_object.status NOT IN ( 0, 7, 8 ) THEN 
8162         result.fail_part := 'asset.copy.status';
8163         result.success := FALSE;
8164         done := TRUE;
8165         RETURN NEXT result;
8166     ELSIF renewal AND item_object.status <> 1 THEN
8167         result.fail_part := 'asset.copy.status';
8168         result.success := FALSE;
8169         done := TRUE;
8170         RETURN NEXT result;
8171     END IF;
8172
8173     -- Fail if the item can't circulate because of the shelving location
8174     SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
8175     IF item_location_object.circulate IS FALSE THEN
8176         result.fail_part := 'asset.copy_location.circulate';
8177         result.success := FALSE;
8178         done := TRUE;
8179         RETURN NEXT result;
8180     END IF;
8181
8182     SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( circ_test.org_unit );
8183
8184     -- Fail if the test is set to hard non-circulating
8185     IF circ_test.circulate IS FALSE THEN
8186         result.fail_part := 'config.circ_matrix_test.circulate';
8187         result.success := FALSE;
8188         done := TRUE;
8189         RETURN NEXT result;
8190     END IF;
8191
8192     -- Fail if the total copy-hold ratio is too low
8193     IF circ_test.total_copy_hold_ratio IS NOT NULL THEN
8194         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8195         IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_test.total_copy_hold_ratio THEN
8196             result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
8197             result.success := FALSE;
8198             done := TRUE;
8199             RETURN NEXT result;
8200         END IF;
8201     END IF;
8202
8203     -- Fail if the available copy-hold ratio is too low
8204     IF circ_test.available_copy_hold_ratio IS NOT NULL THEN
8205         SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
8206         IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_test.available_copy_hold_ratio THEN
8207             result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
8208             result.success := FALSE;
8209             done := TRUE;
8210             RETURN NEXT result;
8211         END IF;
8212     END IF;
8213
8214     IF renewal THEN
8215         penalty_type = '%RENEW%';
8216     ELSE
8217         penalty_type = '%CIRC%';
8218     END IF;
8219
8220     FOR standing_penalty IN
8221         SELECT  DISTINCT csp.*
8222           FROM  actor.usr_standing_penalty usp
8223                 JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
8224           WHERE usr = match_user
8225                 AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
8226                 AND (usp.stop_date IS NULL or usp.stop_date > NOW())
8227                 AND csp.block_list LIKE penalty_type LOOP
8228
8229         result.fail_part := standing_penalty.name;
8230         result.success := FALSE;
8231         done := TRUE;
8232         RETURN NEXT result;
8233     END LOOP;
8234
8235     -- Fail if the user has too many items with specific circ_modifiers checked out
8236     FOR out_by_circ_mod IN SELECT * FROM config.circ_matrix_circ_mod_test WHERE matchpoint = circ_test.id LOOP
8237         SELECT  INTO items_out COUNT(*)
8238           FROM  action.circulation circ
8239             JOIN asset.copy cp ON (cp.id = circ.target_copy)
8240           WHERE circ.usr = match_user
8241                AND circ.circ_lib IN ( SELECT * FROM unnest(context_org_list) )
8242             AND circ.checkin_time IS NULL
8243             AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
8244             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);
8245         IF items_out >= out_by_circ_mod.items_out THEN
8246             result.fail_part := 'config.circ_matrix_circ_mod_test';
8247             result.success := FALSE;
8248             done := TRUE;
8249             RETURN NEXT result;
8250         END IF;
8251     END LOOP;
8252
8253     -- If we passed everything, return the successful matchpoint id
8254     IF NOT done THEN
8255         RETURN NEXT result;
8256     END IF;
8257
8258     RETURN;
8259 END;
8260 $func$ LANGUAGE plpgsql;
8261
8262 CREATE TABLE config.remote_account (
8263     id          SERIAL  PRIMARY KEY,
8264     label       TEXT    NOT NULL,
8265     host        TEXT    NOT NULL,   -- name or IP, :port optional
8266     username    TEXT,               -- optional, since we could default to $USER
8267     password    TEXT,               -- optional, since we could use SSH keys, or anonymous login.
8268     account     TEXT,               -- aka profile or FTP "account" command
8269     path        TEXT,               -- aka directory
8270     owner       INT     NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
8271     last_activity TIMESTAMP WITH TIME ZONE
8272 );
8273
8274 CREATE TABLE acq.edi_account (      -- similar tables can extend remote_account for other parts of EG
8275     provider    INT     NOT NULL REFERENCES acq.provider          (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
8276     in_dir      TEXT,   -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir)
8277         vendcode    TEXT,
8278         vendacct    TEXT
8279
8280 ) INHERITS (config.remote_account);
8281
8282 ALTER TABLE acq.edi_account ADD PRIMARY KEY (id);
8283
8284 CREATE TABLE acq.claim_type (
8285         id             SERIAL           PRIMARY KEY,
8286         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
8287                                                  DEFERRABLE INITIALLY DEFERRED,
8288         code           TEXT             NOT NULL,
8289         description    TEXT             NOT NULL,
8290         CONSTRAINT claim_type_once_per_org UNIQUE ( org_unit, code )
8291 );
8292
8293 CREATE TABLE acq.claim (
8294         id             SERIAL           PRIMARY KEY,
8295         type           INT              NOT NULL REFERENCES acq.claim_type
8296                                                  DEFERRABLE INITIALLY DEFERRED,
8297         lineitem_detail BIGINT          NOT NULL REFERENCES acq.lineitem_detail
8298                                                  DEFERRABLE INITIALLY DEFERRED
8299 );
8300
8301 CREATE TABLE acq.claim_policy (
8302         id              SERIAL       PRIMARY KEY,
8303         org_unit        INT          NOT NULL REFERENCES actor.org_unit
8304                                      DEFERRABLE INITIALLY DEFERRED,
8305         name            TEXT         NOT NULL,
8306         description     TEXT         NOT NULL,
8307         CONSTRAINT name_once_per_org UNIQUE (org_unit, name)
8308 );
8309
8310 -- Add a san column for EDI. 
8311 -- See: http://isbn.org/standards/home/isbn/us/san/san-qa.asp
8312
8313 ALTER TABLE acq.provider ADD COLUMN san INT;
8314
8315 ALTER TABLE acq.provider ALTER COLUMN san TYPE TEXT USING lpad(text(san), 7, '0');
8316
8317 -- null edi_default is OK... it has to be, since we have no values in acq.edi_account yet
8318 ALTER TABLE acq.provider ADD COLUMN edi_default INT REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
8319
8320 ALTER TABLE acq.provider
8321         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
8322
8323 ALTER TABLE acq.provider
8324         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
8325
8326 ALTER TABLE acq.provider
8327         ADD COLUMN url TEXT;
8328
8329 ALTER TABLE acq.provider
8330         ADD COLUMN email TEXT;
8331
8332 ALTER TABLE acq.provider
8333         ADD COLUMN phone TEXT;
8334
8335 ALTER TABLE acq.provider
8336         ADD COLUMN fax_phone TEXT;
8337
8338 ALTER TABLE acq.provider
8339         ADD COLUMN default_claim_policy INT
8340                 REFERENCES acq.claim_policy
8341                 DEFERRABLE INITIALLY DEFERRED;
8342
8343 ALTER TABLE action.transit_copy
8344 ADD COLUMN prev_dest INTEGER REFERENCES actor.org_unit( id )
8345                                                          DEFERRABLE INITIALLY DEFERRED;
8346
8347 DROP SCHEMA IF EXISTS booking CASCADE;
8348
8349 CREATE SCHEMA booking;
8350
8351 CREATE TABLE booking.resource_type (
8352         id             SERIAL          PRIMARY KEY,
8353         name           TEXT            NOT NULL,
8354         fine_interval  INTERVAL,
8355         fine_amount    DECIMAL(8,2)    NOT NULL DEFAULT 0,
8356         owner          INT             NOT NULL
8357                                        REFERENCES actor.org_unit( id )
8358                                        DEFERRABLE INITIALLY DEFERRED,
8359         catalog_item   BOOLEAN         NOT NULL DEFAULT FALSE,
8360         transferable   BOOLEAN         NOT NULL DEFAULT FALSE,
8361     record         BIGINT          REFERENCES biblio.record_entry (id)
8362                                        DEFERRABLE INITIALLY DEFERRED,
8363     max_fine       NUMERIC(8,2),
8364     elbow_room     INTERVAL,
8365     CONSTRAINT brt_name_and_record_once_per_owner UNIQUE(owner, name, record)
8366 );
8367
8368 CREATE TABLE booking.resource (
8369         id             SERIAL           PRIMARY KEY,
8370         owner          INT              NOT NULL
8371                                         REFERENCES actor.org_unit(id)
8372                                         DEFERRABLE INITIALLY DEFERRED,
8373         type           INT              NOT NULL
8374                                         REFERENCES booking.resource_type(id)
8375                                         DEFERRABLE INITIALLY DEFERRED,
8376         overbook       BOOLEAN          NOT NULL DEFAULT FALSE,
8377         barcode        TEXT             NOT NULL,
8378         deposit        BOOLEAN          NOT NULL DEFAULT FALSE,
8379         deposit_amount DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8380         user_fee       DECIMAL(8,2)     NOT NULL DEFAULT 0.00,
8381         CONSTRAINT br_unique UNIQUE (owner, barcode)
8382 );
8383
8384 -- For non-catalog items: hijack barcode for name/description
8385
8386 CREATE TABLE booking.resource_attr (
8387         id              SERIAL          PRIMARY KEY,
8388         owner           INT             NOT NULL
8389                                         REFERENCES actor.org_unit(id)
8390                                         DEFERRABLE INITIALLY DEFERRED,
8391         name            TEXT            NOT NULL,
8392         resource_type   INT             NOT NULL
8393                                         REFERENCES booking.resource_type(id)
8394                                         ON DELETE CASCADE
8395                                         DEFERRABLE INITIALLY DEFERRED,
8396         required        BOOLEAN         NOT NULL DEFAULT FALSE,
8397         CONSTRAINT bra_name_once_per_type UNIQUE(resource_type, name)
8398 );
8399
8400 CREATE TABLE booking.resource_attr_value (
8401         id               SERIAL         PRIMARY KEY,
8402         owner            INT            NOT NULL
8403                                         REFERENCES actor.org_unit(id)
8404                                         DEFERRABLE INITIALLY DEFERRED,
8405         attr             INT            NOT NULL
8406                                         REFERENCES booking.resource_attr(id)
8407                                         DEFERRABLE INITIALLY DEFERRED,
8408         valid_value      TEXT           NOT NULL,
8409         CONSTRAINT brav_logical_key UNIQUE(owner, attr, valid_value)
8410 );
8411
8412 CREATE TABLE booking.resource_attr_map (
8413         id               SERIAL         PRIMARY KEY,
8414         resource         INT            NOT NULL
8415                                         REFERENCES booking.resource(id)
8416                                         ON DELETE CASCADE
8417                                         DEFERRABLE INITIALLY DEFERRED,
8418         resource_attr    INT            NOT NULL
8419                                         REFERENCES booking.resource_attr(id)
8420                                         ON DELETE CASCADE
8421                                         DEFERRABLE INITIALLY DEFERRED,
8422         value            INT            NOT NULL
8423                                         REFERENCES booking.resource_attr_value(id)
8424                                         DEFERRABLE INITIALLY DEFERRED,
8425         CONSTRAINT bram_one_value_per_attr UNIQUE(resource, resource_attr)
8426 );
8427
8428 CREATE TABLE booking.reservation (
8429         request_time     TIMESTAMPTZ   NOT NULL DEFAULT now(),
8430         start_time       TIMESTAMPTZ,
8431         end_time         TIMESTAMPTZ,
8432         capture_time     TIMESTAMPTZ,
8433         cancel_time      TIMESTAMPTZ,
8434         pickup_time      TIMESTAMPTZ,
8435         return_time      TIMESTAMPTZ,
8436         booking_interval INTERVAL,
8437         fine_interval    INTERVAL,
8438         fine_amount      DECIMAL(8,2),
8439         target_resource_type  INT       NOT NULL
8440                                         REFERENCES booking.resource_type(id)
8441                                         ON DELETE CASCADE
8442                                         DEFERRABLE INITIALLY DEFERRED,
8443         target_resource  INT            REFERENCES booking.resource(id)
8444                                         ON DELETE CASCADE
8445                                         DEFERRABLE INITIALLY DEFERRED,
8446         current_resource INT            REFERENCES booking.resource(id)
8447                                         ON DELETE CASCADE
8448                                         DEFERRABLE INITIALLY DEFERRED,
8449         request_lib      INT            NOT NULL
8450                                         REFERENCES actor.org_unit(id)
8451                                         DEFERRABLE INITIALLY DEFERRED,
8452         pickup_lib       INT            REFERENCES actor.org_unit(id)
8453                                         DEFERRABLE INITIALLY DEFERRED,
8454         capture_staff    INT            REFERENCES actor.usr(id)
8455                                         DEFERRABLE INITIALLY DEFERRED,
8456     max_fine         NUMERIC(8,2)
8457 ) INHERITS (money.billable_xact);
8458
8459 ALTER TABLE booking.reservation ADD PRIMARY KEY (id);
8460
8461 ALTER TABLE booking.reservation
8462         ADD CONSTRAINT booking_reservation_usr_fkey
8463         FOREIGN KEY (usr) REFERENCES actor.usr (id)
8464         DEFERRABLE INITIALLY DEFERRED;
8465
8466 CREATE TABLE booking.reservation_attr_value_map (
8467         id               SERIAL         PRIMARY KEY,
8468         reservation      INT            NOT NULL
8469                                         REFERENCES booking.reservation(id)
8470                                         ON DELETE CASCADE
8471                                         DEFERRABLE INITIALLY DEFERRED,
8472         attr_value       INT            NOT NULL
8473                                         REFERENCES booking.resource_attr_value(id)
8474                                         ON DELETE CASCADE
8475                                         DEFERRABLE INITIALLY DEFERRED,
8476         CONSTRAINT bravm_logical_key UNIQUE(reservation, attr_value)
8477 );
8478
8479 -- represents a circ chain summary
8480 CREATE TYPE action.circ_chain_summary AS (
8481     num_circs INTEGER,
8482     start_time TIMESTAMP WITH TIME ZONE,
8483     checkout_workstation TEXT,
8484     last_renewal_time TIMESTAMP WITH TIME ZONE, -- NULL if no renewals
8485     last_stop_fines TEXT,
8486     last_stop_fines_time TIMESTAMP WITH TIME ZONE,
8487     last_renewal_workstation TEXT, -- NULL if no renewals
8488     last_checkin_workstation TEXT,
8489     last_checkin_time TIMESTAMP WITH TIME ZONE,
8490     last_checkin_scan_time TIMESTAMP WITH TIME ZONE
8491 );
8492
8493 CREATE OR REPLACE FUNCTION action.circ_chain ( ctx_circ_id INTEGER ) RETURNS SETOF action.circulation AS $$
8494 DECLARE
8495     tmp_circ action.circulation%ROWTYPE;
8496     circ_0 action.circulation%ROWTYPE;
8497 BEGIN
8498
8499     SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;
8500
8501     IF tmp_circ IS NULL THEN
8502         RETURN NEXT tmp_circ;
8503     END IF;
8504     circ_0 := tmp_circ;
8505
8506     -- find the front of the chain
8507     WHILE TRUE LOOP
8508         SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
8509         IF tmp_circ IS NULL THEN
8510             EXIT;
8511         END IF;
8512         circ_0 := tmp_circ;
8513     END LOOP;
8514
8515     -- now send the circs to the caller, oldest to newest
8516     tmp_circ := circ_0;
8517     WHILE TRUE LOOP
8518         IF tmp_circ IS NULL THEN
8519             EXIT;
8520         END IF;
8521         RETURN NEXT tmp_circ;
8522         SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
8523     END LOOP;
8524
8525 END;
8526 $$ LANGUAGE 'plpgsql';
8527
8528 CREATE OR REPLACE FUNCTION action.summarize_circ_chain ( ctx_circ_id INTEGER ) RETURNS action.circ_chain_summary AS $$
8529
8530 DECLARE
8531
8532     -- first circ in the chain
8533     circ_0 action.circulation%ROWTYPE;
8534
8535     -- last circ in the chain
8536     circ_n action.circulation%ROWTYPE;
8537
8538     -- circ chain under construction
8539     chain action.circ_chain_summary;
8540     tmp_circ action.circulation%ROWTYPE;
8541
8542 BEGIN
8543     
8544     chain.num_circs := 0;
8545     FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP
8546
8547         IF chain.num_circs = 0 THEN
8548             circ_0 := tmp_circ;
8549         END IF;
8550
8551         chain.num_circs := chain.num_circs + 1;
8552         circ_n := tmp_circ;
8553     END LOOP;
8554
8555     chain.start_time := circ_0.xact_start;
8556     chain.last_stop_fines := circ_n.stop_fines;
8557     chain.last_stop_fines_time := circ_n.stop_fines_time;
8558     chain.last_checkin_time := circ_n.checkin_time;
8559     chain.last_checkin_scan_time := circ_n.checkin_scan_time;
8560     SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
8561     SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;
8562
8563     IF chain.num_circs > 1 THEN
8564         chain.last_renewal_time := circ_n.xact_start;
8565         SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
8566     END IF;
8567
8568     RETURN chain;
8569
8570 END;
8571 $$ LANGUAGE 'plpgsql';
8572
8573 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ('reservation');
8574 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
8575 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON booking.reservation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
8576
8577 ALTER TABLE config.standing_penalty
8578         ADD COLUMN org_depth   INTEGER;
8579
8580 CREATE OR REPLACE FUNCTION actor.calculate_system_penalties( match_user INT, context_org INT ) RETURNS SETOF actor.usr_standing_penalty AS $func$
8581 DECLARE
8582     user_object         actor.usr%ROWTYPE;
8583     new_sp_row          actor.usr_standing_penalty%ROWTYPE;
8584     existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
8585     collections_fines   permission.grp_penalty_threshold%ROWTYPE;
8586     max_fines           permission.grp_penalty_threshold%ROWTYPE;
8587     max_overdue         permission.grp_penalty_threshold%ROWTYPE;
8588     max_items_out       permission.grp_penalty_threshold%ROWTYPE;
8589     tmp_grp             INT;
8590     items_overdue       INT;
8591     items_out           INT;
8592     context_org_list    INT[];
8593     current_fines        NUMERIC(8,2) := 0.0;
8594     tmp_fines            NUMERIC(8,2);
8595     tmp_groc            RECORD;
8596     tmp_circ            RECORD;
8597     tmp_org             actor.org_unit%ROWTYPE;
8598     tmp_penalty         config.standing_penalty%ROWTYPE;
8599     tmp_depth           INTEGER;
8600 BEGIN
8601     SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
8602
8603     -- Max fines
8604     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8605
8606     -- Fail if the user has a high fine balance
8607     LOOP
8608         tmp_grp := user_object.profile;
8609         LOOP
8610             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;
8611
8612             IF max_fines.threshold IS NULL THEN
8613                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8614             ELSE
8615                 EXIT;
8616             END IF;
8617
8618             IF tmp_grp IS NULL THEN
8619                 EXIT;
8620             END IF;
8621         END LOOP;
8622
8623         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8624             EXIT;
8625         END IF;
8626
8627         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8628
8629     END LOOP;
8630
8631     IF max_fines.threshold IS NOT NULL THEN
8632
8633         RETURN QUERY
8634             SELECT  *
8635               FROM  actor.usr_standing_penalty
8636               WHERE usr = match_user
8637                     AND org_unit = max_fines.org_unit
8638                     AND (stop_date IS NULL or stop_date > NOW())
8639                     AND standing_penalty = 1;
8640
8641         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8642
8643         SELECT  SUM(f.balance_owed) INTO current_fines
8644           FROM  money.materialized_billable_xact_summary f
8645                 JOIN (
8646                     SELECT  r.id
8647                       FROM  booking.reservation r
8648                       WHERE r.usr = match_user
8649                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8650                             AND xact_finish IS NULL
8651                                 UNION ALL
8652                     SELECT  g.id
8653                       FROM  money.grocery g
8654                       WHERE g.usr = match_user
8655                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8656                             AND xact_finish IS NULL
8657                                 UNION ALL
8658                     SELECT  circ.id
8659                       FROM  action.circulation circ
8660                       WHERE circ.usr = match_user
8661                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8662                             AND xact_finish IS NULL ) l USING (id);
8663
8664         IF current_fines >= max_fines.threshold THEN
8665             new_sp_row.usr := match_user;
8666             new_sp_row.org_unit := max_fines.org_unit;
8667             new_sp_row.standing_penalty := 1;
8668             RETURN NEXT new_sp_row;
8669         END IF;
8670     END IF;
8671
8672     -- Start over for max overdue
8673     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8674
8675     -- Fail if the user has too many overdue items
8676     LOOP
8677         tmp_grp := user_object.profile;
8678         LOOP
8679
8680             SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;
8681
8682             IF max_overdue.threshold IS NULL THEN
8683                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8684             ELSE
8685                 EXIT;
8686             END IF;
8687
8688             IF tmp_grp IS NULL THEN
8689                 EXIT;
8690             END IF;
8691         END LOOP;
8692
8693         IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8694             EXIT;
8695         END IF;
8696
8697         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8698
8699     END LOOP;
8700
8701     IF max_overdue.threshold IS NOT NULL THEN
8702
8703         RETURN QUERY
8704             SELECT  *
8705               FROM  actor.usr_standing_penalty
8706               WHERE usr = match_user
8707                     AND org_unit = max_overdue.org_unit
8708                     AND (stop_date IS NULL or stop_date > NOW())
8709                     AND standing_penalty = 2;
8710
8711         SELECT  INTO items_overdue COUNT(*)
8712           FROM  action.circulation circ
8713                 JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
8714           WHERE circ.usr = match_user
8715             AND circ.checkin_time IS NULL
8716             AND circ.due_date < NOW()
8717             AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);
8718
8719         IF items_overdue >= max_overdue.threshold::INT THEN
8720             new_sp_row.usr := match_user;
8721             new_sp_row.org_unit := max_overdue.org_unit;
8722             new_sp_row.standing_penalty := 2;
8723             RETURN NEXT new_sp_row;
8724         END IF;
8725     END IF;
8726
8727     -- Start over for max out
8728     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8729
8730     -- Fail if the user has too many checked out items
8731     LOOP
8732         tmp_grp := user_object.profile;
8733         LOOP
8734             SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;
8735
8736             IF max_items_out.threshold IS NULL THEN
8737                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8738             ELSE
8739                 EXIT;
8740             END IF;
8741
8742             IF tmp_grp IS NULL THEN
8743                 EXIT;
8744             END IF;
8745         END LOOP;
8746
8747         IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8748             EXIT;
8749         END IF;
8750
8751         SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8752
8753     END LOOP;
8754
8755
8756     -- Fail if the user has too many items checked out
8757     IF max_items_out.threshold IS NOT NULL THEN
8758
8759         RETURN QUERY
8760             SELECT  *
8761               FROM  actor.usr_standing_penalty
8762               WHERE usr = match_user
8763                     AND org_unit = max_items_out.org_unit
8764                     AND (stop_date IS NULL or stop_date > NOW())
8765                     AND standing_penalty = 3;
8766
8767         SELECT  INTO items_out COUNT(*)
8768           FROM  action.circulation circ
8769                 JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
8770           WHERE circ.usr = match_user
8771                 AND circ.checkin_time IS NULL
8772                 AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL);
8773
8774            IF items_out >= max_items_out.threshold::INT THEN
8775             new_sp_row.usr := match_user;
8776             new_sp_row.org_unit := max_items_out.org_unit;
8777             new_sp_row.standing_penalty := 3;
8778             RETURN NEXT new_sp_row;
8779            END IF;
8780     END IF;
8781
8782     -- Start over for collections warning
8783     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8784
8785     -- Fail if the user has a collections-level fine balance
8786     LOOP
8787         tmp_grp := user_object.profile;
8788         LOOP
8789             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;
8790
8791             IF max_fines.threshold IS NULL THEN
8792                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8793             ELSE
8794                 EXIT;
8795             END IF;
8796
8797             IF tmp_grp IS NULL THEN
8798                 EXIT;
8799             END IF;
8800         END LOOP;
8801
8802         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8803             EXIT;
8804         END IF;
8805
8806         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8807
8808     END LOOP;
8809
8810     IF max_fines.threshold IS NOT NULL THEN
8811
8812         RETURN QUERY
8813             SELECT  *
8814               FROM  actor.usr_standing_penalty
8815               WHERE usr = match_user
8816                     AND org_unit = max_fines.org_unit
8817                     AND (stop_date IS NULL or stop_date > NOW())
8818                     AND standing_penalty = 4;
8819
8820         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8821
8822         SELECT  SUM(f.balance_owed) INTO current_fines
8823           FROM  money.materialized_billable_xact_summary f
8824                 JOIN (
8825                     SELECT  r.id
8826                       FROM  booking.reservation r
8827                       WHERE r.usr = match_user
8828                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8829                             AND r.xact_finish IS NULL
8830                                 UNION ALL
8831                     SELECT  g.id
8832                       FROM  money.grocery g
8833                       WHERE g.usr = match_user
8834                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8835                             AND g.xact_finish IS NULL
8836                                 UNION ALL
8837                     SELECT  circ.id
8838                       FROM  action.circulation circ
8839                       WHERE circ.usr = match_user
8840                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8841                             AND circ.xact_finish IS NULL ) l USING (id);
8842
8843         IF current_fines >= max_fines.threshold THEN
8844             new_sp_row.usr := match_user;
8845             new_sp_row.org_unit := max_fines.org_unit;
8846             new_sp_row.standing_penalty := 4;
8847             RETURN NEXT new_sp_row;
8848         END IF;
8849     END IF;
8850
8851     -- Start over for in collections
8852     SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8853
8854     -- Remove the in-collections penalty if the user has paid down enough
8855     -- This penalty is different, because this code is not responsible for creating 
8856     -- new in-collections penalties, only for removing them
8857     LOOP
8858         tmp_grp := user_object.profile;
8859         LOOP
8860             SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;
8861
8862             IF max_fines.threshold IS NULL THEN
8863                 SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
8864             ELSE
8865                 EXIT;
8866             END IF;
8867
8868             IF tmp_grp IS NULL THEN
8869                 EXIT;
8870             END IF;
8871         END LOOP;
8872
8873         IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
8874             EXIT;
8875         END IF;
8876
8877         SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8878
8879     END LOOP;
8880
8881     IF max_fines.threshold IS NOT NULL THEN
8882
8883         SELECT INTO context_org_list ARRAY_ACCUM(id) FROM actor.org_unit_full_path( max_fines.org_unit );
8884
8885         -- first, see if the user had paid down to the threshold
8886         SELECT  SUM(f.balance_owed) INTO current_fines
8887           FROM  money.materialized_billable_xact_summary f
8888                 JOIN (
8889                     SELECT  r.id
8890                       FROM  booking.reservation r
8891                       WHERE r.usr = match_user
8892                             AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
8893                             AND r.xact_finish IS NULL
8894                                 UNION ALL
8895                     SELECT  g.id
8896                       FROM  money.grocery g
8897                       WHERE g.usr = match_user
8898                             AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
8899                             AND g.xact_finish IS NULL
8900                                 UNION ALL
8901                     SELECT  circ.id
8902                       FROM  action.circulation circ
8903                       WHERE circ.usr = match_user
8904                             AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
8905                             AND circ.xact_finish IS NULL ) l USING (id);
8906
8907         IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
8908             -- patron has paid down enough
8909
8910             SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;
8911
8912             IF tmp_penalty.org_depth IS NOT NULL THEN
8913
8914                 -- since this code is not responsible for applying the penalty, it can't 
8915                 -- guarantee the current context org will match the org at which the penalty 
8916                 --- was applied.  search up the org tree until we hit the configured penalty depth
8917                 SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
8918                 SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8919
8920                 WHILE tmp_depth >= tmp_penalty.org_depth LOOP
8921
8922                     RETURN QUERY
8923                         SELECT  *
8924                           FROM  actor.usr_standing_penalty
8925                           WHERE usr = match_user
8926                                 AND org_unit = tmp_org.id
8927                                 AND (stop_date IS NULL or stop_date > NOW())
8928                                 AND standing_penalty = 30;
8929
8930                     IF tmp_org.parent_ou IS NULL THEN
8931                         EXIT;
8932                     END IF;
8933
8934                     SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
8935                     SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
8936                 END LOOP;
8937
8938             ELSE
8939
8940                 -- no penalty depth is defined, look for exact matches
8941
8942                 RETURN QUERY
8943                     SELECT  *
8944                       FROM  actor.usr_standing_penalty
8945                       WHERE usr = match_user
8946                             AND org_unit = max_fines.org_unit
8947                             AND (stop_date IS NULL or stop_date > NOW())
8948                             AND standing_penalty = 30;
8949             END IF;
8950     
8951         END IF;
8952
8953     END IF;
8954
8955     RETURN;
8956 END;
8957 $func$ LANGUAGE plpgsql;
8958
8959 -- Create a default row in acq.fiscal_calendar
8960 -- Add a column in actor.org_unit to point to it
8961
8962 INSERT INTO acq.fiscal_calendar ( id, name ) VALUES ( 1, 'Default' );
8963
8964 ALTER TABLE actor.org_unit
8965 ADD COLUMN fiscal_calendar INT NOT NULL
8966         REFERENCES acq.fiscal_calendar( id )
8967         DEFERRABLE INITIALLY DEFERRED
8968         DEFAULT 1;
8969
8970 ALTER TABLE auditor.actor_org_unit_history
8971         ADD COLUMN fiscal_calendar INT;
8972
8973 DROP VIEW IF EXISTS auditor.actor_org_unit_lifecycle;
8974
8975 SELECT auditor.create_auditor_lifecycle( 'actor', 'org_unit' );
8976
8977 ALTER TABLE acq.funding_source_credit
8978 ADD COLUMN deadline_date TIMESTAMPTZ;
8979
8980 ALTER TABLE acq.funding_source_credit
8981 ADD COLUMN effective_date TIMESTAMPTZ NOT NULL DEFAULT now();
8982
8983 INSERT INTO config.standing_penalty (id,name,label) VALUES (30,'PATRON_IN_COLLECTIONS','Patron has been referred to a collections agency');
8984
8985 CREATE TABLE acq.fund_transfer (
8986         id               SERIAL         PRIMARY KEY,
8987         src_fund         INT            NOT NULL REFERENCES acq.fund( id )
8988                                         DEFERRABLE INITIALLY DEFERRED,
8989         src_amount       NUMERIC        NOT NULL,
8990         dest_fund        INT            REFERENCES acq.fund( id )
8991                                         DEFERRABLE INITIALLY DEFERRED,
8992         dest_amount      NUMERIC,
8993         transfer_time    TIMESTAMPTZ    NOT NULL DEFAULT now(),
8994         transfer_user    INT            NOT NULL REFERENCES actor.usr( id )
8995                                         DEFERRABLE INITIALLY DEFERRED,
8996         note             TEXT,
8997     funding_source_credit INTEGER   NOT NULL
8998                                         REFERENCES acq.funding_source_credit(id)
8999                                         DEFERRABLE INITIALLY DEFERRED
9000 );
9001
9002 CREATE INDEX acqftr_usr_idx
9003 ON acq.fund_transfer( transfer_user );
9004
9005 COMMENT ON TABLE acq.fund_transfer IS $$
9006 /*
9007  * Copyright (C) 2009  Georgia Public Library Service
9008  * Scott McKellar <scott@esilibrary.com>
9009  *
9010  * Fund Transfer
9011  *
9012  * Each row represents the transfer of money from a source fund
9013  * to a destination fund.  There should be corresponding entries
9014  * in acq.fund_allocation.  The purpose of acq.fund_transfer is
9015  * to record how much money moved from which fund to which other
9016  * fund.
9017  * 
9018  * The presence of two amount fields, rather than one, reflects
9019  * the possibility that the two funds are denominated in different
9020  * currencies.  If they use the same currency type, the two
9021  * amounts should be the same.
9022  *
9023  * ****
9024  *
9025  * This program is free software; you can redistribute it and/or
9026  * modify it under the terms of the GNU General Public License
9027  * as published by the Free Software Foundation; either version 2
9028  * of the License, or (at your option) any later version.
9029  *
9030  * This program is distributed in the hope that it will be useful,
9031  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9032  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9033  * GNU General Public License for more details.
9034  */
9035 $$;
9036
9037 CREATE TABLE acq.claim_event_type (
9038         id             SERIAL           PRIMARY KEY,
9039         org_unit       INT              NOT NULL REFERENCES actor.org_unit(id)
9040                                                  DEFERRABLE INITIALLY DEFERRED,
9041         code           TEXT             NOT NULL,
9042         description    TEXT             NOT NULL,
9043         library_initiated BOOL          NOT NULL DEFAULT FALSE,
9044         CONSTRAINT event_type_once_per_org UNIQUE ( org_unit, code )
9045 );
9046
9047 CREATE TABLE acq.claim_event (
9048         id             BIGSERIAL        PRIMARY KEY,
9049         type           INT              NOT NULL REFERENCES acq.claim_event_type
9050                                                  DEFERRABLE INITIALLY DEFERRED,
9051         claim          SERIAL           NOT NULL REFERENCES acq.claim
9052                                                  DEFERRABLE INITIALLY DEFERRED,
9053         event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
9054         creator        INT              NOT NULL REFERENCES actor.usr
9055                                                  DEFERRABLE INITIALLY DEFERRED,
9056         note           TEXT
9057 );
9058
9059 CREATE INDEX claim_event_claim_date_idx ON acq.claim_event( claim, event_date );
9060
9061 CREATE OR REPLACE FUNCTION actor.usr_purge_data(
9062         src_usr  IN INTEGER,
9063         dest_usr IN INTEGER
9064 ) RETURNS VOID AS $$
9065 DECLARE
9066         suffix TEXT;
9067         renamable_row RECORD;
9068 BEGIN
9069
9070         UPDATE actor.usr SET
9071                 active = FALSE,
9072                 card = NULL,
9073                 mailing_address = NULL,
9074                 billing_address = NULL
9075         WHERE id = src_usr;
9076
9077         -- acq.*
9078         UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
9079         UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
9080         UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
9081         UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
9082         UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
9083         UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
9084         DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;
9085
9086         -- Update with a rename to avoid collisions
9087         FOR renamable_row in
9088                 SELECT id, name
9089                 FROM   acq.picklist
9090                 WHERE  owner = src_usr
9091         LOOP
9092                 suffix := ' (' || src_usr || ')';
9093                 LOOP
9094                         BEGIN
9095                                 UPDATE  acq.picklist
9096                                 SET     owner = dest_usr, name = name || suffix
9097                                 WHERE   id = renamable_row.id;
9098                         EXCEPTION WHEN unique_violation THEN
9099                                 suffix := suffix || ' ';
9100                                 CONTINUE;
9101                         END;
9102                         EXIT;
9103                 END LOOP;
9104         END LOOP;
9105
9106         UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
9107         UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
9108         UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
9109         UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
9110         UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
9111         UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
9112         UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
9113         UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;
9114
9115         -- action.*
9116         DELETE FROM action.circulation WHERE usr = src_usr;
9117         UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
9118         UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
9119         UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
9120         UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
9121         UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
9122         DELETE FROM action.hold_request WHERE usr = src_usr;
9123         UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
9124         UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
9125         DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
9126         UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
9127         DELETE FROM action.survey_response WHERE usr = src_usr;
9128         UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
9129
9130         -- actor.*
9131         DELETE FROM actor.card WHERE usr = src_usr;
9132         DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
9133
9134         -- The following update is intended to avoid transient violations of a foreign
9135         -- key constraint, whereby actor.usr_address references itself.  It may not be
9136         -- necessary, but it does no harm.
9137         UPDATE actor.usr_address SET replaces = NULL
9138                 WHERE usr = src_usr AND replaces IS NOT NULL;
9139         DELETE FROM actor.usr_address WHERE usr = src_usr;
9140         DELETE FROM actor.usr_note WHERE usr = src_usr;
9141         UPDATE actor.usr_note SET creator = dest_usr WHERE creator = src_usr;
9142         DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
9143         UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
9144         DELETE FROM actor.usr_setting WHERE usr = src_usr;
9145         DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
9146         UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
9147
9148         -- asset.*
9149         UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
9150         UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
9151         UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
9152         UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
9153         UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
9154         UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
9155
9156         -- auditor.*
9157         DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
9158         DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
9159         UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
9160         UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
9161         UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
9162         UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
9163         UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
9164         UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;
9165
9166         -- biblio.*
9167         UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
9168         UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
9169         UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
9170         UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;
9171
9172         -- container.*
9173         -- Update buckets with a rename to avoid collisions
9174         FOR renamable_row in
9175                 SELECT id, name
9176                 FROM   container.biblio_record_entry_bucket
9177                 WHERE  owner = src_usr
9178         LOOP
9179                 suffix := ' (' || src_usr || ')';
9180                 LOOP
9181                         BEGIN
9182                                 UPDATE  container.biblio_record_entry_bucket
9183                                 SET     owner = dest_usr, name = name || suffix
9184                                 WHERE   id = renamable_row.id;
9185                         EXCEPTION WHEN unique_violation THEN
9186                                 suffix := suffix || ' ';
9187                                 CONTINUE;
9188                         END;
9189                         EXIT;
9190                 END LOOP;
9191         END LOOP;
9192
9193         FOR renamable_row in
9194                 SELECT id, name
9195                 FROM   container.call_number_bucket
9196                 WHERE  owner = src_usr
9197         LOOP
9198                 suffix := ' (' || src_usr || ')';
9199                 LOOP
9200                         BEGIN
9201                                 UPDATE  container.call_number_bucket
9202                                 SET     owner = dest_usr, name = name || suffix
9203                                 WHERE   id = renamable_row.id;
9204                         EXCEPTION WHEN unique_violation THEN
9205                                 suffix := suffix || ' ';
9206                                 CONTINUE;
9207                         END;
9208                         EXIT;
9209                 END LOOP;
9210         END LOOP;
9211
9212         FOR renamable_row in
9213                 SELECT id, name
9214                 FROM   container.copy_bucket
9215                 WHERE  owner = src_usr
9216         LOOP
9217                 suffix := ' (' || src_usr || ')';
9218                 LOOP
9219                         BEGIN
9220                                 UPDATE  container.copy_bucket
9221                                 SET     owner = dest_usr, name = name || suffix
9222                                 WHERE   id = renamable_row.id;
9223                         EXCEPTION WHEN unique_violation THEN
9224                                 suffix := suffix || ' ';
9225                                 CONTINUE;
9226                         END;
9227                         EXIT;
9228                 END LOOP;
9229         END LOOP;
9230
9231         FOR renamable_row in
9232                 SELECT id, name
9233                 FROM   container.user_bucket
9234                 WHERE  owner = src_usr
9235         LOOP
9236                 suffix := ' (' || src_usr || ')';
9237                 LOOP
9238                         BEGIN
9239                                 UPDATE  container.user_bucket
9240                                 SET     owner = dest_usr, name = name || suffix
9241                                 WHERE   id = renamable_row.id;
9242                         EXCEPTION WHEN unique_violation THEN
9243                                 suffix := suffix || ' ';
9244                                 CONTINUE;
9245                         END;
9246                         EXIT;
9247                 END LOOP;
9248         END LOOP;
9249
9250         DELETE FROM container.user_bucket_item WHERE target_user = src_usr;
9251
9252         -- money.*
9253         DELETE FROM money.billable_xact WHERE usr = src_usr;
9254         DELETE FROM money.collections_tracker WHERE usr = src_usr;
9255         UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;
9256
9257         -- permission.*
9258         DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
9259         DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
9260         DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
9261         DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;
9262
9263         -- reporter.*
9264         -- Update with a rename to avoid collisions
9265         BEGIN
9266                 FOR renamable_row in
9267                         SELECT id, name
9268                         FROM   reporter.output_folder
9269                         WHERE  owner = src_usr
9270                 LOOP
9271                         suffix := ' (' || src_usr || ')';
9272                         LOOP
9273                                 BEGIN
9274                                         UPDATE  reporter.output_folder
9275                                         SET     owner = dest_usr, name = name || suffix
9276                                         WHERE   id = renamable_row.id;
9277                                 EXCEPTION WHEN unique_violation THEN
9278                                         suffix := suffix || ' ';
9279                                         CONTINUE;
9280                                 END;
9281                                 EXIT;
9282                         END LOOP;
9283                 END LOOP;
9284         EXCEPTION WHEN undefined_table THEN
9285                 -- do nothing
9286         END;
9287
9288         BEGIN
9289                 UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
9290         EXCEPTION WHEN undefined_table THEN
9291                 -- do nothing
9292         END;
9293
9294         -- Update with a rename to avoid collisions
9295         BEGIN
9296                 FOR renamable_row in
9297                         SELECT id, name
9298                         FROM   reporter.report_folder
9299                         WHERE  owner = src_usr
9300                 LOOP
9301                         suffix := ' (' || src_usr || ')';
9302                         LOOP
9303                                 BEGIN
9304                                         UPDATE  reporter.report_folder
9305                                         SET     owner = dest_usr, name = name || suffix
9306                                         WHERE   id = renamable_row.id;
9307                                 EXCEPTION WHEN unique_violation THEN
9308                                         suffix := suffix || ' ';
9309                                         CONTINUE;
9310                                 END;
9311                                 EXIT;
9312                         END LOOP;
9313                 END LOOP;
9314         EXCEPTION WHEN undefined_table THEN
9315                 -- do nothing
9316         END;
9317
9318         BEGIN
9319                 UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
9320         EXCEPTION WHEN undefined_table THEN
9321                 -- do nothing
9322         END;
9323
9324         BEGIN
9325                 UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
9326         EXCEPTION WHEN undefined_table THEN
9327                 -- do nothing
9328         END;
9329
9330         -- Update with a rename to avoid collisions
9331         BEGIN
9332                 FOR renamable_row in
9333                         SELECT id, name
9334                         FROM   reporter.template_folder
9335                         WHERE  owner = src_usr
9336                 LOOP
9337                         suffix := ' (' || src_usr || ')';
9338                         LOOP
9339                                 BEGIN
9340                                         UPDATE  reporter.template_folder
9341                                         SET     owner = dest_usr, name = name || suffix
9342                                         WHERE   id = renamable_row.id;
9343                                 EXCEPTION WHEN unique_violation THEN
9344                                         suffix := suffix || ' ';
9345                                         CONTINUE;
9346                                 END;
9347                                 EXIT;
9348                         END LOOP;
9349                 END LOOP;
9350         EXCEPTION WHEN undefined_table THEN
9351         -- do nothing
9352         END;
9353
9354         -- vandelay.*
9355         -- Update with a rename to avoid collisions
9356         FOR renamable_row in
9357                 SELECT id, name
9358                 FROM   vandelay.queue
9359                 WHERE  owner = src_usr
9360         LOOP
9361                 suffix := ' (' || src_usr || ')';
9362                 LOOP
9363                         BEGIN
9364                                 UPDATE  vandelay.queue
9365                                 SET     owner = dest_usr, name = name || suffix
9366                                 WHERE   id = renamable_row.id;
9367                         EXCEPTION WHEN unique_violation THEN
9368                                 suffix := suffix || ' ';
9369                                 CONTINUE;
9370                         END;
9371                         EXIT;
9372                 END LOOP;
9373         END LOOP;
9374
9375 END;
9376 $$ LANGUAGE plpgsql;
9377
9378 COMMENT ON FUNCTION actor.usr_purge_data(INT, INT) IS $$
9379 /**
9380  * Finds rows dependent on a given row in actor.usr and either deletes them
9381  * or reassigns them to a different user.
9382  */
9383 $$;
9384
9385 CREATE OR REPLACE FUNCTION actor.usr_delete(
9386         src_usr  IN INTEGER,
9387         dest_usr IN INTEGER
9388 ) RETURNS VOID AS $$
9389 DECLARE
9390         old_profile actor.usr.profile%type;
9391         old_home_ou actor.usr.home_ou%type;
9392         new_profile actor.usr.profile%type;
9393         new_home_ou actor.usr.home_ou%type;
9394         new_name    text;
9395         new_dob     actor.usr.dob%type;
9396 BEGIN
9397         SELECT
9398                 id || '-PURGED-' || now(),
9399                 profile,
9400                 home_ou,
9401                 dob
9402         INTO
9403                 new_name,
9404                 old_profile,
9405                 old_home_ou,
9406                 new_dob
9407         FROM
9408                 actor.usr
9409         WHERE
9410                 id = src_usr;
9411         --
9412         -- Quit if no such user
9413         --
9414         IF old_profile IS NULL THEN
9415                 RETURN;
9416         END IF;
9417         --
9418         perform actor.usr_purge_data( src_usr, dest_usr );
9419         --
9420         -- Find the root grp_tree and the root org_unit.  This would be simpler if we 
9421         -- could assume that there is only one root.  Theoretically, someday, maybe,
9422         -- there could be multiple roots, so we take extra trouble to get the right ones.
9423         --
9424         SELECT
9425                 id
9426         INTO
9427                 new_profile
9428         FROM
9429                 permission.grp_ancestors( old_profile )
9430         WHERE
9431                 parent is null;
9432         --
9433         SELECT
9434                 id
9435         INTO
9436                 new_home_ou
9437         FROM
9438                 actor.org_unit_ancestors( old_home_ou )
9439         WHERE
9440                 parent_ou is null;
9441         --
9442         -- Truncate date of birth
9443         --
9444         IF new_dob IS NOT NULL THEN
9445                 new_dob := date_trunc( 'year', new_dob );
9446         END IF;
9447         --
9448         UPDATE
9449                 actor.usr
9450                 SET
9451                         card = NULL,
9452                         profile = new_profile,
9453                         usrname = new_name,
9454                         email = NULL,
9455                         passwd = random()::text,
9456                         standing = DEFAULT,
9457                         ident_type = 
9458                         (
9459                                 SELECT MIN( id )
9460                                 FROM config.identification_type
9461                         ),
9462                         ident_value = NULL,
9463                         ident_type2 = NULL,
9464                         ident_value2 = NULL,
9465                         net_access_level = DEFAULT,
9466                         photo_url = NULL,
9467                         prefix = NULL,
9468                         first_given_name = new_name,
9469                         second_given_name = NULL,
9470                         family_name = new_name,
9471                         suffix = NULL,
9472                         alias = NULL,
9473                         day_phone = NULL,
9474                         evening_phone = NULL,
9475                         other_phone = NULL,
9476                         mailing_address = NULL,
9477                         billing_address = NULL,
9478                         home_ou = new_home_ou,
9479                         dob = new_dob,
9480                         active = FALSE,
9481                         master_account = DEFAULT, 
9482                         super_user = DEFAULT,
9483                         barred = FALSE,
9484                         deleted = TRUE,
9485                         juvenile = DEFAULT,
9486                         usrgroup = 0,
9487                         claims_returned_count = DEFAULT,
9488                         credit_forward_balance = DEFAULT,
9489                         last_xact_id = DEFAULT,
9490                         alert_message = NULL,
9491                         create_date = now(),
9492                         expire_date = now()
9493         WHERE
9494                 id = src_usr;
9495 END;
9496 $$ LANGUAGE plpgsql;
9497
9498 COMMENT ON FUNCTION actor.usr_delete(INT, INT) IS $$
9499 /**
9500  * Logically deletes a user.  Removes personally identifiable information,
9501  * and purges associated data in other tables.
9502  */
9503 $$;
9504
9505 -- INSERT INTO config.copy_status (id,name) VALUES (15,oils_i18n_gettext(15, 'On reservation shelf', 'ccs', 'name'));
9506
9507 ALTER TABLE acq.fund
9508 ADD COLUMN rollover BOOL NOT NULL DEFAULT FALSE;
9509
9510 ALTER TABLE acq.fund
9511         ADD COLUMN propagate BOOLEAN NOT NULL DEFAULT TRUE;
9512
9513 -- A fund can't roll over if it doesn't propagate from one year to the next
9514
9515 ALTER TABLE acq.fund
9516         ADD CONSTRAINT acq_fund_rollover_implies_propagate CHECK
9517         ( propagate OR NOT rollover );
9518
9519 ALTER TABLE acq.fund
9520         ADD COLUMN active BOOL NOT NULL DEFAULT TRUE;
9521
9522 ALTER TABLE acq.fund
9523     ADD COLUMN balance_warning_percent INT;
9524
9525 ALTER TABLE acq.fund
9526     ADD COLUMN balance_stop_percent INT;
9527
9528 CREATE VIEW acq.ordered_funding_source_credit AS
9529         SELECT
9530                 CASE WHEN deadline_date IS NULL THEN
9531                         2
9532                 ELSE
9533                         1
9534                 END AS sort_priority,
9535                 CASE WHEN deadline_date IS NULL THEN
9536                         effective_date
9537                 ELSE
9538                         deadline_date
9539                 END AS sort_date,
9540                 id,
9541                 funding_source,
9542                 amount,
9543                 note
9544         FROM
9545                 acq.funding_source_credit;
9546
9547 COMMENT ON VIEW acq.ordered_funding_source_credit IS $$
9548 /*
9549  * Copyright (C) 2009  Georgia Public Library Service
9550  * Scott McKellar <scott@gmail.com>
9551  *
9552  * The acq.ordered_funding_source_credit view is a prioritized
9553  * ordering of funding source credits.  When ordered by the first
9554  * three columns, this view defines the order in which the various
9555  * credits are to be tapped for spending, subject to the allocations
9556  * in the acq.fund_allocation table.
9557  *
9558  * The first column reflects the principle that we should spend
9559  * money with deadlines before spending money without deadlines.
9560  *
9561  * The second column reflects the principle that we should spend the
9562  * oldest money first.  For money with deadlines, that means that we
9563  * spend first from the credit with the earliest deadline.  For
9564  * money without deadlines, we spend first from the credit with the
9565  * earliest effective date.  
9566  *
9567  * The third column is a tie breaker to ensure a consistent
9568  * ordering.
9569  *
9570  * ****
9571  *
9572  * This program is free software; you can redistribute it and/or
9573  * modify it under the terms of the GNU General Public License
9574  * as published by the Free Software Foundation; either version 2
9575  * of the License, or (at your option) any later version.
9576  *
9577  * This program is distributed in the hope that it will be useful,
9578  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9579  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9580  * GNU General Public License for more details.
9581  */
9582 $$;
9583
9584 CREATE OR REPLACE VIEW money.billable_xact_summary_location_view AS
9585     SELECT  m.*, COALESCE(c.circ_lib, g.billing_location, r.pickup_lib) AS billing_location
9586       FROM  money.materialized_billable_xact_summary m
9587             LEFT JOIN action.circulation c ON (c.id = m.id)
9588             LEFT JOIN money.grocery g ON (g.id = m.id)
9589             LEFT JOIN booking.reservation r ON (r.id = m.id);
9590
9591 CREATE TABLE config.marc21_rec_type_map (
9592     code        TEXT    PRIMARY KEY,
9593     type_val    TEXT    NOT NULL,
9594     blvl_val    TEXT    NOT NULL
9595 );
9596
9597 CREATE TABLE config.marc21_ff_pos_map (
9598     id          SERIAL  PRIMARY KEY,
9599     fixed_field TEXT    NOT NULL,
9600     tag         TEXT    NOT NULL,
9601     rec_type    TEXT    NOT NULL,
9602     start_pos   INT     NOT NULL,
9603     length      INT     NOT NULL,
9604     default_val TEXT    NOT NULL DEFAULT ' '
9605 );
9606
9607 CREATE TABLE config.marc21_physical_characteristic_type_map (
9608     ptype_key   TEXT    PRIMARY KEY,
9609     label       TEXT    NOT NULL -- I18N
9610 );
9611
9612 CREATE TABLE config.marc21_physical_characteristic_subfield_map (
9613     id          SERIAL  PRIMARY KEY,
9614     ptype_key   TEXT    NOT NULL REFERENCES config.marc21_physical_characteristic_type_map (ptype_key) ON DELETE CASCADE ON UPDATE CASCADE,
9615     subfield    TEXT    NOT NULL,
9616     start_pos   INT     NOT NULL,
9617     length      INT     NOT NULL,
9618     label       TEXT    NOT NULL -- I18N
9619 );
9620
9621 CREATE TABLE config.marc21_physical_characteristic_value_map (
9622     id              SERIAL  PRIMARY KEY,
9623     value           TEXT    NOT NULL,
9624     ptype_subfield  INT     NOT NULL REFERENCES config.marc21_physical_characteristic_subfield_map (id),
9625     label           TEXT    NOT NULL -- I18N
9626 );
9627
9628 ----------------------------------
9629 -- MARC21 record structure data --
9630 ----------------------------------
9631
9632 -- Record type map
9633 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('BKS','at','acdm');
9634 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SER','a','bsi');
9635 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('VIS','gkro','abcdmsi');
9636 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MIX','p','cdi');
9637 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('MAP','ef','abcdmsi');
9638 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('SCO','cd','abcdmsi');
9639 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('REC','ij','abcdmsi');
9640 INSERT INTO config.marc21_rec_type_map (code, type_val, blvl_val) VALUES ('COM','m','abcdmsi');
9641
9642 ------ Physical Characteristics
9643
9644 -- Map
9645 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('a','Map');
9646 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','b','1','1','SMD');
9647 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Atlas');
9648 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diagram');
9649 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Map');
9650 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Profile');
9651 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Model');
9652 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');
9653 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Section');
9654 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9655 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('y',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'View');
9656 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9657 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','d','3','1','Color');
9658 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');
9659 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9660 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','e','4','1','Physical medium');
9661 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9662 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9663 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9664 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9665 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9666 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9667 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9668 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9669 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');
9670 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');
9671 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');
9672 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');
9673 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9674 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');
9675 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9676 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','f','5','1','Type of reproduction');
9677 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9678 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');
9679 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9680 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9681 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','g','6','1','Production/reproduction details');
9682 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');
9683 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photocopy');
9684 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');
9685 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Film');
9686 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9687 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9688 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('a','h','7','1','Positive/negative');
9689 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9690 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9691 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9692 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');
9693
9694 -- Electronic Resource
9695 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('c','Electronic Resource');
9696 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','b','1','1','SMD');
9697 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');
9698 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');
9699 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');
9700 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');
9701 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');
9702 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');
9703 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');
9704 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');
9705 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Remote');
9706 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9707 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9708 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','d','3','1','Color');
9709 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');
9710 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');
9711 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9712 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');
9713 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9714 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');
9715 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9716 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9717 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','e','4','1','Dimensions');
9718 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.');
9719 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.');
9720 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.');
9721 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.');
9722 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.');
9723 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');
9724 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.');
9725 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9726 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.');
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','f','5','1','Sound');
9729 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)');
9730 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Sound');
9731 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9732 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','g','6','3','Image bit depth');
9733 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('---',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9734 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('mmm',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multiple');
9735 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');
9736 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','h','9','1','File formats');
9737 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');
9738 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');
9739 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9740 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','i','10','1','Quality assurance target(s)');
9741 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Absent');
9742 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');
9743 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Present');
9744 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9745 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','j','11','1','Antecedent/Source');
9746 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');
9747 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');
9748 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');
9749 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)');
9750 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9751 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');
9752 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9753 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','k','12','1','Level of compression');
9754 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncompressed');
9755 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossless');
9756 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Lossy');
9757 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9758 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9759 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('c','l','13','1','Reformatting quality');
9760 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Access');
9761 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');
9762 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Preservation');
9763 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Replacement');
9764 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9765
9766 -- Globe
9767 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('d','Globe');
9768 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','b','1','1','SMD');
9769 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');
9770 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');
9771 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');
9772 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');
9773 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9774 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9775 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','d','3','1','Color');
9776 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');
9777 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9778 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','e','4','1','Physical medium');
9779 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9780 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
9781 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
9782 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
9783 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9784 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
9785 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
9786 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
9787 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9788 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9789 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('d','f','5','1','Type of reproduction');
9790 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Facsimile');
9791 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');
9792 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9793 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9794
9795 -- Tactile Material
9796 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('f','Tactile Material');
9797 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','b','1','1','SMD');
9798 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Moon');
9799 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Braille');
9800 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9801 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');
9802 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9803 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9804 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','d','3','2','Class of braille writing');
9805 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');
9806 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');
9807 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');
9808 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');
9809 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');
9810 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');
9811 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');
9812 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9813 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9814 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','e','4','1','Level of contraction');
9815 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Uncontracted');
9816 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Contracted');
9817 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combination');
9818 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');
9819 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9820 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9821 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','f','6','3','Braille music format');
9822 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');
9823 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');
9824 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');
9825 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paragraph');
9826 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');
9827 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');
9828 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');
9829 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');
9830 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');
9831 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');
9832 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outline');
9833 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');
9834 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');
9835 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9836 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9837 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('f','g','9','1','Special physical characteristics');
9838 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');
9839 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');
9840 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');
9841 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9842 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9843
9844 -- Projected Graphic
9845 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('g','Projected Graphic');
9846 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','b','1','1','SMD');
9847 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');
9848 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Filmstrip');
9849 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');
9850 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');
9851 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Slide');
9852 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Transparency');
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 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','d','3','1','Color');
9855 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');
9856 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9857 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');
9858 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9859 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');
9860 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9861 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9862 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','e','4','1','Base of emulsion');
9863 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9864 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9865 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');
9866 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');
9867 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');
9868 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
9869 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9870 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9871 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');
9872 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');
9873 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');
9874 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9875 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','g','6','1','Medium for sound');
9876 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');
9877 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');
9878 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');
9879 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');
9880 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');
9881 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');
9882 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');
9883 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
9884 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9887 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','h','7','1','Dimensions');
9888 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.');
9889 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.');
9890 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.');
9891 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.');
9892 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.');
9893 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.');
9894 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.');
9895 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.)');
9896 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.)');
9897 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.)');
9898 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.)');
9899 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9900 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.)');
9901 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.)');
9902 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.)');
9903 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.)');
9904 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9905 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('g','i','8','1','Secondary support material');
9906 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cardboard');
9907 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
9908 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
9909 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'metal');
9910 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');
9911 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');
9912 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');
9913 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9914 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9915
9916 -- Microform
9917 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('h','Microform');
9918 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','b','1','1','SMD');
9919 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');
9920 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');
9921 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');
9922 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');
9923 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microfiche');
9924 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');
9925 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Microopaque');
9926 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
9927 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9928 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','d','3','1','Positive/negative');
9929 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Positive');
9930 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Negative');
9931 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9932 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9933 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','e','4','1','Dimensions');
9934 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.');
9935 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.');
9936 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.');
9937 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'70mm.');
9938 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.');
9939 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.)');
9940 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.)');
9941 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.)');
9942 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.)');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9945 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');
9946 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)');
9947 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)');
9948 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)');
9949 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)');
9950 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-)');
9951 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9952 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');
9953 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','g','9','1','Color');
9954 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');
9955 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
9956 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9957 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9958 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9959 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','h','10','1','Emulsion on film');
9960 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');
9961 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Diazo');
9962 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vesicular');
9963 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
9964 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');
9965 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9966 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9967 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','i','11','1','Quality assurance target(s)');
9968 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');
9969 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');
9970 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');
9971 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');
9972 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
9973 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('h','j','12','1','Base of film');
9974 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');
9975 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');
9976 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');
9977 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');
9978 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');
9979 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');
9980 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');
9981 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');
9982 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');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
9985
9986 -- Non-projected Graphic
9987 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('k','Non-projected Graphic');
9988 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','b','1','1','SMD');
9989 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Collage');
9990 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Drawing');
9991 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Painting');
9992 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');
9993 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photonegative');
9994 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Photoprint');
9995 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Picture');
9996 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('j',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Print');
9997 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');
9998 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('n',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Chart');
9999 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');
10000 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10001 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10002 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','d','3','1','Color');
10003 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');
10004 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');
10005 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10006 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');
10007 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10008 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10009 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10010 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','e','4','1','Primary support material');
10011 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10012 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');
10013 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');
10014 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10015 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10016 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10017 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10018 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10019 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');
10020 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10021 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10022 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10023 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10024 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10025 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10026 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10027 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10028 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('k','f','5','1','Secondary support material');
10029 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Canvas');
10030 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');
10031 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');
10032 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Glass');
10033 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Synthetics');
10034 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Skins');
10035 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Textile');
10036 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Metal');
10037 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');
10038 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('o',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Paper');
10039 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('p',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Plaster');
10040 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Hardboard');
10041 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Porcelain');
10042 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stone');
10043 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('t',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Wood');
10044 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10045 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10046
10047 -- Motion Picture
10048 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('m','Motion Picture');
10049 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','b','1','1','SMD');
10050 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');
10051 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');
10052 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');
10053 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10054 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10055 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','d','3','1','Color');
10056 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');
10057 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10058 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');
10059 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10060 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10061 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10062 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','e','4','1','Motion picture presentation format');
10063 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');
10064 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)');
10065 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'3D');
10066 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)');
10067 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');
10068 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');
10069 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10070 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10071 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');
10072 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');
10073 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');
10074 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10075 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','g','6','1','Medium for sound');
10076 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');
10077 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');
10078 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');
10079 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');
10080 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');
10081 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');
10082 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');
10083 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10084 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10087 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','h','7','1','Dimensions');
10088 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.');
10089 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.');
10090 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.');
10091 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.');
10092 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.');
10093 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.');
10094 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.');
10095 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10096 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10097 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','i','8','1','Configuration of playback channels');
10098 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10099 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10100 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');
10101 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');
10102 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10103 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10104 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10105 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('m','j','9','1','Production elements');
10106 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');
10107 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Trims');
10108 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Outtakes');
10109 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Rushes');
10110 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');
10111 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');
10112 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');
10113 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');
10114 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10115
10116 -- Remote-sensing Image
10117 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('r','Remote-sensing Image');
10118 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','b','1','1','SMD');
10119 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10120 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','d','3','1','Altitude of sensor');
10121 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Surface');
10122 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Airborne');
10123 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Spaceborne');
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 ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10126 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10127 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','e','4','1','Attitude of sensor');
10128 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');
10129 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');
10130 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Vertical');
10131 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');
10132 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10133 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','f','5','1','Cloud cover');
10134 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%');
10135 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%');
10136 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%');
10137 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%');
10138 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%');
10139 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%');
10140 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%');
10141 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%');
10142 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%');
10143 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%');
10144 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');
10145 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10146 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','g','6','1','Platform construction type');
10147 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Balloon');
10148 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');
10149 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');
10150 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');
10151 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');
10152 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');
10153 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');
10154 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');
10155 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');
10156 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');
10157 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10158 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10159 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','h','7','1','Platform use category');
10160 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Meteorological');
10161 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');
10162 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');
10163 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');
10164 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');
10165 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10166 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10167 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','i','8','1','Sensor type');
10168 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Active');
10169 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Passive');
10170 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10171 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10172 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('r','j','9','2','Data type');
10173 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');
10174 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');
10175 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');
10176 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');
10177 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');
10178 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)');
10179 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');
10180 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('dv',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Combinations');
10181 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');
10182 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)');
10183 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)');
10184 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)');
10185 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');
10186 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');
10187 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');
10188 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');
10189 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');
10190 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');
10191 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');
10192 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');
10193 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');
10194 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');
10195 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');
10196 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');
10197 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');
10198 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');
10199 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');
10200 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');
10201 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');
10202 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');
10203 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');
10204 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');
10205 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');
10206 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)');
10207 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');
10208 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rc',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Bouger');
10209 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('rd',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Isostatic');
10210 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');
10211 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');
10212 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('uu',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10213 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('zz',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10214
10215 -- Sound Recording
10216 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('s','Sound Recording');
10217 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','b','1','1','SMD');
10218 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');
10219 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('e',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Cylinder');
10220 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');
10221 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');
10222 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Roll');
10223 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');
10224 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');
10225 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10226 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');
10227 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10228 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','d','3','1','Speed');
10229 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');
10230 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');
10231 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');
10232 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');
10233 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');
10234 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');
10235 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');
10236 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');
10237 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');
10238 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');
10239 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');
10240 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');
10241 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');
10242 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');
10243 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10244 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10245 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','e','4','1','Configuration of playback channels');
10246 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10247 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('q',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadraphonic');
10248 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10249 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10250 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10251 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','f','5','1','Groove width or pitch');
10252 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');
10253 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');
10254 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');
10255 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10256 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10257 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','g','6','1','Dimensions');
10258 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.');
10259 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.');
10260 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.');
10261 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.');
10262 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.');
10263 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.');
10264 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.)');
10265 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.');
10266 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');
10267 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.');
10268 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.');
10269 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10270 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10271 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','h','7','1','Tape width');
10272 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.');
10273 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.');
10274 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');
10275 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.');
10276 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.');
10277 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10278 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10279 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','i','8','1','Tape configuration ');
10280 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');
10281 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');
10282 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');
10283 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');
10284 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');
10285 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');
10286 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');
10287 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10288 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10289 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','m','12','1','Special playback');
10290 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');
10291 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');
10292 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');
10293 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');
10294 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');
10295 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');
10296 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');
10297 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');
10298 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');
10299 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10300 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10301 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('s','n','13','1','Capture and storage');
10302 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');
10303 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');
10304 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');
10305 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');
10306 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10307 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10308
10309 -- Videorecording
10310 INSERT INTO config.marc21_physical_characteristic_type_map (ptype_key, label) VALUES ('v','Videorecording');
10311 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','b','1','1','SMD');
10312 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocartridge');
10313 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
10314 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videocassette');
10315 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('r',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videoreel');
10316 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unspecified');
10317 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10318 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','d','3','1','Color');
10319 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');
10320 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('c',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Multicolored');
10321 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10322 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');
10323 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10324 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10325 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','e','4','1','Videorecording format');
10326 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('a',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Beta');
10327 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('b',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'VHS');
10328 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');
10329 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('d',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'EIAJ');
10330 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');
10331 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('f',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Quadruplex');
10332 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('g',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Laserdisc');
10333 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'CED');
10334 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Betacam');
10335 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');
10336 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');
10337 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');
10338 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');
10339 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.');
10340 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.');
10341 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10342 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('v',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'DVD');
10343 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10344 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');
10345 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');
10346 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');
10347 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10348 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','g','6','1','Medium for sound');
10349 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');
10350 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');
10351 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');
10352 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');
10353 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');
10354 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');
10355 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');
10356 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('h',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videotape');
10357 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('i',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Videodisc');
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_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10360 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','h','7','1','Dimensions');
10361 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.');
10362 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.');
10363 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.');
10364 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.');
10365 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.');
10366 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.');
10367 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10368 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10369 INSERT INTO config.marc21_physical_characteristic_subfield_map (ptype_key,subfield,start_pos,length,label) VALUES ('v','i','8','1','Configuration of playback channel');
10370 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('k',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Mixed');
10371 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('m',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Monaural');
10372 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');
10373 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');
10374 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('s',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Stereophonic');
10375 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('u',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Unknown');
10376 INSERT INTO config.marc21_physical_characteristic_value_map (value,ptype_subfield,label) VALUES ('z',CURRVAL('config.marc21_physical_characteristic_subfield_map_id_seq'),'Other');
10377
10378 -- Fixed Field position data -- 0-based!
10379 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '006', 'SER', 16, 1, ' ');
10380 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Alph', '008', 'SER', 33, 1, ' ');
10381 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'BKS', 5, 1, ' ');
10382 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'COM', 5, 1, ' ');
10383 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'REC', 5, 1, ' ');
10384 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SCO', 5, 1, ' ');
10385 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'SER', 5, 1, ' ');
10386 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '006', 'VIS', 5, 1, ' ');
10387 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'BKS', 22, 1, ' ');
10388 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'COM', 22, 1, ' ');
10389 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'REC', 22, 1, ' ');
10390 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SCO', 22, 1, ' ');
10391 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'SER', 22, 1, ' ');
10392 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Audn', '008', 'VIS', 22, 1, ' ');
10393 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'BKS', 7, 1, 'm');
10394 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'COM', 7, 1, 'm');
10395 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MAP', 7, 1, 'm');
10396 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'MIX', 7, 1, 'c');
10397 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'REC', 7, 1, 'm');
10398 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SCO', 7, 1, 'm');
10399 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'SER', 7, 1, 's');
10400 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('BLvl', 'ldr', 'VIS', 7, 1, 'm');
10401 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '006', 'BKS', 17, 1, ' ');
10402 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Biog', '008', 'BKS', 34, 1, ' ');
10403 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'BKS', 7, 4, ' ');
10404 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '006', 'SER', 8, 3, ' ');
10405 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'BKS', 24, 4, ' ');
10406 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Conf', '008', 'SER', 25, 3, ' ');
10407 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'BKS', 8, 1, ' ');
10408 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'COM', 8, 1, ' ');
10409 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MAP', 8, 1, ' ');
10410 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'MIX', 8, 1, ' ');
10411 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'REC', 8, 1, ' ');
10412 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SCO', 8, 1, ' ');
10413 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'SER', 8, 1, ' ');
10414 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctrl', 'ldr', 'VIS', 8, 1, ' ');
10415 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'BKS', 15, 3, ' ');
10416 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'COM', 15, 3, ' ');
10417 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MAP', 15, 3, ' ');
10418 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'MIX', 15, 3, ' ');
10419 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'REC', 15, 3, ' ');
10420 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SCO', 15, 3, ' ');
10421 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'SER', 15, 3, ' ');
10422 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ctry', '008', 'VIS', 15, 3, ' ');
10423 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'BKS', 7, 4, ' ');
10424 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'COM', 7, 4, ' ');
10425 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MAP', 7, 4, ' ');
10426 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'MIX', 7, 4, ' ');
10427 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'REC', 7, 4, ' ');
10428 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SCO', 7, 4, ' ');
10429 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'SER', 7, 4, ' ');
10430 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date1', '008', 'VIS', 7, 4, ' ');
10431 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'BKS', 11, 4, ' ');
10432 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'COM', 11, 4, ' ');
10433 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MAP', 11, 4, ' ');
10434 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'MIX', 11, 4, ' ');
10435 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'REC', 11, 4, ' ');
10436 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SCO', 11, 4, ' ');
10437 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'SER', 11, 4, '9');
10438 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Date2', '008', 'VIS', 11, 4, ' ');
10439 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'BKS', 18, 1, ' ');
10440 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'COM', 18, 1, ' ');
10441 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MAP', 18, 1, ' ');
10442 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'MIX', 18, 1, ' ');
10443 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'REC', 18, 1, ' ');
10444 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SCO', 18, 1, ' ');
10445 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'SER', 18, 1, ' ');
10446 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Desc', 'ldr', 'VIS', 18, 1, ' ');
10447 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'BKS', 6, 1, ' ');
10448 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'COM', 6, 1, ' ');
10449 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MAP', 6, 1, ' ');
10450 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'MIX', 6, 1, ' ');
10451 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'REC', 6, 1, ' ');
10452 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SCO', 6, 1, ' ');
10453 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'SER', 6, 1, 'c');
10454 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('DtSt', '008', 'VIS', 6, 1, ' ');
10455 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'BKS', 17, 1, ' ');
10456 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'COM', 17, 1, ' ');
10457 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MAP', 17, 1, ' ');
10458 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'MIX', 17, 1, ' ');
10459 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'REC', 17, 1, ' ');
10460 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SCO', 17, 1, ' ');
10461 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'SER', 17, 1, ' ');
10462 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('ELvl', 'ldr', 'VIS', 17, 1, ' ');
10463 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '006', 'BKS', 13, 1, '0');
10464 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Fest', '008', 'BKS', 30, 1, '0');
10465 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'BKS', 6, 1, ' ');
10466 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MAP', 12, 1, ' ');
10467 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'MIX', 6, 1, ' ');
10468 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'REC', 6, 1, ' ');
10469 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SCO', 6, 1, ' ');
10470 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'SER', 6, 1, ' ');
10471 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '006', 'VIS', 12, 1, ' ');
10472 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'BKS', 23, 1, ' ');
10473 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MAP', 29, 1, ' ');
10474 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'MIX', 23, 1, ' ');
10475 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'REC', 23, 1, ' ');
10476 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SCO', 23, 1, ' ');
10477 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'SER', 23, 1, ' ');
10478 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Form', '008', 'VIS', 29, 1, ' ');
10479 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'BKS', 11, 1, ' ');
10480 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'COM', 11, 1, ' ');
10481 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'MAP', 11, 1, ' ');
10482 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'SER', 11, 1, ' ');
10483 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '006', 'VIS', 11, 1, ' ');
10484 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'BKS', 28, 1, ' ');
10485 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'COM', 28, 1, ' ');
10486 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'MAP', 28, 1, ' ');
10487 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'SER', 28, 1, ' ');
10488 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('GPub', '008', 'VIS', 28, 1, ' ');
10489 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '006', 'BKS', 1, 4, ' ');
10490 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Ills', '008', 'BKS', 18, 4, ' ');
10491 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'BKS', 14, 1, '0');
10492 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '006', 'MAP', 14, 1, '0');
10493 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'BKS', 31, 1, '0');
10494 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Indx', '008', 'MAP', 31, 1, '0');
10495 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'BKS', 35, 3, ' ');
10496 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'COM', 35, 3, ' ');
10497 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MAP', 35, 3, ' ');
10498 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'MIX', 35, 3, ' ');
10499 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'REC', 35, 3, ' ');
10500 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SCO', 35, 3, ' ');
10501 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'SER', 35, 3, ' ');
10502 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Lang', '008', 'VIS', 35, 3, ' ');
10503 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '006', 'BKS', 16, 1, '0');
10504 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('LitF', '008', 'BKS', 33, 1, '0');
10505 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'BKS', 38, 1, ' ');
10506 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'COM', 38, 1, ' ');
10507 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MAP', 38, 1, ' ');
10508 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'MIX', 38, 1, ' ');
10509 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'REC', 38, 1, ' ');
10510 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SCO', 38, 1, ' ');
10511 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'SER', 38, 1, ' ');
10512 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('MRec', '008', 'VIS', 38, 1, ' ');
10513 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');
10514 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');
10515 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '006', 'VIS', 16, 1, ' ');
10516 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('TMat', '008', 'VIS', 33, 1, ' ');
10517 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'BKS', 6, 1, 'a');
10518 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'COM', 6, 1, 'm');
10519 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MAP', 6, 1, 'e');
10520 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'MIX', 6, 1, 'p');
10521 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'REC', 6, 1, 'i');
10522 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SCO', 6, 1, 'c');
10523 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'SER', 6, 1, 'a');
10524 INSERT INTO config.marc21_ff_pos_map (fixed_field, tag, rec_type,start_pos, length, default_val) VALUES ('Type', 'ldr', 'VIS', 6, 1, 'g');
10525
10526 CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
10527 DECLARE
10528         ldr         RECORD;
10529         tval        TEXT;
10530         tval_rec    RECORD;
10531         bval        TEXT;
10532         bval_rec    RECORD;
10533     retval      config.marc21_rec_type_map%ROWTYPE;
10534 BEGIN
10535     SELECT * INTO ldr FROM metabib.full_rec WHERE record = rid AND tag = 'LDR' LIMIT 1;
10536
10537     IF ldr.id IS NULL THEN
10538         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10539         RETURN retval;
10540     END IF;
10541
10542     SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10543     SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10544
10545
10546     tval := SUBSTRING( ldr.value, tval_rec.start_pos + 1, tval_rec.length );
10547     bval := SUBSTRING( ldr.value, bval_rec.start_pos + 1, bval_rec.length );
10548
10549     -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr.value;
10550
10551     SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10552
10553
10554     IF retval.code IS NULL THEN
10555         SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
10556     END IF;
10557
10558     RETURN retval;
10559 END;
10560 $func$ LANGUAGE PLPGSQL;
10561
10562 CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
10563 DECLARE
10564     rtype       TEXT;
10565     ff_pos      RECORD;
10566     tag_data    RECORD;
10567     val         TEXT;
10568 BEGIN
10569     rtype := (biblio.marc21_record_type( rid )).code;
10570     FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
10571         FOR tag_data IN SELECT * FROM metabib.full_rec WHERE tag = UPPER(ff_pos.tag) AND record = rid LOOP
10572             val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
10573             RETURN val;
10574         END LOOP;
10575         val := REPEAT( ff_pos.default_val, ff_pos.length );
10576         RETURN val;
10577     END LOOP;
10578
10579     RETURN NULL;
10580 END;
10581 $func$ LANGUAGE PLPGSQL;
10582
10583 CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
10584 CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
10585 DECLARE
10586     rowid   INT := 0;
10587     _007    RECORD;
10588     ptype   config.marc21_physical_characteristic_type_map%ROWTYPE;
10589     psf     config.marc21_physical_characteristic_subfield_map%ROWTYPE;
10590     pval    config.marc21_physical_characteristic_value_map%ROWTYPE;
10591     retval  biblio.marc21_physical_characteristics%ROWTYPE;
10592 BEGIN
10593
10594     SELECT * INTO _007 FROM metabib.full_rec WHERE record = rid AND tag = '007' LIMIT 1;
10595
10596     IF _007.id IS NOT NULL THEN
10597         SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007.value, 1, 1 );
10598
10599         IF ptype.ptype_key IS NOT NULL THEN
10600             FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
10601                 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 );
10602
10603                 IF pval.id IS NOT NULL THEN
10604                     rowid := rowid + 1;
10605                     retval.id := rowid;
10606                     retval.record := rid;
10607                     retval.ptype := ptype.ptype_key;
10608                     retval.subfield := psf.id;
10609                     retval.value := pval.id;
10610                     RETURN NEXT retval;
10611                 END IF;
10612
10613             END LOOP;
10614         END IF;
10615     END IF;
10616
10617     RETURN;
10618 END;
10619 $func$ LANGUAGE PLPGSQL;
10620
10621 DROP VIEW IF EXISTS money.open_usr_circulation_summary;
10622 DROP VIEW IF EXISTS money.open_usr_summary;
10623 DROP VIEW IF EXISTS money.open_billable_xact_summary;
10624
10625 -- The view should supply defaults for numeric (amount) columns
10626 CREATE OR REPLACE VIEW money.billable_xact_summary AS
10627     SELECT  xact.id,
10628         xact.usr,
10629         xact.xact_start,
10630         xact.xact_finish,
10631         COALESCE(credit.amount, 0.0::numeric) AS total_paid,
10632         credit.payment_ts AS last_payment_ts,
10633         credit.note AS last_payment_note,
10634         credit.payment_type AS last_payment_type,
10635         COALESCE(debit.amount, 0.0::numeric) AS total_owed,
10636         debit.billing_ts AS last_billing_ts,
10637         debit.note AS last_billing_note,
10638         debit.billing_type AS last_billing_type,
10639         COALESCE(debit.amount, 0.0::numeric) - COALESCE(credit.amount, 0.0::numeric) AS balance_owed,
10640         p.relname AS xact_type
10641       FROM  money.billable_xact xact
10642         JOIN pg_class p ON xact.tableoid = p.oid
10643         LEFT JOIN (
10644             SELECT  billing.xact,
10645                 sum(billing.amount) AS amount,
10646                 max(billing.billing_ts) AS billing_ts,
10647                 last(billing.note) AS note,
10648                 last(billing.billing_type) AS billing_type
10649               FROM  money.billing
10650               WHERE billing.voided IS FALSE
10651               GROUP BY billing.xact
10652             ) debit ON xact.id = debit.xact
10653         LEFT JOIN (
10654             SELECT  payment_view.xact,
10655                 sum(payment_view.amount) AS amount,
10656                 max(payment_view.payment_ts) AS payment_ts,
10657                 last(payment_view.note) AS note,
10658                 last(payment_view.payment_type) AS payment_type
10659               FROM  money.payment_view
10660               WHERE payment_view.voided IS FALSE
10661               GROUP BY payment_view.xact
10662             ) credit ON xact.id = credit.xact
10663       ORDER BY debit.billing_ts, credit.payment_ts;
10664
10665 CREATE OR REPLACE VIEW money.open_billable_xact_summary AS 
10666     SELECT * FROM money.billable_xact_summary_location_view
10667     WHERE xact_finish IS NULL;
10668
10669 CREATE OR REPLACE VIEW money.open_usr_circulation_summary AS
10670     SELECT 
10671         usr,
10672         SUM(total_paid) AS total_paid,
10673         SUM(total_owed) AS total_owed,
10674         SUM(balance_owed) AS balance_owed
10675     FROM  money.materialized_billable_xact_summary
10676     WHERE xact_type = 'circulation' AND xact_finish IS NULL
10677     GROUP BY usr;
10678
10679 CREATE OR REPLACE VIEW money.usr_summary AS
10680     SELECT 
10681         usr, 
10682         sum(total_paid) AS total_paid, 
10683         sum(total_owed) AS total_owed, 
10684         sum(balance_owed) AS balance_owed
10685     FROM money.materialized_billable_xact_summary
10686     GROUP BY usr;
10687
10688 CREATE OR REPLACE VIEW money.open_usr_summary AS
10689     SELECT 
10690         usr, 
10691         sum(total_paid) AS total_paid, 
10692         sum(total_owed) AS total_owed, 
10693         sum(balance_owed) AS balance_owed
10694     FROM money.materialized_billable_xact_summary
10695     WHERE xact_finish IS NULL
10696     GROUP BY usr;
10697
10698 -- 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;
10699
10700 CREATE TABLE config.biblio_fingerprint (
10701         id                      SERIAL  PRIMARY KEY,
10702         name            TEXT    NOT NULL, 
10703         xpath           TEXT    NOT NULL,
10704     first_word  BOOL    NOT NULL DEFAULT FALSE,
10705         format          TEXT    NOT NULL DEFAULT 'marcxml'
10706 );
10707
10708 INSERT INTO config.biblio_fingerprint (name, xpath, format)
10709     VALUES (
10710         'Title',
10711         '//marc:datafield[@tag="700"]/marc:subfield[@code="t"]|' ||
10712             '//marc:datafield[@tag="240"]/marc:subfield[@code="a"]|' ||
10713             '//marc:datafield[@tag="242"]/marc:subfield[@code="a"]|' ||
10714             '//marc:datafield[@tag="246"]/marc:subfield[@code="a"]|' ||
10715             '//marc:datafield[@tag="245"]/marc:subfield[@code="a"]',
10716         'marcxml'
10717     );
10718
10719 INSERT INTO config.biblio_fingerprint (name, xpath, format, first_word)
10720     VALUES (
10721         'Author',
10722         '//marc:datafield[@tag="700" and ./*[@code="t"]]/marc:subfield[@code="a"]|'
10723             '//marc:datafield[@tag="100"]/marc:subfield[@code="a"]|'
10724             '//marc:datafield[@tag="110"]/marc:subfield[@code="a"]|'
10725             '//marc:datafield[@tag="111"]/marc:subfield[@code="a"]|'
10726             '//marc:datafield[@tag="260"]/marc:subfield[@code="b"]',
10727         'marcxml',
10728         TRUE
10729     );
10730
10731 CREATE OR REPLACE FUNCTION biblio.extract_quality ( marc TEXT, best_lang TEXT, best_type TEXT ) RETURNS INT AS $func$
10732 DECLARE
10733     qual        INT;
10734     ldr         TEXT;
10735     tval        TEXT;
10736     tval_rec    RECORD;
10737     bval        TEXT;
10738     bval_rec    RECORD;
10739     type_map    RECORD;
10740     ff_pos      RECORD;
10741     ff_tag_data TEXT;
10742 BEGIN
10743
10744     IF marc IS NULL OR marc = '' THEN
10745         RETURN NULL;
10746     END IF;
10747
10748     -- First, the count of tags
10749     qual := ARRAY_UPPER(oils_xpath('*[local-name()="datafield"]', marc), 1);
10750
10751     -- now go through a bunch of pain to get the record type
10752     IF best_type IS NOT NULL THEN
10753         ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];
10754
10755         IF ldr IS NOT NULL THEN
10756             SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
10757             SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
10758
10759
10760             tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
10761             bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
10762
10763             -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
10764
10765             SELECT * INTO type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
10766
10767             IF type_map.code IS NOT NULL THEN
10768                 IF best_type = type_map.code THEN
10769                     qual := qual + qual / 2;
10770                 END IF;
10771
10772                 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
10773                     ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
10774                     IF ff_tag_data = best_lang THEN
10775                             qual := qual + 100;
10776                     END IF;
10777                 END LOOP;
10778             END IF;
10779         END IF;
10780     END IF;
10781
10782     -- Now look for some quality metrics
10783     -- DCL record?
10784     IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
10785         qual := qual + 10;
10786     END IF;
10787
10788     -- From OCLC?
10789     IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
10790         qual := qual + 10;
10791     END IF;
10792
10793     RETURN qual;
10794
10795 END;
10796 $func$ LANGUAGE PLPGSQL;
10797
10798 CREATE OR REPLACE FUNCTION biblio.extract_fingerprint ( marc text ) RETURNS TEXT AS $func$
10799 DECLARE
10800     idx     config.biblio_fingerprint%ROWTYPE;
10801     xfrm        config.xml_transform%ROWTYPE;
10802     prev_xfrm   TEXT;
10803     transformed_xml TEXT;
10804     xml_node    TEXT;
10805     xml_node_list   TEXT[];
10806     raw_text    TEXT;
10807     output_text TEXT := '';
10808 BEGIN
10809
10810     IF marc IS NULL OR marc = '' THEN
10811         RETURN NULL;
10812     END IF;
10813
10814     -- Loop over the indexing entries
10815     FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP
10816
10817         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
10818
10819         -- See if we can skip the XSLT ... it's expensive
10820         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
10821             -- Can't skip the transform
10822             IF xfrm.xslt <> '---' THEN
10823                 transformed_xml := oils_xslt_process(marc,xfrm.xslt);
10824             ELSE
10825                 transformed_xml := marc;
10826             END IF;
10827
10828             prev_xfrm := xfrm.name;
10829         END IF;
10830
10831         raw_text := COALESCE(
10832             naco_normalize(
10833                 ARRAY_TO_STRING(
10834                     oils_xpath(
10835                         '//text()',
10836                         (oils_xpath(
10837                             idx.xpath,
10838                             transformed_xml,
10839                             ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
10840                         ))[1]
10841                     ),
10842                     ''
10843                 )
10844             ),
10845             ''
10846         );
10847
10848         raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
10849         raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!
10850
10851         IF idx.first_word IS TRUE THEN
10852             raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
10853         END IF;
10854
10855         output_text := output_text || REGEXP_REPLACE(raw_text, E'\\s+', '', 'g');
10856
10857     END LOOP;
10858
10859     RETURN output_text;
10860
10861 END;
10862 $func$ LANGUAGE PLPGSQL;
10863
10864 -- BEFORE UPDATE OR INSERT trigger for biblio.record_entry
10865 CREATE OR REPLACE FUNCTION biblio.fingerprint_trigger () RETURNS TRIGGER AS $func$
10866 BEGIN
10867
10868     -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')
10869
10870     IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
10871         RETURN NEW;
10872     END IF;
10873
10874     NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
10875     NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);
10876
10877     RETURN NEW;
10878
10879 END;
10880 $func$ LANGUAGE PLPGSQL;
10881
10882 CREATE TABLE config.internal_flag (
10883     name    TEXT    PRIMARY KEY,
10884     value   TEXT,
10885     enabled BOOL    NOT NULL DEFAULT FALSE
10886 );
10887 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_insert');
10888 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.force_on_same_marc');
10889 INSERT INTO config.internal_flag (name) VALUES ('ingest.reingest.skip_located_uri');
10890 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_located_uri');
10891 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_full_rec');
10892 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_rec_descriptor');
10893 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_metabib_field_entry');
10894 INSERT INTO config.internal_flag (name) VALUES ('ingest.disable_authority_linking');
10895 INSERT INTO config.internal_flag (name) VALUES ('ingest.metarecord_mapping.skip_on_update');
10896 INSERT INTO config.internal_flag (name) VALUES ('ingest.assume_inserts_only');
10897
10898 CREATE TABLE authority.bib_linking (
10899     id          BIGSERIAL   PRIMARY KEY,
10900     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
10901     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
10902 );
10903 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
10904 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
10905
10906 CREATE OR REPLACE FUNCTION public.remove_paren_substring( TEXT ) RETURNS TEXT AS $func$
10907     SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');
10908 $func$ LANGUAGE SQL STRICT IMMUTABLE;
10909
10910 CREATE OR REPLACE FUNCTION biblio.map_authority_linking (bibid BIGINT, marc TEXT) RETURNS BIGINT AS $func$
10911     DELETE FROM authority.bib_linking WHERE bib = $1;
10912     INSERT INTO authority.bib_linking (bib, authority)
10913         SELECT  y.bib,
10914                 y.authority
10915           FROM (    SELECT  DISTINCT $1 AS bib,
10916                             BTRIM(remove_paren_substring(txt))::BIGINT AS authority
10917                       FROM  explode_array(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
10918                       WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
10919                 ) y JOIN authority.record_entry r ON r.id = y.authority;
10920     SELECT $1;
10921 $func$ LANGUAGE SQL;
10922
10923 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_rec_descriptor( bib_id BIGINT ) RETURNS VOID AS $func$
10924 BEGIN
10925     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10926     IF NOT FOUND THEN
10927         DELETE FROM metabib.rec_descriptor WHERE record = bib_id;
10928     END IF;
10929     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)
10930         SELECT  bib_id,
10931                 biblio.marc21_extract_fixed_field( bib_id, 'Type' ),
10932                 biblio.marc21_extract_fixed_field( bib_id, 'Form' ),
10933                 biblio.marc21_extract_fixed_field( bib_id, 'BLvl' ),
10934                 biblio.marc21_extract_fixed_field( bib_id, 'Ctrl' ),
10935                 biblio.marc21_extract_fixed_field( bib_id, 'ELvl' ),
10936                 biblio.marc21_extract_fixed_field( bib_id, 'Audn' ),
10937                 biblio.marc21_extract_fixed_field( bib_id, 'LitF' ),
10938                 biblio.marc21_extract_fixed_field( bib_id, 'TMat' ),
10939                 biblio.marc21_extract_fixed_field( bib_id, 'Desc' ),
10940                 biblio.marc21_extract_fixed_field( bib_id, 'DtSt' ),
10941                 biblio.marc21_extract_fixed_field( bib_id, 'Lang' ),
10942                 (   SELECT  v.value
10943                       FROM  biblio.marc21_physical_characteristics( bib_id) p
10944                             JOIN config.marc21_physical_characteristic_subfield_map s ON (s.id = p.subfield)
10945                             JOIN config.marc21_physical_characteristic_value_map v ON (v.id = p.value)
10946                       WHERE p.ptype = 'v' AND s.subfield = 'e'    ),
10947                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date1'), ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
10948                 LPAD(NULLIF(REGEXP_REPLACE(NULLIF(biblio.marc21_extract_fixed_field( bib_id, 'Date2'), ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
10949
10950     RETURN;
10951 END;
10952 $func$ LANGUAGE PLPGSQL;
10953
10954 CREATE TABLE config.metabib_class (
10955     name    TEXT    PRIMARY KEY,
10956     label   TEXT    NOT NULL UNIQUE
10957 );
10958
10959 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'keyword', oils_i18n_gettext('keyword', 'Keyword', 'cmc', 'label') );
10960 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'title', oils_i18n_gettext('title', 'Title', 'cmc', 'label') );
10961 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'author', oils_i18n_gettext('author', 'Author', 'cmc', 'label') );
10962 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'subject', oils_i18n_gettext('subject', 'Subject', 'cmc', 'label') );
10963 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'series', oils_i18n_gettext('series', 'Series', 'cmc', 'label') );
10964
10965 CREATE TABLE metabib.facet_entry (
10966         id              BIGSERIAL       PRIMARY KEY,
10967         source          BIGINT          NOT NULL,
10968         field           INT             NOT NULL,
10969         value           TEXT            NOT NULL
10970 );
10971
10972 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( bib_id BIGINT ) RETURNS VOID AS $func$
10973 DECLARE
10974     fclass          RECORD;
10975     ind_data        metabib.field_entry_template%ROWTYPE;
10976 BEGIN
10977     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
10978     IF NOT FOUND THEN
10979         FOR fclass IN SELECT * FROM config.metabib_class LOOP
10980             -- RAISE NOTICE 'Emptying out %', fclass.name;
10981             EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
10982         END LOOP;
10983         DELETE FROM metabib.facet_entry WHERE source = bib_id;
10984     END IF;
10985
10986     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
10987         IF ind_data.field < 0 THEN
10988             ind_data.field = -1 * ind_data.field;
10989             INSERT INTO metabib.facet_entry (field, source, value)
10990                 VALUES (ind_data.field, ind_data.source, ind_data.value);
10991         ELSE
10992             EXECUTE $$
10993                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
10994                     VALUES ($$ ||
10995                         quote_literal(ind_data.field) || $$, $$ ||
10996                         quote_literal(ind_data.source) || $$, $$ ||
10997                         quote_literal(ind_data.value) ||
10998                     $$);$$;
10999         END IF;
11000
11001     END LOOP;
11002
11003     RETURN;
11004 END;
11005 $func$ LANGUAGE PLPGSQL;
11006
11007 CREATE OR REPLACE FUNCTION biblio.extract_located_uris( bib_id BIGINT, marcxml TEXT, editor_id INT ) RETURNS VOID AS $func$
11008 DECLARE
11009     uris            TEXT[];
11010     uri_xml         TEXT;
11011     uri_label       TEXT;
11012     uri_href        TEXT;
11013     uri_use         TEXT;
11014     uri_owner       TEXT;
11015     uri_owner_id    INT;
11016     uri_id          INT;
11017     uri_cn_id       INT;
11018     uri_map_id      INT;
11019 BEGIN
11020
11021     uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
11022     IF ARRAY_UPPER(uris,1) > 0 THEN
11023         FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
11024             -- First we pull info out of the 856
11025             uri_xml     := uris[i];
11026
11027             uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
11028             CONTINUE WHEN uri_href IS NULL;
11029
11030             uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11031             CONTINUE WHEN uri_label IS NULL;
11032
11033             uri_owner   := (oils_xpath('//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',uri_xml))[1];
11034             CONTINUE WHEN uri_owner IS NULL;
11035
11036             uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()|//*[@code="u"]/text()',uri_xml))[1];
11037
11038             uri_owner := REGEXP_REPLACE(uri_owner, $re$^.*?\((\w+)\).*$$re$, E'\\1');
11039
11040             SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = uri_owner;
11041             CONTINUE WHEN NOT FOUND;
11042
11043             -- now we look for a matching uri
11044             SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11045             IF NOT FOUND THEN -- create one
11046                 INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
11047                 SELECT id INTO uri_id FROM asset.uri WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
11048             END IF;
11049
11050             -- we need a call number to link through
11051             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;
11052             IF NOT FOUND THEN
11053                 INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
11054                     VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
11055                 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;
11056             END IF;
11057
11058             -- now, link them if they're not already
11059             SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
11060             IF NOT FOUND THEN
11061                 INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
11062             END IF;
11063
11064         END LOOP;
11065     END IF;
11066
11067     RETURN;
11068 END;
11069 $func$ LANGUAGE PLPGSQL;
11070
11071 CREATE OR REPLACE FUNCTION metabib.remap_metarecord_for_bib( bib_id BIGINT, fp TEXT ) RETURNS BIGINT AS $func$
11072 DECLARE
11073     source_count    INT;
11074     old_mr          BIGINT;
11075     tmp_mr          metabib.metarecord%ROWTYPE;
11076     deleted_mrs     BIGINT[];
11077 BEGIN
11078
11079     DELETE FROM metabib.metarecord_source_map WHERE source = bib_id; -- Rid ourselves of the search-estimate-killing linkage
11080
11081     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
11082
11083         IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN -- Find the first fingerprint-matching
11084             old_mr := tmp_mr.id;
11085         ELSE
11086             SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
11087             IF source_count = 0 THEN -- No other records
11088                 deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
11089                 DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
11090             END IF;
11091         END IF;
11092
11093     END LOOP;
11094
11095     IF old_mr IS NULL THEN -- we found no suitable, preexisting MR based on old source maps
11096         SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?
11097         IF old_mr IS NULL THEN -- nope, create one and grab its id
11098             INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
11099             SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;
11100         ELSE -- indeed there is. update it with a null cache and recalcualated master record
11101             UPDATE  metabib.metarecord
11102               SET   mods = NULL,
11103                     master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11104               WHERE id = old_mr;
11105         END IF;
11106     ELSE -- there was one we already attached to, update its mods cache and master_record
11107         UPDATE  metabib.metarecord
11108           SET   mods = NULL,
11109                 master_record = ( SELECT id FROM biblio.record_entry WHERE fingerprint = fp ORDER BY quality DESC LIMIT 1)
11110           WHERE id = old_mr;
11111     END IF;
11112
11113     INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
11114
11115     IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
11116         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
11117     END IF;
11118
11119     RETURN old_mr;
11120
11121 END;
11122 $func$ LANGUAGE PLPGSQL;
11123
11124 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_full_rec( bib_id BIGINT ) RETURNS VOID AS $func$
11125 BEGIN
11126     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
11127     IF NOT FOUND THEN
11128         DELETE FROM metabib.real_full_rec WHERE record = bib_id;
11129     END IF;
11130     INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
11131         SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );
11132
11133     RETURN;
11134 END;
11135 $func$ LANGUAGE PLPGSQL;
11136
11137 -- AFTER UPDATE OR INSERT trigger for biblio.record_entry
11138 CREATE OR REPLACE FUNCTION biblio.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
11139 BEGIN
11140
11141     IF NEW.deleted IS TRUE THEN -- If this bib is deleted
11142         DELETE FROM metabib.metarecord_source_map WHERE source = NEW.id; -- Rid ourselves of the search-estimate-killing linkage
11143         DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
11144         RETURN NEW; -- and we're done
11145     END IF;
11146
11147     IF TG_OP = 'UPDATE' THEN -- re-ingest?
11148         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
11149
11150         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
11151             RETURN NEW;
11152         END IF;
11153     END IF;
11154
11155     -- Record authority linking
11156     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
11157     IF NOT FOUND THEN
11158         PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
11159     END IF;
11160
11161     -- Flatten and insert the mfr data
11162     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
11163     IF NOT FOUND THEN
11164         PERFORM metabib.reingest_metabib_full_rec(NEW.id);
11165         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
11166         IF NOT FOUND THEN
11167             PERFORM metabib.reingest_metabib_rec_descriptor(NEW.id);
11168         END IF;
11169     END IF;
11170
11171     -- Gather and insert the field entry data
11172     PERFORM metabib.reingest_metabib_field_entries(NEW.id);
11173
11174     -- Located URI magic
11175     IF TG_OP = 'INSERT' THEN
11176         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11177         IF NOT FOUND THEN
11178             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11179         END IF;
11180     ELSE
11181         PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
11182         IF NOT FOUND THEN
11183             PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor );
11184         END IF;
11185     END IF;
11186
11187     -- (re)map metarecord-bib linking
11188     IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
11189         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
11190         IF NOT FOUND THEN
11191             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11192         END IF;
11193     ELSE -- we're doing an update, and we're not deleted, remap
11194         PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
11195         IF NOT FOUND THEN
11196             PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
11197         END IF;
11198     END IF;
11199
11200     RETURN NEW;
11201 END;
11202 $func$ LANGUAGE PLPGSQL;
11203
11204 CREATE TRIGGER fingerprint_tgr BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.fingerprint_trigger ('eng','BKS');
11205 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 ();
11206
11207 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_record_tgr ON metabib.real_full_rec;
11208 DROP TRIGGER IF EXISTS zzz_update_materialized_simple_rec_delete_tgr ON biblio.record_entry;
11209
11210 CREATE OR REPLACE FUNCTION oils_xpath_table ( key TEXT, document_field TEXT, relation_name TEXT, xpaths TEXT, criteria TEXT ) RETURNS SETOF RECORD AS $func$
11211 DECLARE
11212     xpath_list  TEXT[];
11213     select_list TEXT[];
11214     where_list  TEXT[];
11215     q           TEXT;
11216     out_record  RECORD;
11217     empty_test  RECORD;
11218 BEGIN
11219     xpath_list := STRING_TO_ARRAY( xpaths, '|' );
11220  
11221     select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );
11222  
11223     FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
11224         IF xpath_list[i] = 'null()' THEN
11225             select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
11226         ELSE
11227             select_list := ARRAY_APPEND(
11228                 select_list,
11229                 $sel$
11230                 EXPLODE_ARRAY(
11231                     COALESCE(
11232                         NULLIF(
11233                             oils_xpath(
11234                                 $sel$ ||
11235                                     quote_literal(
11236                                         CASE
11237                                             WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
11238                                             ELSE xpath_list[i] || '//text()'
11239                                         END
11240                                     ) ||
11241                                 $sel$,
11242                                 $sel$ || document_field || $sel$
11243                             ),
11244                            '{}'::TEXT[]
11245                         ),
11246                         '{NULL}'::TEXT[]
11247                     )
11248                 ) AS c_$sel$ || i
11249             );
11250             where_list := ARRAY_APPEND(
11251                 where_list,
11252                 'c_' || i || ' IS NOT NULL'
11253             );
11254         END IF;
11255     END LOOP;
11256  
11257     q := $q$
11258 SELECT * FROM (
11259     SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
11260 )x WHERE $q$ || ARRAY_TO_STRING( where_list, ' AND ' );
11261     -- RAISE NOTICE 'query: %', q;
11262  
11263     FOR out_record IN EXECUTE q LOOP
11264         RETURN NEXT out_record;
11265     END LOOP;
11266  
11267     RETURN;
11268 END;
11269 $func$ LANGUAGE PLPGSQL IMMUTABLE;
11270
11271 CREATE OR REPLACE FUNCTION vandelay.ingest_items ( import_id BIGINT, attr_def_id BIGINT ) RETURNS SETOF vandelay.import_item AS $$
11272 DECLARE
11273
11274     owning_lib      TEXT;
11275     circ_lib        TEXT;
11276     call_number     TEXT;
11277     copy_number     TEXT;
11278     status          TEXT;
11279     location        TEXT;
11280     circulate       TEXT;
11281     deposit         TEXT;
11282     deposit_amount  TEXT;
11283     ref             TEXT;
11284     holdable        TEXT;
11285     price           TEXT;
11286     barcode         TEXT;
11287     circ_modifier   TEXT;
11288     circ_as_type    TEXT;
11289     alert_message   TEXT;
11290     opac_visible    TEXT;
11291     pub_note        TEXT;
11292     priv_note       TEXT;
11293
11294     attr_def        RECORD;
11295     tmp_attr_set    RECORD;
11296     attr_set        vandelay.import_item%ROWTYPE;
11297
11298     xpath           TEXT;
11299
11300 BEGIN
11301
11302     SELECT * INTO attr_def FROM vandelay.import_item_attr_definition WHERE id = attr_def_id;
11303
11304     IF FOUND THEN
11305
11306         attr_set.definition := attr_def.id; 
11307     
11308         -- Build the combined XPath
11309     
11310         owning_lib :=
11311             CASE
11312                 WHEN attr_def.owning_lib IS NULL THEN 'null()'
11313                 WHEN LENGTH( attr_def.owning_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.owning_lib || '"]'
11314                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.owning_lib
11315             END;
11316     
11317         circ_lib :=
11318             CASE
11319                 WHEN attr_def.circ_lib IS NULL THEN 'null()'
11320                 WHEN LENGTH( attr_def.circ_lib ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_lib || '"]'
11321                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_lib
11322             END;
11323     
11324         call_number :=
11325             CASE
11326                 WHEN attr_def.call_number IS NULL THEN 'null()'
11327                 WHEN LENGTH( attr_def.call_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.call_number || '"]'
11328                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.call_number
11329             END;
11330     
11331         copy_number :=
11332             CASE
11333                 WHEN attr_def.copy_number IS NULL THEN 'null()'
11334                 WHEN LENGTH( attr_def.copy_number ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.copy_number || '"]'
11335                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.copy_number
11336             END;
11337     
11338         status :=
11339             CASE
11340                 WHEN attr_def.status IS NULL THEN 'null()'
11341                 WHEN LENGTH( attr_def.status ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.status || '"]'
11342                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.status
11343             END;
11344     
11345         location :=
11346             CASE
11347                 WHEN attr_def.location IS NULL THEN 'null()'
11348                 WHEN LENGTH( attr_def.location ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.location || '"]'
11349                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.location
11350             END;
11351     
11352         circulate :=
11353             CASE
11354                 WHEN attr_def.circulate IS NULL THEN 'null()'
11355                 WHEN LENGTH( attr_def.circulate ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circulate || '"]'
11356                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circulate
11357             END;
11358     
11359         deposit :=
11360             CASE
11361                 WHEN attr_def.deposit IS NULL THEN 'null()'
11362                 WHEN LENGTH( attr_def.deposit ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit || '"]'
11363                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit
11364             END;
11365     
11366         deposit_amount :=
11367             CASE
11368                 WHEN attr_def.deposit_amount IS NULL THEN 'null()'
11369                 WHEN LENGTH( attr_def.deposit_amount ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.deposit_amount || '"]'
11370                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.deposit_amount
11371             END;
11372     
11373         ref :=
11374             CASE
11375                 WHEN attr_def.ref IS NULL THEN 'null()'
11376                 WHEN LENGTH( attr_def.ref ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.ref || '"]'
11377                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.ref
11378             END;
11379     
11380         holdable :=
11381             CASE
11382                 WHEN attr_def.holdable IS NULL THEN 'null()'
11383                 WHEN LENGTH( attr_def.holdable ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.holdable || '"]'
11384                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.holdable
11385             END;
11386     
11387         price :=
11388             CASE
11389                 WHEN attr_def.price IS NULL THEN 'null()'
11390                 WHEN LENGTH( attr_def.price ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.price || '"]'
11391                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.price
11392             END;
11393     
11394         barcode :=
11395             CASE
11396                 WHEN attr_def.barcode IS NULL THEN 'null()'
11397                 WHEN LENGTH( attr_def.barcode ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.barcode || '"]'
11398                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.barcode
11399             END;
11400     
11401         circ_modifier :=
11402             CASE
11403                 WHEN attr_def.circ_modifier IS NULL THEN 'null()'
11404                 WHEN LENGTH( attr_def.circ_modifier ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_modifier || '"]'
11405                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_modifier
11406             END;
11407     
11408         circ_as_type :=
11409             CASE
11410                 WHEN attr_def.circ_as_type IS NULL THEN 'null()'
11411                 WHEN LENGTH( attr_def.circ_as_type ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.circ_as_type || '"]'
11412                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.circ_as_type
11413             END;
11414     
11415         alert_message :=
11416             CASE
11417                 WHEN attr_def.alert_message IS NULL THEN 'null()'
11418                 WHEN LENGTH( attr_def.alert_message ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.alert_message || '"]'
11419                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.alert_message
11420             END;
11421     
11422         opac_visible :=
11423             CASE
11424                 WHEN attr_def.opac_visible IS NULL THEN 'null()'
11425                 WHEN LENGTH( attr_def.opac_visible ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.opac_visible || '"]'
11426                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.opac_visible
11427             END;
11428
11429         pub_note :=
11430             CASE
11431                 WHEN attr_def.pub_note IS NULL THEN 'null()'
11432                 WHEN LENGTH( attr_def.pub_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.pub_note || '"]'
11433                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.pub_note
11434             END;
11435         priv_note :=
11436             CASE
11437                 WHEN attr_def.priv_note IS NULL THEN 'null()'
11438                 WHEN LENGTH( attr_def.priv_note ) = 1 THEN '//*[@tag="' || attr_def.tag || '"]/*[@code="' || attr_def.priv_note || '"]'
11439                 ELSE '//*[@tag="' || attr_def.tag || '"]/*' || attr_def.priv_note
11440             END;
11441     
11442     
11443         xpath := 
11444             owning_lib      || '|' || 
11445             circ_lib        || '|' || 
11446             call_number     || '|' || 
11447             copy_number     || '|' || 
11448             status          || '|' || 
11449             location        || '|' || 
11450             circulate       || '|' || 
11451             deposit         || '|' || 
11452             deposit_amount  || '|' || 
11453             ref             || '|' || 
11454             holdable        || '|' || 
11455             price           || '|' || 
11456             barcode         || '|' || 
11457             circ_modifier   || '|' || 
11458             circ_as_type    || '|' || 
11459             alert_message   || '|' || 
11460             pub_note        || '|' || 
11461             priv_note       || '|' || 
11462             opac_visible;
11463
11464         -- RAISE NOTICE 'XPath: %', xpath;
11465         
11466         FOR tmp_attr_set IN
11467                 SELECT  *
11468                   FROM  oils_xpath_table( 'id', 'marc', 'vandelay.queued_bib_record', xpath, 'id = ' || import_id )
11469                             AS t( id INT, ol TEXT, clib TEXT, cn TEXT, cnum TEXT, cs TEXT, cl TEXT, circ TEXT,
11470                                   dep TEXT, dep_amount TEXT, r TEXT, hold TEXT, pr TEXT, bc TEXT, circ_mod TEXT,
11471                                   circ_as TEXT, amessage TEXT, note TEXT, pnote TEXT, opac_vis TEXT )
11472         LOOP
11473     
11474             tmp_attr_set.pr = REGEXP_REPLACE(tmp_attr_set.pr, E'[^0-9\\.]', '', 'g');
11475             tmp_attr_set.dep_amount = REGEXP_REPLACE(tmp_attr_set.dep_amount, E'[^0-9\\.]', '', 'g');
11476
11477             tmp_attr_set.pr := NULLIF( tmp_attr_set.pr, '' );
11478             tmp_attr_set.dep_amount := NULLIF( tmp_attr_set.dep_amount, '' );
11479     
11480             SELECT id INTO attr_set.owning_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.ol); -- INT
11481             SELECT id INTO attr_set.circ_lib FROM actor.org_unit WHERE shortname = UPPER(tmp_attr_set.clib); -- INT
11482             SELECT id INTO attr_set.status FROM config.copy_status WHERE LOWER(name) = LOWER(tmp_attr_set.cs); -- INT
11483     
11484             SELECT  id INTO attr_set.location
11485               FROM  asset.copy_location
11486               WHERE LOWER(name) = LOWER(tmp_attr_set.cl)
11487                     AND asset.copy_location.owning_lib = COALESCE(attr_set.owning_lib, attr_set.circ_lib); -- INT
11488     
11489             attr_set.circulate      :=
11490                 LOWER( SUBSTRING( tmp_attr_set.circ, 1, 1)) IN ('t','y','1')
11491                 OR LOWER(tmp_attr_set.circ) = 'circulating'; -- BOOL
11492
11493             attr_set.deposit        :=
11494                 LOWER( SUBSTRING( tmp_attr_set.dep, 1, 1 ) ) IN ('t','y','1')
11495                 OR LOWER(tmp_attr_set.dep) = 'deposit'; -- BOOL
11496
11497             attr_set.holdable       :=
11498                 LOWER( SUBSTRING( tmp_attr_set.hold, 1, 1 ) ) IN ('t','y','1')
11499                 OR LOWER(tmp_attr_set.hold) = 'holdable'; -- BOOL
11500
11501             attr_set.opac_visible   :=
11502                 LOWER( SUBSTRING( tmp_attr_set.opac_vis, 1, 1 ) ) IN ('t','y','1')
11503                 OR LOWER(tmp_attr_set.opac_vis) = 'visible'; -- BOOL
11504
11505             attr_set.ref            :=
11506                 LOWER( SUBSTRING( tmp_attr_set.r, 1, 1 ) ) IN ('t','y','1')
11507                 OR LOWER(tmp_attr_set.r) = 'reference'; -- BOOL
11508     
11509             attr_set.copy_number    := tmp_attr_set.cnum::INT; -- INT,
11510             attr_set.deposit_amount := tmp_attr_set.dep_amount::NUMERIC(6,2); -- NUMERIC(6,2),
11511             attr_set.price          := tmp_attr_set.pr::NUMERIC(8,2); -- NUMERIC(8,2),
11512     
11513             attr_set.call_number    := tmp_attr_set.cn; -- TEXT
11514             attr_set.barcode        := tmp_attr_set.bc; -- TEXT,
11515             attr_set.circ_modifier  := tmp_attr_set.circ_mod; -- TEXT,
11516             attr_set.circ_as_type   := tmp_attr_set.circ_as; -- TEXT,
11517             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11518             attr_set.pub_note       := tmp_attr_set.note; -- TEXT,
11519             attr_set.priv_note      := tmp_attr_set.pnote; -- TEXT,
11520             attr_set.alert_message  := tmp_attr_set.amessage; -- TEXT,
11521     
11522             RETURN NEXT attr_set;
11523     
11524         END LOOP;
11525     
11526     END IF;
11527
11528     RETURN;
11529
11530 END;
11531 $$ LANGUAGE PLPGSQL;
11532
11533 CREATE OR REPLACE FUNCTION vandelay.ingest_bib_items ( ) RETURNS TRIGGER AS $func$
11534 DECLARE
11535     attr_def    BIGINT;
11536     item_data   vandelay.import_item%ROWTYPE;
11537 BEGIN
11538
11539     SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
11540
11541     FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
11542         INSERT INTO vandelay.import_item (
11543             record,
11544             definition,
11545             owning_lib,
11546             circ_lib,
11547             call_number,
11548             copy_number,
11549             status,
11550             location,
11551             circulate,
11552             deposit,
11553             deposit_amount,
11554             ref,
11555             holdable,
11556             price,
11557             barcode,
11558             circ_modifier,
11559             circ_as_type,
11560             alert_message,
11561             pub_note,
11562             priv_note,
11563             opac_visible
11564         ) VALUES (
11565             NEW.id,
11566             item_data.definition,
11567             item_data.owning_lib,
11568             item_data.circ_lib,
11569             item_data.call_number,
11570             item_data.copy_number,
11571             item_data.status,
11572             item_data.location,
11573             item_data.circulate,
11574             item_data.deposit,
11575             item_data.deposit_amount,
11576             item_data.ref,
11577             item_data.holdable,
11578             item_data.price,
11579             item_data.barcode,
11580             item_data.circ_modifier,
11581             item_data.circ_as_type,
11582             item_data.alert_message,
11583             item_data.pub_note,
11584             item_data.priv_note,
11585             item_data.opac_visible
11586         );
11587     END LOOP;
11588
11589     RETURN NULL;
11590 END;
11591 $func$ LANGUAGE PLPGSQL;
11592
11593 CREATE OR REPLACE FUNCTION acq.create_acq_seq     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11594 BEGIN
11595     EXECUTE $$
11596         CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
11597     $$;
11598         RETURN TRUE;
11599 END;
11600 $creator$ LANGUAGE 'plpgsql';
11601
11602 CREATE OR REPLACE FUNCTION acq.create_acq_history ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11603 BEGIN
11604     EXECUTE $$
11605         CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
11606             audit_id    BIGINT                          PRIMARY KEY,
11607             audit_time  TIMESTAMP WITH TIME ZONE        NOT NULL,
11608             audit_action        TEXT                            NOT NULL,
11609             LIKE $$ || sch || $$.$$ || tbl || $$
11610         );
11611     $$;
11612         RETURN TRUE;
11613 END;
11614 $creator$ LANGUAGE 'plpgsql';
11615
11616 CREATE OR REPLACE FUNCTION acq.create_acq_func    ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11617 BEGIN
11618     EXECUTE $$
11619         CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
11620         RETURNS TRIGGER AS $func$
11621         BEGIN
11622             INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
11623                 SELECT  nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
11624                     now(),
11625                     SUBSTR(TG_OP,1,1),
11626                     OLD.*;
11627             RETURN NULL;
11628         END;
11629         $func$ LANGUAGE 'plpgsql';
11630     $$;
11631         RETURN TRUE;
11632 END;
11633 $creator$ LANGUAGE 'plpgsql';
11634
11635 CREATE OR REPLACE FUNCTION acq.create_acq_update_trigger ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11636 BEGIN
11637     EXECUTE $$
11638         CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
11639             AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
11640             EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
11641     $$;
11642         RETURN TRUE;
11643 END;
11644 $creator$ LANGUAGE 'plpgsql';
11645
11646 CREATE OR REPLACE FUNCTION acq.create_acq_lifecycle     ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11647 BEGIN
11648     EXECUTE $$
11649         CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
11650             SELECT      -1, now() as audit_time, '-' as audit_action, *
11651               FROM      $$ || sch || $$.$$ || tbl || $$
11652                 UNION ALL
11653             SELECT      *
11654               FROM      acq.$$ || sch || $$_$$ || tbl || $$_history;
11655     $$;
11656         RETURN TRUE;
11657 END;
11658 $creator$ LANGUAGE 'plpgsql';
11659
11660 -- The main event
11661
11662 CREATE OR REPLACE FUNCTION acq.create_acq_auditor ( sch TEXT, tbl TEXT ) RETURNS BOOL AS $creator$
11663 BEGIN
11664     PERFORM acq.create_acq_seq(sch, tbl);
11665     PERFORM acq.create_acq_history(sch, tbl);
11666     PERFORM acq.create_acq_func(sch, tbl);
11667     PERFORM acq.create_acq_update_trigger(sch, tbl);
11668     PERFORM acq.create_acq_lifecycle(sch, tbl);
11669     RETURN TRUE;
11670 END;
11671 $creator$ LANGUAGE 'plpgsql';
11672
11673 ALTER TABLE acq.lineitem DROP COLUMN item_count;
11674
11675 CREATE OR REPLACE VIEW acq.fund_debit_total AS
11676     SELECT  fund.id AS fund,
11677             fund_debit.encumbrance AS encumbrance,
11678             SUM( COALESCE( fund_debit.amount, 0 ) ) AS amount
11679       FROM acq.fund AS fund
11680                         LEFT JOIN acq.fund_debit AS fund_debit
11681                                 ON ( fund.id = fund_debit.fund )
11682       GROUP BY 1,2;
11683
11684 CREATE TABLE acq.debit_attribution (
11685         id                     INT         NOT NULL PRIMARY KEY,
11686         fund_debit             INT         NOT NULL
11687                                            REFERENCES acq.fund_debit
11688                                            DEFERRABLE INITIALLY DEFERRED,
11689     debit_amount           NUMERIC     NOT NULL,
11690         funding_source_credit  INT         REFERENCES acq.funding_source_credit
11691                                            DEFERRABLE INITIALLY DEFERRED,
11692     credit_amount          NUMERIC
11693 );
11694
11695 CREATE INDEX acq_attribution_debit_idx
11696         ON acq.debit_attribution( fund_debit );
11697
11698 CREATE INDEX acq_attribution_credit_idx
11699         ON acq.debit_attribution( funding_source_credit );
11700
11701 CREATE OR REPLACE FUNCTION acq.attribute_debits() RETURNS VOID AS $$
11702 /*
11703 Function to attribute expenditures and encumbrances to funding source credits,
11704 and thereby to funding sources.
11705
11706 Read the debits in chonological order, attributing each one to one or
11707 more funding source credits.  Constraints:
11708
11709 1. Don't attribute more to a credit than the amount of the credit.
11710
11711 2. For a given fund, don't attribute more to a funding source than the
11712 source has allocated to that fund.
11713
11714 3. Attribute debits to credits with deadlines before attributing them to
11715 credits without deadlines.  Otherwise attribute to the earliest credits
11716 first, based on the deadline date when present, or on the effective date
11717 when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
11718 This ordering is defined by an ORDER BY clause on the view
11719 acq.ordered_funding_source_credit.
11720
11721 Start by truncating the table acq.debit_attribution.  Then insert a row
11722 into that table for each attribution.  If a debit cannot be fully
11723 attributed, insert a row for the unattributable balance, with the 
11724 funding_source_credit and credit_amount columns NULL.
11725 */
11726 DECLARE
11727         curr_fund_source_bal RECORD;
11728         seqno                INT;     -- sequence num for credits applicable to a fund
11729         fund_credit          RECORD;  -- current row in temp t_fund_credit table
11730         fc                   RECORD;  -- used for loading t_fund_credit table
11731         sc                   RECORD;  -- used for loading t_fund_credit table
11732         --
11733         -- Used exclusively in the main loop:
11734         --
11735         deb                 RECORD;   -- current row from acq.fund_debit table
11736         curr_credit_bal     RECORD;   -- current row from temp t_credit table
11737         debit_balance       NUMERIC;  -- amount left to attribute for current debit
11738         conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
11739         attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
11740         conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
11741         conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
11742         conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
11743         attrib_count        INT;      -- populates id of acq.debit_attribution
11744 BEGIN
11745         --
11746         -- Load a temporary table.  For each combination of fund and funding source,
11747         -- load an entry with the total amount allocated to that fund by that source.
11748         -- This sum may reflect transfers as well as original allocations.  We will
11749         -- reduce this balance whenever we attribute debits to it.
11750         --
11751         CREATE TEMP TABLE t_fund_source_bal
11752         ON COMMIT DROP AS
11753                 SELECT
11754                         fund AS fund,
11755                         funding_source AS source,
11756                         sum( amount ) AS balance
11757                 FROM
11758                         acq.fund_allocation
11759                 GROUP BY
11760                         fund,
11761                         funding_source
11762                 HAVING
11763                         sum( amount ) > 0;
11764         --
11765         CREATE INDEX t_fund_source_bal_idx
11766                 ON t_fund_source_bal( fund, source );
11767         -------------------------------------------------------------------------------
11768         --
11769         -- Load another temporary table.  For each fund, load zero or more
11770         -- funding source credits from which that fund can get money.
11771         --
11772         CREATE TEMP TABLE t_fund_credit (
11773                 fund        INT,
11774                 seq         INT,
11775                 credit      INT
11776         ) ON COMMIT DROP;
11777         --
11778         FOR fc IN
11779                 SELECT DISTINCT fund
11780                 FROM acq.fund_allocation
11781                 ORDER BY fund
11782         LOOP                  -- Loop over the funds
11783                 seqno := 1;
11784                 FOR sc IN
11785                         SELECT
11786                                 ofsc.id
11787                         FROM
11788                                 acq.ordered_funding_source_credit AS ofsc
11789                         WHERE
11790                                 ofsc.funding_source IN
11791                                 (
11792                                         SELECT funding_source
11793                                         FROM acq.fund_allocation
11794                                         WHERE fund = fc.fund
11795                                 )
11796                 ORDER BY
11797                     ofsc.sort_priority,
11798                     ofsc.sort_date,
11799                     ofsc.id
11800                 LOOP                        -- Add each credit to the list
11801                         INSERT INTO t_fund_credit (
11802                                 fund,
11803                                 seq,
11804                                 credit
11805                         ) VALUES (
11806                                 fc.fund,
11807                                 seqno,
11808                                 sc.id
11809                         );
11810                         --RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
11811                         seqno := seqno + 1;
11812                 END LOOP;     -- Loop over credits for a given fund
11813         END LOOP;         -- Loop over funds
11814         --
11815         CREATE INDEX t_fund_credit_idx
11816                 ON t_fund_credit( fund, seq );
11817         -------------------------------------------------------------------------------
11818         --
11819         -- Load yet another temporary table.  This one is a list of funding source
11820         -- credits, with their balances.  We shall reduce those balances as we
11821         -- attribute debits to them.
11822         --
11823         CREATE TEMP TABLE t_credit
11824         ON COMMIT DROP AS
11825         SELECT
11826             fsc.id AS credit,
11827             fsc.funding_source AS source,
11828             fsc.amount AS balance,
11829             fs.currency_type AS currency_type
11830         FROM
11831             acq.funding_source_credit AS fsc,
11832             acq.funding_source fs
11833         WHERE
11834             fsc.funding_source = fs.id
11835                         AND fsc.amount > 0;
11836         --
11837         CREATE INDEX t_credit_idx
11838                 ON t_credit( credit );
11839         --
11840         -------------------------------------------------------------------------------
11841         --
11842         -- Now that we have loaded the lookup tables: loop through the debits,
11843         -- attributing each one to one or more funding source credits.
11844         -- 
11845         truncate table acq.debit_attribution;
11846         --
11847         attrib_count := 0;
11848         FOR deb in
11849                 SELECT
11850                         fd.id,
11851                         fd.fund,
11852                         fd.amount,
11853                         f.currency_type,
11854                         fd.encumbrance
11855                 FROM
11856                         acq.fund_debit fd,
11857                         acq.fund f
11858                 WHERE
11859                         fd.fund = f.id
11860                 ORDER BY
11861                         fd.id
11862         LOOP
11863                 --RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
11864                 --
11865                 debit_balance := deb.amount;
11866                 --
11867                 -- Loop over the funding source credits that are eligible
11868                 -- to pay for this debit
11869                 --
11870                 FOR fund_credit IN
11871                         SELECT
11872                                 credit
11873                         FROM
11874                                 t_fund_credit
11875                         WHERE
11876                                 fund = deb.fund
11877                         ORDER BY
11878                                 seq
11879                 LOOP
11880                         --RAISE NOTICE '   Examining credit %', fund_credit.credit;
11881                         --
11882                         -- Look up the balance for this credit.  If it's zero, then
11883                         -- it's not useful, so treat it as if you didn't find it.
11884                         -- (Actually there shouldn't be any zero balances in the table,
11885                         -- but we check just to make sure.)
11886                         --
11887                         SELECT *
11888                         INTO curr_credit_bal
11889                         FROM t_credit
11890                         WHERE
11891                                 credit = fund_credit.credit
11892                                 AND balance > 0;
11893                         --
11894                         IF curr_credit_bal IS NULL THEN
11895                                 --
11896                                 -- This credit is exhausted; try the next one.
11897                                 --
11898                                 CONTINUE;
11899                         END IF;
11900                         --
11901                         --
11902                         -- At this point we have an applicable credit with some money left.
11903                         -- Now see if the relevant funding_source has any money left.
11904                         --
11905                         -- Look up the balance of the allocation for this combination of
11906                         -- fund and source.  If you find such an entry, but it has a zero
11907                         -- balance, then it's not useful, so treat it as unfound.
11908                         -- (Actually there shouldn't be any zero balances in the table,
11909                         -- but we check just to make sure.)
11910                         --
11911                         SELECT *
11912                         INTO curr_fund_source_bal
11913                         FROM t_fund_source_bal
11914                         WHERE
11915                                 fund = deb.fund
11916                                 AND source = curr_credit_bal.source
11917                                 AND balance > 0;
11918                         --
11919                         IF curr_fund_source_bal IS NULL THEN
11920                                 --
11921                                 -- This fund/source doesn't exist or is already exhausted,
11922                                 -- so we can't use this credit.  Go on to the next one.
11923                                 --
11924                                 CONTINUE;
11925                         END IF;
11926                         --
11927                         -- Convert the available balances to the currency of the fund
11928                         --
11929                         conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
11930                                 curr_credit_bal.currency_type, deb.currency_type );
11931                         conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
11932                                 curr_credit_bal.currency_type, deb.currency_type );
11933                         --
11934                         -- Determine how much we can attribute to this credit: the minimum
11935                         -- of the debit amount, the fund/source balance, and the
11936                         -- credit balance
11937                         --
11938                         --RAISE NOTICE '   deb bal %', debit_balance;
11939                         --RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
11940                         --RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
11941                         --
11942                         conv_attr_amount := NULL;
11943                         attr_amount := debit_balance;
11944                         --
11945                         IF attr_amount > conv_alloc_balance THEN
11946                                 attr_amount := conv_alloc_balance;
11947                                 conv_attr_amount := curr_fund_source_bal.balance;
11948                         END IF;
11949                         IF attr_amount > conv_cred_balance THEN
11950                                 attr_amount := conv_cred_balance;
11951                                 conv_attr_amount := curr_credit_bal.balance;
11952                         END IF;
11953                         --
11954                         -- If we're attributing all of one of the balances, then that's how
11955                         -- much we will deduct from the balances, and we already captured
11956                         -- that amount above.  Otherwise we must convert the amount of the
11957                         -- attribution from the currency of the fund back to the currency of
11958                         -- the funding source.
11959                         --
11960                         IF conv_attr_amount IS NULL THEN
11961                                 conv_attr_amount := attr_amount * acq.exchange_ratio(
11962                                         deb.currency_type, curr_credit_bal.currency_type );
11963                         END IF;
11964                         --
11965                         -- Insert a row to record the attribution
11966                         --
11967                         attrib_count := attrib_count + 1;
11968                         INSERT INTO acq.debit_attribution (
11969                                 id,
11970                                 fund_debit,
11971                                 debit_amount,
11972                                 funding_source_credit,
11973                                 credit_amount
11974                         ) VALUES (
11975                                 attrib_count,
11976                                 deb.id,
11977                                 attr_amount,
11978                                 curr_credit_bal.credit,
11979                                 conv_attr_amount
11980                         );
11981                         --
11982                         -- Subtract the attributed amount from the various balances
11983                         --
11984                         debit_balance := debit_balance - attr_amount;
11985                         curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
11986                         --
11987                         IF curr_fund_source_bal.balance <= 0 THEN
11988                                 --
11989                                 -- This allocation is exhausted.  Delete it so
11990                                 -- that we don't waste time looking at it again.
11991                                 --
11992                                 DELETE FROM t_fund_source_bal
11993                                 WHERE
11994                                         fund = curr_fund_source_bal.fund
11995                                         AND source = curr_fund_source_bal.source;
11996                         ELSE
11997                                 UPDATE t_fund_source_bal
11998                                 SET balance = balance - conv_attr_amount
11999                                 WHERE
12000                                         fund = curr_fund_source_bal.fund
12001                                         AND source = curr_fund_source_bal.source;
12002                         END IF;
12003                         --
12004                         IF curr_credit_bal.balance <= 0 THEN
12005                                 --
12006                                 -- This funding source credit is exhausted.  Delete it
12007                                 -- so that we don't waste time looking at it again.
12008                                 --
12009                                 --DELETE FROM t_credit
12010                                 --WHERE
12011                                 --      credit = curr_credit_bal.credit;
12012                                 --
12013                                 DELETE FROM t_fund_credit
12014                                 WHERE
12015                                         credit = curr_credit_bal.credit;
12016                         ELSE
12017                                 UPDATE t_credit
12018                                 SET balance = curr_credit_bal.balance
12019                                 WHERE
12020                                         credit = curr_credit_bal.credit;
12021                         END IF;
12022                         --
12023                         -- Are we done with this debit yet?
12024                         --
12025                         IF debit_balance <= 0 THEN
12026                                 EXIT;       -- We've fully attributed this debit; stop looking at credits.
12027                         END IF;
12028                 END LOOP;       -- End loop over credits
12029                 --
12030                 IF debit_balance <> 0 THEN
12031                         --
12032                         -- We weren't able to attribute this debit, or at least not
12033                         -- all of it.  Insert a row for the unattributed balance.
12034                         --
12035                         attrib_count := attrib_count + 1;
12036                         INSERT INTO acq.debit_attribution (
12037                                 id,
12038                                 fund_debit,
12039                                 debit_amount,
12040                                 funding_source_credit,
12041                                 credit_amount
12042                         ) VALUES (
12043                                 attrib_count,
12044                                 deb.id,
12045                                 debit_balance,
12046                                 NULL,
12047                                 NULL
12048                         );
12049                 END IF;
12050         END LOOP;   -- End of loop over debits
12051 END;
12052 $$ LANGUAGE 'plpgsql';
12053
12054 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT, TEXT ) RETURNS TEXT AS $$
12055 DECLARE
12056     query TEXT;
12057     output TEXT;
12058 BEGIN
12059     query := $q$
12060         SELECT  regexp_replace(
12061                     oils_xpath_string(
12062                         $q$ || quote_literal($3) || $q$,
12063                         marc,
12064                         ' '
12065                     ),
12066                     $q$ || quote_literal($4) || $q$,
12067                     '',
12068                     'g')
12069           FROM  $q$ || $1 || $q$
12070           WHERE id = $q$ || $2;
12071
12072     EXECUTE query INTO output;
12073
12074     -- RAISE NOTICE 'query: %, output; %', query, output;
12075
12076     RETURN output;
12077 END;
12078 $$ LANGUAGE PLPGSQL IMMUTABLE;
12079
12080 CREATE OR REPLACE FUNCTION extract_marc_field ( TEXT, BIGINT, TEXT ) RETURNS TEXT AS $$
12081     SELECT extract_marc_field($1,$2,$3,'');
12082 $$ LANGUAGE SQL IMMUTABLE;
12083
12084 CREATE OR REPLACE FUNCTION asset.merge_record_assets( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
12085 DECLARE
12086     moved_objects INT := 0;
12087     source_cn     asset.call_number%ROWTYPE;
12088     target_cn     asset.call_number%ROWTYPE;
12089     metarec       metabib.metarecord%ROWTYPE;
12090     hold          action.hold_request%ROWTYPE;
12091     ser_rec       serial.record_entry%ROWTYPE;
12092     uri_count     INT := 0;
12093     counter       INT := 0;
12094     uri_datafield TEXT;
12095     uri_text      TEXT := '';
12096 BEGIN
12097
12098     -- move any 856 entries on records that have at least one MARC-mapped URI entry
12099     SELECT  INTO uri_count COUNT(*)
12100       FROM  asset.uri_call_number_map m
12101             JOIN asset.call_number cn ON (m.call_number = cn.id)
12102       WHERE cn.record = source_record;
12103
12104     IF uri_count > 0 THEN
12105
12106         SELECT  COUNT(*) INTO counter
12107           FROM  oils_xpath_table(
12108                     'id',
12109                     'marc',
12110                     'biblio.record_entry',
12111                     '//*[@tag="856"]',
12112                     'id=' || source_record
12113                 ) as t(i int,c text);
12114
12115         FOR i IN 1 .. counter LOOP
12116             SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' ||
12117                         ' tag="856"' || 
12118                         ' ind1="' || FIRST(ind1) || '"'  || 
12119                         ' ind2="' || FIRST(ind2) || '">' || 
12120                         array_to_string(
12121                             array_accum(
12122                                 '<subfield code="' || subfield || '">' ||
12123                                 regexp_replace(
12124                                     regexp_replace(
12125                                         regexp_replace(data,'&','&amp;','g'),
12126                                         '>', '&gt;', 'g'
12127                                     ),
12128                                     '<', '&lt;', 'g'
12129                                 ) || '</subfield>'
12130                             ), ''
12131                         ) || '</datafield>' INTO uri_datafield
12132               FROM  oils_xpath_table(
12133                         'id',
12134                         'marc',
12135                         'biblio.record_entry',
12136                         '//*[@tag="856"][position()=' || i || ']/@ind1|' || 
12137                         '//*[@tag="856"][position()=' || i || ']/@ind2|' || 
12138                         '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
12139                         '//*[@tag="856"][position()=' || i || ']/*[@code]',
12140                         'id=' || source_record
12141                     ) as t(id int,ind1 text, ind2 text,subfield text,data text);
12142
12143             uri_text := uri_text || uri_datafield;
12144         END LOOP;
12145
12146         IF uri_text <> '' THEN
12147             UPDATE  biblio.record_entry
12148               SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
12149               WHERE id = target_record;
12150         END IF;
12151
12152     END IF;
12153
12154     -- Find and move metarecords to the target record
12155     SELECT  INTO metarec *
12156       FROM  metabib.metarecord
12157       WHERE master_record = source_record;
12158
12159     IF FOUND THEN
12160         UPDATE  metabib.metarecord
12161           SET   master_record = target_record,
12162             mods = NULL
12163           WHERE id = metarec.id;
12164
12165         moved_objects := moved_objects + 1;
12166     END IF;
12167
12168     -- Find call numbers attached to the source ...
12169     FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP
12170
12171         SELECT  INTO target_cn *
12172           FROM  asset.call_number
12173           WHERE label = source_cn.label
12174             AND owning_lib = source_cn.owning_lib
12175             AND record = target_record;
12176
12177         -- ... and if there's a conflicting one on the target ...
12178         IF FOUND THEN
12179
12180             -- ... move the copies to that, and ...
12181             UPDATE  asset.copy
12182               SET   call_number = target_cn.id
12183               WHERE call_number = source_cn.id;
12184
12185             -- ... move V holds to the move-target call number
12186             FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
12187
12188                 UPDATE  action.hold_request
12189                   SET   target = target_cn.id
12190                   WHERE id = hold.id;
12191
12192                 moved_objects := moved_objects + 1;
12193             END LOOP;
12194
12195         -- ... if not ...
12196         ELSE
12197             -- ... just move the call number to the target record
12198             UPDATE  asset.call_number
12199               SET   record = target_record
12200               WHERE id = source_cn.id;
12201         END IF;
12202
12203         moved_objects := moved_objects + 1;
12204     END LOOP;
12205
12206     -- Find T holds targeting the source record ...
12207     FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP
12208
12209         -- ... and move them to the target record
12210         UPDATE  action.hold_request
12211           SET   target = target_record
12212           WHERE id = hold.id;
12213
12214         moved_objects := moved_objects + 1;
12215     END LOOP;
12216
12217     -- Find serial records targeting the source record ...
12218     FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
12219         -- ... and move them to the target record
12220         UPDATE  serial.record_entry
12221           SET   record = target_record
12222           WHERE id = ser_rec.id;
12223
12224         moved_objects := moved_objects + 1;
12225     END LOOP;
12226
12227     -- Finally, "delete" the source record
12228     DELETE FROM biblio.record_entry WHERE id = source_record;
12229
12230     -- That's all, folks!
12231     RETURN moved_objects;
12232 END;
12233 $func$ LANGUAGE plpgsql;
12234
12235 CREATE OR REPLACE FUNCTION acq.transfer_fund(
12236         old_fund   IN INT,
12237         old_amount IN NUMERIC,     -- in currency of old fund
12238         new_fund   IN INT,
12239         new_amount IN NUMERIC,     -- in currency of new fund
12240         user_id    IN INT,
12241         xfer_note  IN TEXT         -- to be recorded in acq.fund_transfer
12242         -- ,funding_source_in IN INT  -- if user wants to specify a funding source (see notes)
12243 ) RETURNS VOID AS $$
12244 /* -------------------------------------------------------------------------------
12245
12246 Function to transfer money from one fund to another.
12247
12248 A transfer is represented as a pair of entries in acq.fund_allocation, with a
12249 negative amount for the old (losing) fund and a positive amount for the new
12250 (gaining) fund.  In some cases there may be more than one such pair of entries
12251 in order to pull the money from different funding sources, or more specifically
12252 from different funding source credits.  For each such pair there is also an
12253 entry in acq.fund_transfer.
12254
12255 Since funding_source is a non-nullable column in acq.fund_allocation, we must
12256 choose a funding source for the transferred money to come from.  This choice
12257 must meet two constraints, so far as possible:
12258
12259 1. The amount transferred from a given funding source must not exceed the
12260 amount allocated to the old fund by the funding source.  To that end we
12261 compare the amount being transferred to the amount allocated.
12262
12263 2. We shouldn't transfer money that has already been spent or encumbered, as
12264 defined by the funding attribution process.  We attribute expenses to the
12265 oldest funding source credits first.  In order to avoid transferring that
12266 attributed money, we reverse the priority, transferring from the newest funding
12267 source credits first.  There can be no guarantee that this approach will
12268 avoid overcommitting a fund, but no other approach can do any better.
12269
12270 In this context the age of a funding source credit is defined by the
12271 deadline_date for credits with deadline_dates, and by the effective_date for
12272 credits without deadline_dates, with the proviso that credits with deadline_dates
12273 are all considered "older" than those without.
12274
12275 ----------
12276
12277 In the signature for this function, there is one last parameter commented out,
12278 named "funding_source_in".  Correspondingly, the WHERE clause for the query
12279 driving the main loop has an OR clause commented out, which references the
12280 funding_source_in parameter.
12281
12282 If these lines are uncommented, this function will allow the user optionally to
12283 restrict a fund transfer to a specified funding source.  If the source
12284 parameter is left NULL, then there will be no such restriction.
12285
12286 ------------------------------------------------------------------------------- */ 
12287 DECLARE
12288         same_currency      BOOLEAN;
12289         currency_ratio     NUMERIC;
12290         old_fund_currency  TEXT;
12291         old_remaining      NUMERIC;  -- in currency of old fund
12292         new_fund_currency  TEXT;
12293         new_fund_active    BOOLEAN;
12294         new_remaining      NUMERIC;  -- in currency of new fund
12295         curr_old_amt       NUMERIC;  -- in currency of old fund
12296         curr_new_amt       NUMERIC;  -- in currency of new fund
12297         source_addition    NUMERIC;  -- in currency of funding source
12298         source_deduction   NUMERIC;  -- in currency of funding source
12299         orig_allocated_amt NUMERIC;  -- in currency of funding source
12300         allocated_amt      NUMERIC;  -- in currency of fund
12301         source             RECORD;
12302 BEGIN
12303         --
12304         -- Sanity checks
12305         --
12306         IF old_fund IS NULL THEN
12307                 RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
12308         END IF;
12309         --
12310         IF old_amount IS NULL THEN
12311                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
12312         END IF;
12313         --
12314         -- The new fund and its amount must be both NULL or both not NULL.
12315         --
12316         IF new_fund IS NOT NULL AND new_amount IS NULL THEN
12317                 RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
12318         END IF;
12319         --
12320         IF new_fund IS NULL AND new_amount IS NOT NULL THEN
12321                 RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
12322         END IF;
12323         --
12324         IF user_id IS NULL THEN
12325                 RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
12326         END IF;
12327         --
12328         -- Initialize the amounts to be transferred, each denominated
12329         -- in the currency of its respective fund.  They will be
12330         -- reduced on each iteration of the loop.
12331         --
12332         old_remaining := old_amount;
12333         new_remaining := new_amount;
12334         --
12335         -- RAISE NOTICE 'Transferring % in fund % to % in fund %',
12336         --      old_amount, old_fund, new_amount, new_fund;
12337         --
12338         -- Get the currency types of the old and new funds.
12339         --
12340         SELECT
12341                 currency_type
12342         INTO
12343                 old_fund_currency
12344         FROM
12345                 acq.fund
12346         WHERE
12347                 id = old_fund;
12348         --
12349         IF old_fund_currency IS NULL THEN
12350                 RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
12351         END IF;
12352         --
12353         IF new_fund IS NOT NULL THEN
12354                 SELECT
12355                         currency_type,
12356                         active
12357                 INTO
12358                         new_fund_currency,
12359                         new_fund_active
12360                 FROM
12361                         acq.fund
12362                 WHERE
12363                         id = new_fund;
12364                 --
12365                 IF new_fund_currency IS NULL THEN
12366                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
12367                 ELSIF NOT new_fund_active THEN
12368                         --
12369                         -- No point in putting money into a fund from whence you can't spend it
12370                         --
12371                         RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
12372                 END IF;
12373                 --
12374                 IF new_amount = old_amount THEN
12375                         same_currency := true;
12376                         currency_ratio := 1;
12377                 ELSE
12378                         --
12379                         -- We'll have to translate currency between funds.  We presume that
12380                         -- the calling code has already applied an appropriate exchange rate,
12381                         -- so we'll apply the same conversion to each sub-transfer.
12382                         --
12383                         same_currency := false;
12384                         currency_ratio := new_amount / old_amount;
12385                 END IF;
12386         END IF;
12387         --
12388         -- Identify the funding source(s) from which we want to transfer the money.
12389         -- The principle is that we want to transfer the newest money first, because
12390         -- we spend the oldest money first.  The priority for spending is defined
12391         -- by a sort of the view acq.ordered_funding_source_credit.
12392         --
12393         FOR source in
12394                 SELECT
12395                         ofsc.id,
12396                         ofsc.funding_source,
12397                         ofsc.amount,
12398                         ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
12399                                 AS converted_amt,
12400                         fs.currency_type
12401                 FROM
12402                         acq.ordered_funding_source_credit AS ofsc,
12403                         acq.funding_source fs
12404                 WHERE
12405                         ofsc.funding_source = fs.id
12406                         and ofsc.funding_source IN
12407                         (
12408                                 SELECT funding_source
12409                                 FROM acq.fund_allocation
12410                                 WHERE fund = old_fund
12411                         )
12412                         -- and
12413                         -- (
12414                         --      ofsc.funding_source = funding_source_in
12415                         --      OR funding_source_in IS NULL
12416                         -- )
12417                 ORDER BY
12418                         ofsc.sort_priority desc,
12419                         ofsc.sort_date desc,
12420                         ofsc.id desc
12421         LOOP
12422                 --
12423                 -- Determine how much money the old fund got from this funding source,
12424                 -- denominated in the currency types of the source and of the fund.
12425                 -- This result may reflect transfers from previous iterations.
12426                 --
12427                 SELECT
12428                         COALESCE( sum( amount ), 0 ),
12429                         COALESCE( sum( amount )
12430                                 * acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
12431                 INTO
12432                         orig_allocated_amt,     -- in currency of the source
12433                         allocated_amt           -- in currency of the old fund
12434                 FROM
12435                         acq.fund_allocation
12436                 WHERE
12437                         fund = old_fund
12438                         and funding_source = source.funding_source;
12439                 --      
12440                 -- Determine how much to transfer from this credit, in the currency
12441                 -- of the fund.   Begin with the amount remaining to be attributed:
12442                 --
12443                 curr_old_amt := old_remaining;
12444                 --
12445                 -- Can't attribute more than was allocated from the fund:
12446                 --
12447                 IF curr_old_amt > allocated_amt THEN
12448                         curr_old_amt := allocated_amt;
12449                 END IF;
12450                 --
12451                 -- Can't attribute more than the amount of the current credit:
12452                 --
12453                 IF curr_old_amt > source.converted_amt THEN
12454                         curr_old_amt := source.converted_amt;
12455                 END IF;
12456                 --
12457                 curr_old_amt := trunc( curr_old_amt, 2 );
12458                 --
12459                 old_remaining := old_remaining - curr_old_amt;
12460                 --
12461                 -- Determine the amount to be deducted, if any,
12462                 -- from the old allocation.
12463                 --
12464                 IF old_remaining > 0 THEN
12465                         --
12466                         -- In this case we're using the whole allocation, so use that
12467                         -- amount directly instead of applying a currency translation
12468                         -- and thereby inviting round-off errors.
12469                         --
12470                         source_deduction := - orig_allocated_amt;
12471                 ELSE 
12472                         source_deduction := trunc(
12473                                 ( - curr_old_amt ) *
12474                                         acq.exchange_ratio( old_fund_currency, source.currency_type ),
12475                                 2 );
12476                 END IF;
12477                 --
12478                 IF source_deduction <> 0 THEN
12479                         --
12480                         -- Insert negative allocation for old fund in fund_allocation,
12481                         -- converted into the currency of the funding source
12482                         --
12483                         INSERT INTO acq.fund_allocation (
12484                                 funding_source,
12485                                 fund,
12486                                 amount,
12487                                 allocator,
12488                                 note
12489                         ) VALUES (
12490                                 source.funding_source,
12491                                 old_fund,
12492                                 source_deduction,
12493                                 user_id,
12494                                 'Transfer to fund ' || new_fund
12495                         );
12496                 END IF;
12497                 --
12498                 IF new_fund IS NOT NULL THEN
12499                         --
12500                         -- Determine how much to add to the new fund, in
12501                         -- its currency, and how much remains to be added:
12502                         --
12503                         IF same_currency THEN
12504                                 curr_new_amt := curr_old_amt;
12505                         ELSE
12506                                 IF old_remaining = 0 THEN
12507                                         --
12508                                         -- This is the last iteration, so nothing should be left
12509                                         --
12510                                         curr_new_amt := new_remaining;
12511                                         new_remaining := 0;
12512                                 ELSE
12513                                         curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
12514                                         new_remaining := new_remaining - curr_new_amt;
12515                                 END IF;
12516                         END IF;
12517                         --
12518                         -- Determine how much to add, if any,
12519                         -- to the new fund's allocation.
12520                         --
12521                         IF old_remaining > 0 THEN
12522                                 --
12523                                 -- In this case we're using the whole allocation, so use that amount
12524                                 -- amount directly instead of applying a currency translation and
12525                                 -- thereby inviting round-off errors.
12526                                 --
12527                                 source_addition := orig_allocated_amt;
12528                         ELSIF source.currency_type = old_fund_currency THEN
12529                                 --
12530                                 -- In this case we don't need a round trip currency translation,
12531                                 -- thereby inviting round-off errors:
12532                                 --
12533                                 source_addition := curr_old_amt;
12534                         ELSE 
12535                                 source_addition := trunc(
12536                                         curr_new_amt *
12537                                                 acq.exchange_ratio( new_fund_currency, source.currency_type ),
12538                                         2 );
12539                         END IF;
12540                         --
12541                         IF source_addition <> 0 THEN
12542                                 --
12543                                 -- Insert positive allocation for new fund in fund_allocation,
12544                                 -- converted to the currency of the founding source
12545                                 --
12546                                 INSERT INTO acq.fund_allocation (
12547                                         funding_source,
12548                                         fund,
12549                                         amount,
12550                                         allocator,
12551                                         note
12552                                 ) VALUES (
12553                                         source.funding_source,
12554                                         new_fund,
12555                                         source_addition,
12556                                         user_id,
12557                                         'Transfer from fund ' || old_fund
12558                                 );
12559                         END IF;
12560                 END IF;
12561                 --
12562                 IF trunc( curr_old_amt, 2 ) <> 0
12563                 OR trunc( curr_new_amt, 2 ) <> 0 THEN
12564                         --
12565                         -- Insert row in fund_transfer, using amounts in the currency of the funds
12566                         --
12567                         INSERT INTO acq.fund_transfer (
12568                                 src_fund,
12569                                 src_amount,
12570                                 dest_fund,
12571                                 dest_amount,
12572                                 transfer_user,
12573                                 note,
12574                                 funding_source_credit
12575                         ) VALUES (
12576                                 old_fund,
12577                                 trunc( curr_old_amt, 2 ),
12578                                 new_fund,
12579                                 trunc( curr_new_amt, 2 ),
12580                                 user_id,
12581                                 xfer_note,
12582                                 source.id
12583                         );
12584                 END IF;
12585                 --
12586                 if old_remaining <= 0 THEN
12587                         EXIT;                   -- Nothing more to be transferred
12588                 END IF;
12589         END LOOP;
12590 END;
12591 $$ LANGUAGE plpgsql;
12592
12593 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_unit(
12594         old_year INTEGER,
12595         user_id INTEGER,
12596         org_unit_id INTEGER
12597 ) RETURNS VOID AS $$
12598 DECLARE
12599 --
12600 new_id      INT;
12601 old_fund    RECORD;
12602 org_found   BOOLEAN;
12603 --
12604 BEGIN
12605         --
12606         -- Sanity checks
12607         --
12608         IF old_year IS NULL THEN
12609                 RAISE EXCEPTION 'Input year argument is NULL';
12610         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12611                 RAISE EXCEPTION 'Input year is out of range';
12612         END IF;
12613         --
12614         IF user_id IS NULL THEN
12615                 RAISE EXCEPTION 'Input user id argument is NULL';
12616         END IF;
12617         --
12618         IF org_unit_id IS NULL THEN
12619                 RAISE EXCEPTION 'Org unit id argument is NULL';
12620         ELSE
12621                 SELECT TRUE INTO org_found
12622                 FROM actor.org_unit
12623                 WHERE id = org_unit_id;
12624                 --
12625                 IF org_found IS NULL THEN
12626                         RAISE EXCEPTION 'Org unit id is invalid';
12627                 END IF;
12628         END IF;
12629         --
12630         -- Loop over the applicable funds
12631         --
12632         FOR old_fund in SELECT * FROM acq.fund
12633         WHERE
12634                 year = old_year
12635                 AND propagate
12636                 AND org = org_unit_id
12637         LOOP
12638                 BEGIN
12639                         INSERT INTO acq.fund (
12640                                 org,
12641                                 name,
12642                                 year,
12643                                 currency_type,
12644                                 code,
12645                                 rollover,
12646                                 propagate,
12647                                 balance_warning_percent,
12648                                 balance_stop_percent
12649                         ) VALUES (
12650                                 old_fund.org,
12651                                 old_fund.name,
12652                                 old_year + 1,
12653                                 old_fund.currency_type,
12654                                 old_fund.code,
12655                                 old_fund.rollover,
12656                                 true,
12657                                 old_fund.balance_warning_percent,
12658                                 old_fund.balance_stop_percent
12659                         )
12660                         RETURNING id INTO new_id;
12661                 EXCEPTION
12662                         WHEN unique_violation THEN
12663                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12664                                 CONTINUE;
12665                 END;
12666                 --RAISE NOTICE 'Propagating fund % to fund %',
12667                 --      old_fund.code, new_id;
12668         END LOOP;
12669 END;
12670 $$ LANGUAGE plpgsql;
12671
12672 CREATE OR REPLACE FUNCTION acq.propagate_funds_by_org_tree(
12673         old_year INTEGER,
12674         user_id INTEGER,
12675         org_unit_id INTEGER
12676 ) RETURNS VOID AS $$
12677 DECLARE
12678 --
12679 new_id      INT;
12680 old_fund    RECORD;
12681 org_found   BOOLEAN;
12682 --
12683 BEGIN
12684         --
12685         -- Sanity checks
12686         --
12687         IF old_year IS NULL THEN
12688                 RAISE EXCEPTION 'Input year argument is NULL';
12689         ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12690                 RAISE EXCEPTION 'Input year is out of range';
12691         END IF;
12692         --
12693         IF user_id IS NULL THEN
12694                 RAISE EXCEPTION 'Input user id argument is NULL';
12695         END IF;
12696         --
12697         IF org_unit_id IS NULL THEN
12698                 RAISE EXCEPTION 'Org unit id argument is NULL';
12699         ELSE
12700                 SELECT TRUE INTO org_found
12701                 FROM actor.org_unit
12702                 WHERE id = org_unit_id;
12703                 --
12704                 IF org_found IS NULL THEN
12705                         RAISE EXCEPTION 'Org unit id is invalid';
12706                 END IF;
12707         END IF;
12708         --
12709         -- Loop over the applicable funds
12710         --
12711         FOR old_fund in SELECT * FROM acq.fund
12712         WHERE
12713                 year = old_year
12714                 AND propagate
12715                 AND org in (
12716                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12717                 )
12718         LOOP
12719                 BEGIN
12720                         INSERT INTO acq.fund (
12721                                 org,
12722                                 name,
12723                                 year,
12724                                 currency_type,
12725                                 code,
12726                                 rollover,
12727                                 propagate,
12728                                 balance_warning_percent,
12729                                 balance_stop_percent
12730                         ) VALUES (
12731                                 old_fund.org,
12732                                 old_fund.name,
12733                                 old_year + 1,
12734                                 old_fund.currency_type,
12735                                 old_fund.code,
12736                                 old_fund.rollover,
12737                                 true,
12738                                 old_fund.balance_warning_percent,
12739                                 old_fund.balance_stop_percent
12740                         )
12741                         RETURNING id INTO new_id;
12742                 EXCEPTION
12743                         WHEN unique_violation THEN
12744                                 --RAISE NOTICE 'Fund % already propagated', old_fund.id;
12745                                 CONTINUE;
12746                 END;
12747                 --RAISE NOTICE 'Propagating fund % to fund %',
12748                 --      old_fund.code, new_id;
12749         END LOOP;
12750 END;
12751 $$ LANGUAGE plpgsql;
12752
12753 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_unit(
12754         old_year INTEGER,
12755         user_id INTEGER,
12756         org_unit_id INTEGER
12757 ) RETURNS VOID AS $$
12758 DECLARE
12759 --
12760 new_fund    INT;
12761 new_year    INT := old_year + 1;
12762 org_found   BOOL;
12763 xfer_amount NUMERIC;
12764 roll_fund   RECORD;
12765 deb         RECORD;
12766 detail      RECORD;
12767 --
12768 BEGIN
12769         --
12770         -- Sanity checks
12771         --
12772         IF old_year IS NULL THEN
12773                 RAISE EXCEPTION 'Input year argument is NULL';
12774     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12775         RAISE EXCEPTION 'Input year is out of range';
12776         END IF;
12777         --
12778         IF user_id IS NULL THEN
12779                 RAISE EXCEPTION 'Input user id argument is NULL';
12780         END IF;
12781         --
12782         IF org_unit_id IS NULL THEN
12783                 RAISE EXCEPTION 'Org unit id argument is NULL';
12784         ELSE
12785                 --
12786                 -- Validate the org unit
12787                 --
12788                 SELECT TRUE
12789                 INTO org_found
12790                 FROM actor.org_unit
12791                 WHERE id = org_unit_id;
12792                 --
12793                 IF org_found IS NULL THEN
12794                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12795                 END IF;
12796         END IF;
12797         --
12798         -- Loop over the propagable funds to identify the details
12799         -- from the old fund plus the id of the new one, if it exists.
12800         --
12801         FOR roll_fund in
12802         SELECT
12803             oldf.id AS old_fund,
12804             oldf.org,
12805             oldf.name,
12806             oldf.currency_type,
12807             oldf.code,
12808                 oldf.rollover,
12809             newf.id AS new_fund_id
12810         FROM
12811         acq.fund AS oldf
12812         LEFT JOIN acq.fund AS newf
12813                 ON ( oldf.code = newf.code )
12814         WHERE
12815                     oldf.org = org_unit_id
12816                 and oldf.year = old_year
12817                 and oldf.propagate
12818         and newf.year = new_year
12819         LOOP
12820                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
12821                 --
12822                 IF roll_fund.new_fund_id IS NULL THEN
12823                         --
12824                         -- The old fund hasn't been propagated yet.  Propagate it now.
12825                         --
12826                         INSERT INTO acq.fund (
12827                                 org,
12828                                 name,
12829                                 year,
12830                                 currency_type,
12831                                 code,
12832                                 rollover,
12833                                 propagate,
12834                                 balance_warning_percent,
12835                                 balance_stop_percent
12836                         ) VALUES (
12837                                 roll_fund.org,
12838                                 roll_fund.name,
12839                                 new_year,
12840                                 roll_fund.currency_type,
12841                                 roll_fund.code,
12842                                 true,
12843                                 true,
12844                                 roll_fund.balance_warning_percent,
12845                                 roll_fund.balance_stop_percent
12846                         )
12847                         RETURNING id INTO new_fund;
12848                 ELSE
12849                         new_fund = roll_fund.new_fund_id;
12850                 END IF;
12851                 --
12852                 -- Determine the amount to transfer
12853                 --
12854                 SELECT amount
12855                 INTO xfer_amount
12856                 FROM acq.fund_spent_balance
12857                 WHERE fund = roll_fund.old_fund;
12858                 --
12859                 IF xfer_amount <> 0 THEN
12860                         IF roll_fund.rollover THEN
12861                                 --
12862                                 -- Transfer balance from old fund to new
12863                                 --
12864                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
12865                                 --
12866                                 PERFORM acq.transfer_fund(
12867                                         roll_fund.old_fund,
12868                                         xfer_amount,
12869                                         new_fund,
12870                                         xfer_amount,
12871                                         user_id,
12872                                         'Rollover'
12873                                 );
12874                         ELSE
12875                                 --
12876                                 -- Transfer balance from old fund to the void
12877                                 --
12878                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
12879                                 --
12880                                 PERFORM acq.transfer_fund(
12881                                         roll_fund.old_fund,
12882                                         xfer_amount,
12883                                         NULL,
12884                                         NULL,
12885                                         user_id,
12886                                         'Rollover'
12887                                 );
12888                         END IF;
12889                 END IF;
12890                 --
12891                 IF roll_fund.rollover THEN
12892                         --
12893                         -- Move any lineitems from the old fund to the new one
12894                         -- where the associated debit is an encumbrance.
12895                         --
12896                         -- Any other tables tying expenditure details to funds should
12897                         -- receive similar treatment.  At this writing there are none.
12898                         --
12899                         UPDATE acq.lineitem_detail
12900                         SET fund = new_fund
12901                         WHERE
12902                         fund = roll_fund.old_fund -- this condition may be redundant
12903                         AND fund_debit in
12904                         (
12905                                 SELECT id
12906                                 FROM acq.fund_debit
12907                                 WHERE
12908                                 fund = roll_fund.old_fund
12909                                 AND encumbrance
12910                         );
12911                         --
12912                         -- Move encumbrance debits from the old fund to the new fund
12913                         --
12914                         UPDATE acq.fund_debit
12915                         SET fund = new_fund
12916                         wHERE
12917                                 fund = roll_fund.old_fund
12918                                 AND encumbrance;
12919                 END IF;
12920                 --
12921                 -- Mark old fund as inactive, now that we've closed it
12922                 --
12923                 UPDATE acq.fund
12924                 SET active = FALSE
12925                 WHERE id = roll_fund.old_fund;
12926         END LOOP;
12927 END;
12928 $$ LANGUAGE plpgsql;
12929
12930 CREATE OR REPLACE FUNCTION acq.rollover_funds_by_org_tree(
12931         old_year INTEGER,
12932         user_id INTEGER,
12933         org_unit_id INTEGER
12934 ) RETURNS VOID AS $$
12935 DECLARE
12936 --
12937 new_fund    INT;
12938 new_year    INT := old_year + 1;
12939 org_found   BOOL;
12940 xfer_amount NUMERIC;
12941 roll_fund   RECORD;
12942 deb         RECORD;
12943 detail      RECORD;
12944 --
12945 BEGIN
12946         --
12947         -- Sanity checks
12948         --
12949         IF old_year IS NULL THEN
12950                 RAISE EXCEPTION 'Input year argument is NULL';
12951     ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
12952         RAISE EXCEPTION 'Input year is out of range';
12953         END IF;
12954         --
12955         IF user_id IS NULL THEN
12956                 RAISE EXCEPTION 'Input user id argument is NULL';
12957         END IF;
12958         --
12959         IF org_unit_id IS NULL THEN
12960                 RAISE EXCEPTION 'Org unit id argument is NULL';
12961         ELSE
12962                 --
12963                 -- Validate the org unit
12964                 --
12965                 SELECT TRUE
12966                 INTO org_found
12967                 FROM actor.org_unit
12968                 WHERE id = org_unit_id;
12969                 --
12970                 IF org_found IS NULL THEN
12971                         RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
12972                 END IF;
12973         END IF;
12974         --
12975         -- Loop over the propagable funds to identify the details
12976         -- from the old fund plus the id of the new one, if it exists.
12977         --
12978         FOR roll_fund in
12979         SELECT
12980             oldf.id AS old_fund,
12981             oldf.org,
12982             oldf.name,
12983             oldf.currency_type,
12984             oldf.code,
12985                 oldf.rollover,
12986             newf.id AS new_fund_id
12987         FROM
12988         acq.fund AS oldf
12989         LEFT JOIN acq.fund AS newf
12990                 ON ( oldf.code = newf.code )
12991         WHERE
12992                     oldf.year = old_year
12993                 AND oldf.propagate
12994         AND newf.year = new_year
12995                 AND oldf.org in (
12996                         SELECT id FROM actor.org_unit_descendants( org_unit_id )
12997                 )
12998         LOOP
12999                 --RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
13000                 --
13001                 IF roll_fund.new_fund_id IS NULL THEN
13002                         --
13003                         -- The old fund hasn't been propagated yet.  Propagate it now.
13004                         --
13005                         INSERT INTO acq.fund (
13006                                 org,
13007                                 name,
13008                                 year,
13009                                 currency_type,
13010                                 code,
13011                                 rollover,
13012                                 propagate,
13013                                 balance_warning_percent,
13014                                 balance_stop_percent
13015                         ) VALUES (
13016                                 roll_fund.org,
13017                                 roll_fund.name,
13018                                 new_year,
13019                                 roll_fund.currency_type,
13020                                 roll_fund.code,
13021                                 true,
13022                                 true,
13023                                 roll_fund.balance_warning_percent,
13024                                 roll_fund.balance_stop_percent
13025                         )
13026                         RETURNING id INTO new_fund;
13027                 ELSE
13028                         new_fund = roll_fund.new_fund_id;
13029                 END IF;
13030                 --
13031                 -- Determine the amount to transfer
13032                 --
13033                 SELECT amount
13034                 INTO xfer_amount
13035                 FROM acq.fund_spent_balance
13036                 WHERE fund = roll_fund.old_fund;
13037                 --
13038                 IF xfer_amount <> 0 THEN
13039                         IF roll_fund.rollover THEN
13040                                 --
13041                                 -- Transfer balance from old fund to new
13042                                 --
13043                                 --RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
13044                                 --
13045                                 PERFORM acq.transfer_fund(
13046                                         roll_fund.old_fund,
13047                                         xfer_amount,
13048                                         new_fund,
13049                                         xfer_amount,
13050                                         user_id,
13051                                         'Rollover'
13052                                 );
13053                         ELSE
13054                                 --
13055                                 -- Transfer balance from old fund to the void
13056                                 --
13057                                 -- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
13058                                 --
13059                                 PERFORM acq.transfer_fund(
13060                                         roll_fund.old_fund,
13061                                         xfer_amount,
13062                                         NULL,
13063                                         NULL,
13064                                         user_id,
13065                                         'Rollover'
13066                                 );
13067                         END IF;
13068                 END IF;
13069                 --
13070                 IF roll_fund.rollover THEN
13071                         --
13072                         -- Move any lineitems from the old fund to the new one
13073                         -- where the associated debit is an encumbrance.
13074                         --
13075                         -- Any other tables tying expenditure details to funds should
13076                         -- receive similar treatment.  At this writing there are none.
13077                         --
13078                         UPDATE acq.lineitem_detail
13079                         SET fund = new_fund
13080                         WHERE
13081                         fund = roll_fund.old_fund -- this condition may be redundant
13082                         AND fund_debit in
13083                         (
13084                                 SELECT id
13085                                 FROM acq.fund_debit
13086                                 WHERE
13087                                 fund = roll_fund.old_fund
13088                                 AND encumbrance
13089                         );
13090                         --
13091                         -- Move encumbrance debits from the old fund to the new fund
13092                         --
13093                         UPDATE acq.fund_debit
13094                         SET fund = new_fund
13095                         wHERE
13096                                 fund = roll_fund.old_fund
13097                                 AND encumbrance;
13098                 END IF;
13099                 --
13100                 -- Mark old fund as inactive, now that we've closed it
13101                 --
13102                 UPDATE acq.fund
13103                 SET active = FALSE
13104                 WHERE id = roll_fund.old_fund;
13105         END LOOP;
13106 END;
13107 $$ LANGUAGE plpgsql;
13108
13109 CREATE OR REPLACE FUNCTION public.remove_commas( TEXT ) RETURNS TEXT AS $$
13110     SELECT regexp_replace($1, ',', '', 'g');
13111 $$ LANGUAGE SQL STRICT IMMUTABLE;
13112
13113 CREATE OR REPLACE FUNCTION public.remove_whitespace( TEXT ) RETURNS TEXT AS $$
13114     SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');
13115 $$ LANGUAGE SQL STRICT IMMUTABLE;
13116
13117 CREATE TABLE acq.distribution_formula_application (
13118     id BIGSERIAL PRIMARY KEY,
13119     creator INT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED,
13120     create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
13121     formula INT NOT NULL
13122         REFERENCES acq.distribution_formula(id) DEFERRABLE INITIALLY DEFERRED,
13123     lineitem INT NOT NULL
13124         REFERENCES acq.lineitem( id )
13125                 ON DELETE CASCADE
13126                 DEFERRABLE INITIALLY DEFERRED
13127 );
13128
13129 CREATE INDEX acqdfa_df_idx
13130     ON acq.distribution_formula_application(formula);
13131 CREATE INDEX acqdfa_li_idx
13132     ON acq.distribution_formula_application(lineitem);
13133 CREATE INDEX acqdfa_creator_idx
13134     ON acq.distribution_formula_application(creator);
13135
13136 CREATE TABLE acq.user_request_type (
13137     id      SERIAL  PRIMARY KEY,
13138     label   TEXT    NOT NULL UNIQUE -- i18n-ize
13139 );
13140
13141 INSERT INTO acq.user_request_type (id,label) VALUES (1, oils_i18n_gettext('1', 'Books', 'aurt', 'label'));
13142 INSERT INTO acq.user_request_type (id,label) VALUES (2, oils_i18n_gettext('2', 'Journal/Magazine & Newspaper Articles', 'aurt', 'label'));
13143 INSERT INTO acq.user_request_type (id,label) VALUES (3, oils_i18n_gettext('3', 'Audiobooks', 'aurt', 'label'));
13144 INSERT INTO acq.user_request_type (id,label) VALUES (4, oils_i18n_gettext('4', 'Music', 'aurt', 'label'));
13145 INSERT INTO acq.user_request_type (id,label) VALUES (5, oils_i18n_gettext('5', 'DVDs', 'aurt', 'label'));
13146
13147 SELECT SETVAL('acq.user_request_type_id_seq'::TEXT, 6);
13148
13149 CREATE TABLE acq.cancel_reason (
13150         id            SERIAL            PRIMARY KEY,
13151         org_unit      INTEGER           NOT NULL REFERENCES actor.org_unit( id )
13152                                         DEFERRABLE INITIALLY DEFERRED,
13153         label         TEXT              NOT NULL,
13154         description   TEXT              NOT NULL,
13155         keep_debits   BOOL              NOT NULL DEFAULT FALSE,
13156         CONSTRAINT acq_cancel_reason_one_per_org_unit UNIQUE( org_unit, label )
13157 );
13158
13159 -- Reserve ids 1-999 for stock reasons
13160 -- Reserve ids 1000-1999 for EDI reasons
13161 -- 2000+ are available for staff to create
13162
13163 SELECT SETVAL('acq.cancel_reason_id_seq'::TEXT, 2000);
13164
13165 CREATE TABLE acq.user_request (
13166     id                  SERIAL  PRIMARY KEY,
13167     usr                 INT     NOT NULL REFERENCES actor.usr (id), -- requesting user
13168     hold                BOOL    NOT NULL DEFAULT TRUE,
13169
13170     pickup_lib          INT     NOT NULL REFERENCES actor.org_unit (id), -- pickup lib
13171     holdable_formats    TEXT,           -- nullable, for use in hold creation
13172     phone_notify        TEXT,
13173     email_notify        BOOL    NOT NULL DEFAULT TRUE,
13174     lineitem            INT     REFERENCES acq.lineitem (id) ON DELETE CASCADE,
13175     eg_bib              BIGINT  REFERENCES biblio.record_entry (id) ON DELETE CASCADE,
13176     request_date        TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when they requested it
13177     need_before         TIMESTAMPTZ,    -- don't create holds after this
13178     max_fee             TEXT,
13179
13180     request_type        INT     NOT NULL REFERENCES acq.user_request_type (id), 
13181     isxn                TEXT,
13182     title               TEXT,
13183     volume              TEXT,
13184     author              TEXT,
13185     article_title       TEXT,
13186     article_pages       TEXT,
13187     publisher           TEXT,
13188     location            TEXT,
13189     pubdate             TEXT,
13190     mentioned           TEXT,
13191     other_info          TEXT,
13192         cancel_reason       INT              REFERENCES acq.cancel_reason( id )
13193                                              DEFERRABLE INITIALLY DEFERRED
13194 );
13195
13196 CREATE TABLE acq.lineitem_alert_text (
13197         id               SERIAL         PRIMARY KEY,
13198         code             TEXT           NOT NULL,
13199         description      TEXT,
13200         owning_lib       INT            NOT NULL
13201                                         REFERENCES actor.org_unit(id)
13202                                         DEFERRABLE INITIALLY DEFERRED,
13203         CONSTRAINT alert_one_code_per_org UNIQUE (code, owning_lib)
13204 );
13205
13206 ALTER TABLE acq.lineitem_note
13207         ADD COLUMN alert_text    INT     REFERENCES acq.lineitem_alert_text(id)
13208                                          DEFERRABLE INITIALLY DEFERRED;
13209
13210 -- add ON DELETE CASCADE clause
13211
13212 ALTER TABLE acq.lineitem_note
13213         DROP CONSTRAINT lineitem_note_lineitem_fkey;
13214
13215 ALTER TABLE acq.lineitem_note
13216         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
13217                 ON DELETE CASCADE
13218                 DEFERRABLE INITIALLY DEFERRED;
13219
13220 ALTER TABLE acq.lineitem_note
13221         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
13222
13223 CREATE TABLE acq.invoice_method (
13224     code    TEXT    PRIMARY KEY,
13225     name    TEXT    NOT NULL -- i18n-ize
13226 );
13227 INSERT INTO acq.invoice_method (code,name) VALUES ('EDI',oils_i18n_gettext('EDI', 'EDI', 'acqim', 'name'));
13228 INSERT INTO acq.invoice_method (code,name) VALUES ('PPR',oils_i18n_gettext('PPR', 'Paper', 'acqit', 'name'));
13229
13230 CREATE TABLE acq.invoice_payment_method (
13231         code      TEXT     PRIMARY KEY,
13232         name      TEXT     NOT NULL
13233 );
13234
13235 CREATE TABLE acq.invoice (
13236     id             SERIAL      PRIMARY KEY,
13237     receiver       INT         NOT NULL REFERENCES actor.org_unit (id),
13238     provider       INT         NOT NULL REFERENCES acq.provider (id),
13239     shipper        INT         NOT NULL REFERENCES acq.provider (id),
13240     recv_date      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
13241     recv_method    TEXT        NOT NULL REFERENCES acq.invoice_method (code) DEFAULT 'EDI',
13242     inv_type       TEXT,       -- A "type" field is desired, but no idea what goes here
13243     inv_ident      TEXT        NOT NULL, -- vendor-supplied invoice id/number
13244         payment_auth   TEXT,
13245         payment_method TEXT        REFERENCES acq.invoice_payment_method (code)
13246                                    DEFERRABLE INITIALLY DEFERRED,
13247         note           TEXT,
13248     complete       BOOL        NOT NULL DEFAULT FALSE,
13249     CONSTRAINT inv_ident_once_per_provider UNIQUE(provider, inv_ident)
13250 );
13251
13252 CREATE TABLE acq.invoice_entry (
13253     id              SERIAL      PRIMARY KEY,
13254     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON DELETE CASCADE,
13255     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13256     lineitem        INT         REFERENCES acq.lineitem (id) ON UPDATE CASCADE ON DELETE SET NULL,
13257     inv_item_count  INT         NOT NULL, -- How many acqlids did they say they sent
13258     phys_item_count INT, -- and how many did staff count
13259     note            TEXT,
13260     billed_per_item BOOL,
13261     cost_billed     NUMERIC(8,2),
13262     actual_cost     NUMERIC(8,2),
13263         amount_paid     NUMERIC (8,2)
13264 );
13265
13266 CREATE TABLE acq.invoice_item_type (
13267     code    TEXT    PRIMARY KEY,
13268     name    TEXT    NOT NULL, -- i18n-ize
13269         prorate BOOL    NOT NULL DEFAULT FALSE
13270 );
13271
13272 INSERT INTO acq.invoice_item_type (code,name) VALUES ('TAX',oils_i18n_gettext('TAX', 'Tax', 'aiit', 'name'));
13273 INSERT INTO acq.invoice_item_type (code,name) VALUES ('PRO',oils_i18n_gettext('PRO', 'Processing Fee', 'aiit', 'name'));
13274 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SHP',oils_i18n_gettext('SHP', 'Shipping Charge', 'aiit', 'name'));
13275 INSERT INTO acq.invoice_item_type (code,name) VALUES ('HND',oils_i18n_gettext('HND', 'Handling Charge', 'aiit', 'name'));
13276 INSERT INTO acq.invoice_item_type (code,name) VALUES ('ITM',oils_i18n_gettext('ITM', 'Non-library Item', 'aiit', 'name'));
13277 INSERT INTO acq.invoice_item_type (code,name) VALUES ('SUB',oils_i18n_gettext('SUB', 'Serial Subscription', 'aiit', 'name'));
13278
13279 CREATE TABLE acq.po_item (
13280         id              SERIAL      PRIMARY KEY,
13281         purchase_order  INT         REFERENCES acq.purchase_order (id)
13282                                     ON UPDATE CASCADE ON DELETE SET NULL
13283                                     DEFERRABLE INITIALLY DEFERRED,
13284         fund_debit      INT         REFERENCES acq.fund_debit (id)
13285                                     DEFERRABLE INITIALLY DEFERRED,
13286         inv_item_type   TEXT        NOT NULL
13287                                     REFERENCES acq.invoice_item_type (code)
13288                                     DEFERRABLE INITIALLY DEFERRED,
13289         title           TEXT,
13290         author          TEXT,
13291         note            TEXT,
13292         estimated_cost  NUMERIC(8,2),
13293         fund            INT         REFERENCES acq.fund (id)
13294                                     DEFERRABLE INITIALLY DEFERRED,
13295         target          BIGINT
13296 );
13297
13298 CREATE TABLE acq.invoice_item ( -- for invoice-only debits: taxes/fees/non-bib items/etc
13299     id              SERIAL      PRIMARY KEY,
13300     invoice         INT         NOT NULL REFERENCES acq.invoice (id) ON UPDATE CASCADE ON DELETE CASCADE,
13301     purchase_order  INT         REFERENCES acq.purchase_order (id) ON UPDATE CASCADE ON DELETE SET NULL,
13302     fund_debit      INT         REFERENCES acq.fund_debit (id),
13303     inv_item_type   TEXT        NOT NULL REFERENCES acq.invoice_item_type (code),
13304     title           TEXT,
13305     author          TEXT,
13306     note            TEXT,
13307     cost_billed     NUMERIC(8,2),
13308     actual_cost     NUMERIC(8,2),
13309     fund            INT         REFERENCES acq.fund (id)
13310                                 DEFERRABLE INITIALLY DEFERRED,
13311     amount_paid     NUMERIC (8,2),
13312     po_item         INT         REFERENCES acq.po_item (id)
13313                                 DEFERRABLE INITIALLY DEFERRED,
13314     target          BIGINT
13315 );
13316
13317 CREATE TABLE acq.edi_message (
13318     id               SERIAL          PRIMARY KEY,
13319     account          INTEGER         REFERENCES acq.edi_account(id)
13320                                      DEFERRABLE INITIALLY DEFERRED,
13321     remote_file      TEXT,
13322     create_time      TIMESTAMPTZ     NOT NULL DEFAULT now(),
13323     translate_time   TIMESTAMPTZ,
13324     process_time     TIMESTAMPTZ,
13325     error_time       TIMESTAMPTZ,
13326     status           TEXT            NOT NULL DEFAULT 'new'
13327                                      CONSTRAINT status_value CHECK
13328                                      ( status IN (
13329                                         'new',          -- needs to be translated
13330                                         'translated',   -- needs to be processed
13331                                         'trans_error',  -- error in translation step
13332                                         'processed',    -- needs to have remote_file deleted
13333                                         'proc_error',   -- error in processing step
13334                                         'delete_error', -- error in deletion
13335                                         'retry',        -- need to retry
13336                                         'complete'      -- done
13337                                      )),
13338     edi              TEXT,
13339     jedi             TEXT,
13340     error            TEXT,
13341     purchase_order   INT             REFERENCES acq.purchase_order
13342                                      DEFERRABLE INITIALLY DEFERRED,
13343     message_type     TEXT            NOT NULL CONSTRAINT valid_message_type
13344                                      CHECK ( message_type IN (
13345                                         'ORDERS',
13346                                         'ORDRSP',
13347                                         'INVOIC',
13348                                         'OSTENQ',
13349                                         'OSTRPT'
13350                                      ))
13351 );
13352
13353 ALTER TABLE actor.org_address ADD COLUMN san TEXT;
13354
13355 ALTER TABLE acq.provider_address
13356         ADD COLUMN fax_phone TEXT;
13357
13358 ALTER TABLE acq.provider_contact_address
13359         ADD COLUMN fax_phone TEXT;
13360
13361 CREATE TABLE acq.provider_note (
13362     id      SERIAL              PRIMARY KEY,
13363     provider    INT             NOT NULL REFERENCES acq.provider (id) DEFERRABLE INITIALLY DEFERRED,
13364     creator     INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13365     editor      INT             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
13366     create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13367     edit_time   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
13368     value       TEXT            NOT NULL
13369 );
13370 CREATE INDEX acq_pro_note_pro_idx      ON acq.provider_note ( provider );
13371 CREATE INDEX acq_pro_note_creator_idx  ON acq.provider_note ( creator );
13372 CREATE INDEX acq_pro_note_editor_idx   ON acq.provider_note ( editor );
13373
13374 -- For each fund: the total allocation from all sources, in the
13375 -- currency of the fund (or 0 if there are no allocations)
13376
13377 CREATE VIEW acq.all_fund_allocation_total AS
13378 SELECT
13379     f.id AS fund,
13380     COALESCE( SUM( a.amount * acq.exchange_ratio(
13381         s.currency_type, f.currency_type))::numeric(100,2), 0 )
13382     AS amount
13383 FROM
13384     acq.fund f
13385         LEFT JOIN acq.fund_allocation a
13386             ON a.fund = f.id
13387         LEFT JOIN acq.funding_source s
13388             ON a.funding_source = s.id
13389 GROUP BY
13390     f.id;
13391
13392 -- For every fund: the total encumbrances (or 0 if none),
13393 -- in the currency of the fund.
13394
13395 CREATE VIEW acq.all_fund_encumbrance_total AS
13396 SELECT
13397         f.id AS fund,
13398         COALESCE( encumb.amount, 0 ) AS amount
13399 FROM
13400         acq.fund AS f
13401                 LEFT JOIN (
13402                         SELECT
13403                                 fund,
13404                                 sum( amount ) AS amount
13405                         FROM
13406                                 acq.fund_debit
13407                         WHERE
13408                                 encumbrance
13409                         GROUP BY fund
13410                 ) AS encumb
13411                         ON f.id = encumb.fund;
13412
13413 -- For every fund: the total spent (or 0 if none),
13414 -- in the currency of the fund.
13415
13416 CREATE VIEW acq.all_fund_spent_total AS
13417 SELECT
13418     f.id AS fund,
13419     COALESCE( spent.amount, 0 ) AS amount
13420 FROM
13421     acq.fund AS f
13422         LEFT JOIN (
13423             SELECT
13424                 fund,
13425                 sum( amount ) AS amount
13426             FROM
13427                 acq.fund_debit
13428             WHERE
13429                 NOT encumbrance
13430             GROUP BY fund
13431         ) AS spent
13432             ON f.id = spent.fund;
13433
13434 -- For each fund: the amount not yet spent, in the currency
13435 -- of the fund.  May include encumbrances.
13436
13437 CREATE VIEW acq.all_fund_spent_balance AS
13438 SELECT
13439         c.fund,
13440         c.amount - d.amount AS amount
13441 FROM acq.all_fund_allocation_total c
13442     LEFT JOIN acq.all_fund_spent_total d USING (fund);
13443
13444 -- For each fund: the amount neither spent nor encumbered,
13445 -- in the currency of the fund
13446
13447 CREATE VIEW acq.all_fund_combined_balance AS
13448 SELECT
13449      a.fund,
13450      a.amount - COALESCE( c.amount, 0 ) AS amount
13451 FROM
13452      acq.all_fund_allocation_total a
13453         LEFT OUTER JOIN (
13454             SELECT
13455                 fund,
13456                 SUM( amount ) AS amount
13457             FROM
13458                 acq.fund_debit
13459             GROUP BY
13460                 fund
13461         ) AS c USING ( fund );
13462
13463 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 $$
13464 DECLARE
13465         suffix TEXT;
13466         bucket_row RECORD;
13467         picklist_row RECORD;
13468         queue_row RECORD;
13469         folder_row RECORD;
13470 BEGIN
13471
13472     -- do some initial cleanup 
13473     UPDATE actor.usr SET card = NULL WHERE id = src_usr;
13474     UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
13475     UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;
13476
13477     -- actor.*
13478     IF del_cards THEN
13479         DELETE FROM actor.card where usr = src_usr;
13480     ELSE
13481         IF deactivate_cards THEN
13482             UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
13483         END IF;
13484         UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
13485     END IF;
13486
13487
13488     IF del_addrs THEN
13489         DELETE FROM actor.usr_address WHERE usr = src_usr;
13490     ELSE
13491         UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
13492     END IF;
13493
13494     UPDATE actor.usr_note SET usr = dest_usr WHERE usr = src_usr;
13495     -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
13496     UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
13497     PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
13498     PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);
13499
13500     -- permission.*
13501     PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
13502     PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
13503     PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
13504     PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);
13505
13506
13507     -- container.*
13508         
13509         -- For each *_bucket table: transfer every bucket belonging to src_usr
13510         -- into the custody of dest_usr.
13511         --
13512         -- In order to avoid colliding with an existing bucket owned by
13513         -- the destination user, append the source user's id (in parenthesese)
13514         -- to the name.  If you still get a collision, add successive
13515         -- spaces to the name and keep trying until you succeed.
13516         --
13517         FOR bucket_row in
13518                 SELECT id, name
13519                 FROM   container.biblio_record_entry_bucket
13520                 WHERE  owner = src_usr
13521         LOOP
13522                 suffix := ' (' || src_usr || ')';
13523                 LOOP
13524                         BEGIN
13525                                 UPDATE  container.biblio_record_entry_bucket
13526                                 SET     owner = dest_usr, name = name || suffix
13527                                 WHERE   id = bucket_row.id;
13528                         EXCEPTION WHEN unique_violation THEN
13529                                 suffix := suffix || ' ';
13530                                 CONTINUE;
13531                         END;
13532                         EXIT;
13533                 END LOOP;
13534         END LOOP;
13535
13536         FOR bucket_row in
13537                 SELECT id, name
13538                 FROM   container.call_number_bucket
13539                 WHERE  owner = src_usr
13540         LOOP
13541                 suffix := ' (' || src_usr || ')';
13542                 LOOP
13543                         BEGIN
13544                                 UPDATE  container.call_number_bucket
13545                                 SET     owner = dest_usr, name = name || suffix
13546                                 WHERE   id = bucket_row.id;
13547                         EXCEPTION WHEN unique_violation THEN
13548                                 suffix := suffix || ' ';
13549                                 CONTINUE;
13550                         END;
13551                         EXIT;
13552                 END LOOP;
13553         END LOOP;
13554
13555         FOR bucket_row in
13556                 SELECT id, name
13557                 FROM   container.copy_bucket
13558                 WHERE  owner = src_usr
13559         LOOP
13560                 suffix := ' (' || src_usr || ')';
13561                 LOOP
13562                         BEGIN
13563                                 UPDATE  container.copy_bucket
13564                                 SET     owner = dest_usr, name = name || suffix
13565                                 WHERE   id = bucket_row.id;
13566                         EXCEPTION WHEN unique_violation THEN
13567                                 suffix := suffix || ' ';
13568                                 CONTINUE;
13569                         END;
13570                         EXIT;
13571                 END LOOP;
13572         END LOOP;
13573
13574         FOR bucket_row in
13575                 SELECT id, name
13576                 FROM   container.user_bucket
13577                 WHERE  owner = src_usr
13578         LOOP
13579                 suffix := ' (' || src_usr || ')';
13580                 LOOP
13581                         BEGIN
13582                                 UPDATE  container.user_bucket
13583                                 SET     owner = dest_usr, name = name || suffix
13584                                 WHERE   id = bucket_row.id;
13585                         EXCEPTION WHEN unique_violation THEN
13586                                 suffix := suffix || ' ';
13587                                 CONTINUE;
13588                         END;
13589                         EXIT;
13590                 END LOOP;
13591         END LOOP;
13592
13593         UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;
13594
13595     -- vandelay.*
13596         -- transfer queues the same way we transfer buckets (see above)
13597         FOR queue_row in
13598                 SELECT id, name
13599                 FROM   vandelay.queue
13600                 WHERE  owner = src_usr
13601         LOOP
13602                 suffix := ' (' || src_usr || ')';
13603                 LOOP
13604                         BEGIN
13605                                 UPDATE  vandelay.queue
13606                                 SET     owner = dest_usr, name = name || suffix
13607                                 WHERE   id = queue_row.id;
13608                         EXCEPTION WHEN unique_violation THEN
13609                                 suffix := suffix || ' ';
13610                                 CONTINUE;
13611                         END;
13612                         EXIT;
13613                 END LOOP;
13614         END LOOP;
13615
13616     -- money.*
13617     PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
13618     PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
13619     UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
13620     UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
13621     UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;
13622
13623     -- action.*
13624     UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
13625     UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
13626     UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
13627
13628     UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
13629     UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
13630     UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
13631     UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
13632
13633     UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
13634     UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
13635     UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
13636     UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
13637     UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;
13638
13639     -- acq.*
13640     UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
13641         UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
13642
13643         -- transfer picklists the same way we transfer buckets (see above)
13644         FOR picklist_row in
13645                 SELECT id, name
13646                 FROM   acq.picklist
13647                 WHERE  owner = src_usr
13648         LOOP
13649                 suffix := ' (' || src_usr || ')';
13650                 LOOP
13651                         BEGIN
13652                                 UPDATE  acq.picklist
13653                                 SET     owner = dest_usr, name = name || suffix
13654                                 WHERE   id = picklist_row.id;
13655                         EXCEPTION WHEN unique_violation THEN
13656                                 suffix := suffix || ' ';
13657                                 CONTINUE;
13658                         END;
13659                         EXIT;
13660                 END LOOP;
13661         END LOOP;
13662
13663     UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
13664     UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
13665     UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
13666     UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
13667     UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
13668     UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
13669     UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
13670     UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;
13671
13672     -- asset.*
13673     UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
13674     UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
13675     UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
13676     UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
13677     UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
13678     UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
13679
13680     -- serial.*
13681     UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
13682     UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;
13683
13684     -- reporter.*
13685     -- It's not uncommon to define the reporter schema in a replica 
13686     -- DB only, so don't assume these tables exist in the write DB.
13687     BEGIN
13688         UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
13689     EXCEPTION WHEN undefined_table THEN
13690         -- do nothing
13691     END;
13692     BEGIN
13693         UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
13694     EXCEPTION WHEN undefined_table THEN
13695         -- do nothing
13696     END;
13697     BEGIN
13698         UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
13699     EXCEPTION WHEN undefined_table THEN
13700         -- do nothing
13701     END;
13702     BEGIN
13703                 -- transfer folders the same way we transfer buckets (see above)
13704                 FOR folder_row in
13705                         SELECT id, name
13706                         FROM   reporter.template_folder
13707                         WHERE  owner = src_usr
13708                 LOOP
13709                         suffix := ' (' || src_usr || ')';
13710                         LOOP
13711                                 BEGIN
13712                                         UPDATE  reporter.template_folder
13713                                         SET     owner = dest_usr, name = name || suffix
13714                                         WHERE   id = folder_row.id;
13715                                 EXCEPTION WHEN unique_violation THEN
13716                                         suffix := suffix || ' ';
13717                                         CONTINUE;
13718                                 END;
13719                                 EXIT;
13720                         END LOOP;
13721                 END LOOP;
13722     EXCEPTION WHEN undefined_table THEN
13723         -- do nothing
13724     END;
13725     BEGIN
13726                 -- transfer folders the same way we transfer buckets (see above)
13727                 FOR folder_row in
13728                         SELECT id, name
13729                         FROM   reporter.report_folder
13730                         WHERE  owner = src_usr
13731                 LOOP
13732                         suffix := ' (' || src_usr || ')';
13733                         LOOP
13734                                 BEGIN
13735                                         UPDATE  reporter.report_folder
13736                                         SET     owner = dest_usr, name = name || suffix
13737                                         WHERE   id = folder_row.id;
13738                                 EXCEPTION WHEN unique_violation THEN
13739                                         suffix := suffix || ' ';
13740                                         CONTINUE;
13741                                 END;
13742                                 EXIT;
13743                         END LOOP;
13744                 END LOOP;
13745     EXCEPTION WHEN undefined_table THEN
13746         -- do nothing
13747     END;
13748     BEGIN
13749                 -- transfer folders the same way we transfer buckets (see above)
13750                 FOR folder_row in
13751                         SELECT id, name
13752                         FROM   reporter.output_folder
13753                         WHERE  owner = src_usr
13754                 LOOP
13755                         suffix := ' (' || src_usr || ')';
13756                         LOOP
13757                                 BEGIN
13758                                         UPDATE  reporter.output_folder
13759                                         SET     owner = dest_usr, name = name || suffix
13760                                         WHERE   id = folder_row.id;
13761                                 EXCEPTION WHEN unique_violation THEN
13762                                         suffix := suffix || ' ';
13763                                         CONTINUE;
13764                                 END;
13765                                 EXIT;
13766                         END LOOP;
13767                 END LOOP;
13768     EXCEPTION WHEN undefined_table THEN
13769         -- do nothing
13770     END;
13771
13772     -- Finally, delete the source user
13773     DELETE FROM actor.usr WHERE id = src_usr;
13774
13775 END;
13776 $$ LANGUAGE plpgsql;
13777
13778 -- The "add" trigger functions should protect against existing NULLed values, just in case
13779 CREATE OR REPLACE FUNCTION money.materialized_summary_billing_add () RETURNS TRIGGER AS $$
13780 BEGIN
13781     IF NOT NEW.voided THEN
13782         UPDATE  money.materialized_billable_xact_summary
13783           SET   total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
13784             last_billing_ts = NEW.billing_ts,
13785             last_billing_note = NEW.note,
13786             last_billing_type = NEW.billing_type,
13787             balance_owed = balance_owed + NEW.amount
13788           WHERE id = NEW.xact;
13789     END IF;
13790
13791     RETURN NEW;
13792 END;
13793 $$ LANGUAGE PLPGSQL;
13794
13795 CREATE OR REPLACE FUNCTION money.materialized_summary_payment_add () RETURNS TRIGGER AS $$
13796 BEGIN
13797     IF NOT NEW.voided THEN
13798         UPDATE  money.materialized_billable_xact_summary
13799           SET   total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
13800             last_payment_ts = NEW.payment_ts,
13801             last_payment_note = NEW.note,
13802             last_payment_type = TG_ARGV[0],
13803             balance_owed = balance_owed - NEW.amount
13804           WHERE id = NEW.xact;
13805     END IF;
13806
13807     RETURN NEW;
13808 END;
13809 $$ LANGUAGE PLPGSQL;
13810
13811 -- Refresh the mat view with the corrected underlying view
13812 TRUNCATE money.materialized_billable_xact_summary;
13813 INSERT INTO money.materialized_billable_xact_summary SELECT * FROM money.billable_xact_summary;
13814
13815 -- Now redefine the view as a window onto the materialized view
13816 CREATE OR REPLACE VIEW money.billable_xact_summary AS
13817     SELECT * FROM money.materialized_billable_xact_summary;
13818
13819 CREATE OR REPLACE FUNCTION permission.usr_has_perm_at_nd(
13820     user_id    IN INTEGER,
13821     perm_code  IN TEXT
13822 )
13823 RETURNS SETOF INTEGER AS $$
13824 --
13825 -- Return a set of all the org units for which a given user has a given
13826 -- permission, granted directly (not through inheritance from a parent
13827 -- org unit).
13828 --
13829 -- The permissions apply to a minimum depth of the org unit hierarchy,
13830 -- for the org unit(s) to which the user is assigned.  (They also apply
13831 -- to the subordinates of those org units, but we don't report the
13832 -- subordinates here.)
13833 --
13834 -- For purposes of this function, the permission.usr_work_ou_map table
13835 -- defines which users belong to which org units.  I.e. we ignore the
13836 -- home_ou column of actor.usr.
13837 --
13838 -- The result set may contain duplicates, which should be eliminated
13839 -- by a DISTINCT clause.
13840 --
13841 DECLARE
13842     b_super       BOOLEAN;
13843     n_perm        INTEGER;
13844     n_min_depth   INTEGER;
13845     n_work_ou     INTEGER;
13846     n_curr_ou     INTEGER;
13847     n_depth       INTEGER;
13848     n_curr_depth  INTEGER;
13849 BEGIN
13850     --
13851     -- Check for superuser
13852     --
13853     SELECT INTO b_super
13854         super_user
13855     FROM
13856         actor.usr
13857     WHERE
13858         id = user_id;
13859     --
13860     IF NOT FOUND THEN
13861         return;             -- No user?  No permissions.
13862     ELSIF b_super THEN
13863         --
13864         -- Super user has all permissions everywhere
13865         --
13866         FOR n_work_ou IN
13867             SELECT
13868                 id
13869             FROM
13870                 actor.org_unit
13871             WHERE
13872                 parent_ou IS NULL
13873         LOOP
13874             RETURN NEXT n_work_ou;
13875         END LOOP;
13876         RETURN;
13877     END IF;
13878     --
13879     -- Translate the permission name
13880     -- to a numeric permission id
13881     --
13882     SELECT INTO n_perm
13883         id
13884     FROM
13885         permission.perm_list
13886     WHERE
13887         code = perm_code;
13888     --
13889     IF NOT FOUND THEN
13890         RETURN;               -- No such permission
13891     END IF;
13892     --
13893     -- Find the highest-level org unit (i.e. the minimum depth)
13894     -- to which the permission is applied for this user
13895     --
13896     -- This query is modified from the one in permission.usr_perms().
13897     --
13898     SELECT INTO n_min_depth
13899         min( depth )
13900     FROM    (
13901         SELECT depth
13902           FROM permission.usr_perm_map upm
13903          WHERE upm.usr = user_id
13904            AND (upm.perm = n_perm OR upm.perm = -1)
13905                     UNION
13906         SELECT  gpm.depth
13907           FROM  permission.grp_perm_map gpm
13908           WHERE (gpm.perm = n_perm OR gpm.perm = -1)
13909             AND gpm.grp IN (
13910                SELECT   (permission.grp_ancestors(
13911                     (SELECT profile FROM actor.usr WHERE id = user_id)
13912                 )).id
13913             )
13914                     UNION
13915         SELECT  p.depth
13916           FROM  permission.grp_perm_map p
13917           WHERE (p.perm = n_perm OR p.perm = -1)
13918             AND p.grp IN (
13919                 SELECT (permission.grp_ancestors(m.grp)).id
13920                 FROM   permission.usr_grp_map m
13921                 WHERE  m.usr = user_id
13922             )
13923     ) AS x;
13924     --
13925     IF NOT FOUND THEN
13926         RETURN;                -- No such permission for this user
13927     END IF;
13928     --
13929     -- Identify the org units to which the user is assigned.  Note that
13930     -- we pay no attention to the home_ou column in actor.usr.
13931     --
13932     FOR n_work_ou IN
13933         SELECT
13934             work_ou
13935         FROM
13936             permission.usr_work_ou_map
13937         WHERE
13938             usr = user_id
13939     LOOP            -- For each org unit to which the user is assigned
13940         --
13941         -- Determine the level of the org unit by a lookup in actor.org_unit_type.
13942         -- We take it on faith that this depth agrees with the actual hierarchy
13943         -- defined in actor.org_unit.
13944         --
13945         SELECT INTO n_depth
13946             type.depth
13947         FROM
13948             actor.org_unit_type type
13949                 INNER JOIN actor.org_unit ou
13950                     ON ( ou.ou_type = type.id )
13951         WHERE
13952             ou.id = n_work_ou;
13953         --
13954         IF NOT FOUND THEN
13955             CONTINUE;        -- Maybe raise exception?
13956         END IF;
13957         --
13958         -- Compare the depth of the work org unit to the
13959         -- minimum depth, and branch accordingly
13960         --
13961         IF n_depth = n_min_depth THEN
13962             --
13963             -- The org unit is at the right depth, so return it.
13964             --
13965             RETURN NEXT n_work_ou;
13966         ELSIF n_depth > n_min_depth THEN
13967             --
13968             -- Traverse the org unit tree toward the root,
13969             -- until you reach the minimum depth determined above
13970             --
13971             n_curr_depth := n_depth;
13972             n_curr_ou := n_work_ou;
13973             WHILE n_curr_depth > n_min_depth LOOP
13974                 SELECT INTO n_curr_ou
13975                     parent_ou
13976                 FROM
13977                     actor.org_unit
13978                 WHERE
13979                     id = n_curr_ou;
13980                 --
13981                 IF FOUND THEN
13982                     n_curr_depth := n_curr_depth - 1;
13983                 ELSE
13984                     --
13985                     -- This can happen only if the hierarchy defined in
13986                     -- actor.org_unit is corrupted, or out of sync with
13987                     -- the depths defined in actor.org_unit_type.
13988                     -- Maybe we should raise an exception here, instead
13989                     -- of silently ignoring the problem.
13990                     --
13991                     n_curr_ou = NULL;
13992                     EXIT;
13993                 END IF;
13994             END LOOP;
13995             --
13996             IF n_curr_ou IS NOT NULL THEN
13997                 RETURN NEXT n_curr_ou;
13998             END IF;
13999         ELSE
14000             --
14001             -- The permission applies only at a depth greater than the work org unit.
14002             -- Use connectby() to find all dependent org units at the specified depth.
14003             --
14004             FOR n_curr_ou IN
14005                 SELECT ou::INTEGER
14006                 FROM connectby(
14007                         'actor.org_unit',         -- table name
14008                         'id',                     -- key column
14009                         'parent_ou',              -- recursive foreign key
14010                         n_work_ou::TEXT,          -- id of starting point
14011                         (n_min_depth - n_depth)   -- max depth to search, relative
14012                     )                             --   to starting point
14013                     AS t(
14014                         ou text,            -- dependent org unit
14015                         parent_ou text,     -- (ignore)
14016                         level int           -- depth relative to starting point
14017                     )
14018                 WHERE
14019                     level = n_min_depth - n_depth
14020             LOOP
14021                 RETURN NEXT n_curr_ou;
14022             END LOOP;
14023         END IF;
14024         --
14025     END LOOP;
14026     --
14027     RETURN;
14028     --
14029 END;
14030 $$ LANGUAGE 'plpgsql';
14031
14032 ALTER TABLE acq.purchase_order
14033         ADD COLUMN cancel_reason INT
14034                 REFERENCES acq.cancel_reason( id )
14035             DEFERRABLE INITIALLY DEFERRED,
14036         ADD COLUMN prepayment_required BOOLEAN NOT NULL DEFAULT FALSE;
14037
14038 -- Build the history table and lifecycle view
14039 -- for acq.purchase_order
14040
14041 SELECT acq.create_acq_auditor ( 'acq', 'purchase_order' );
14042
14043 CREATE INDEX acq_po_hist_id_idx            ON acq.acq_purchase_order_history( id );
14044
14045 ALTER TABLE acq.lineitem
14046         ADD COLUMN cancel_reason INT
14047                 REFERENCES acq.cancel_reason( id )
14048             DEFERRABLE INITIALLY DEFERRED,
14049         ADD COLUMN estimated_unit_price NUMERIC,
14050         ADD COLUMN claim_policy INT
14051                 REFERENCES acq.claim_policy
14052                 DEFERRABLE INITIALLY DEFERRED,
14053         ALTER COLUMN eg_bib_id SET DATA TYPE bigint;
14054
14055 -- Build the history table and lifecycle view
14056 -- for acq.lineitem
14057
14058 SELECT acq.create_acq_auditor ( 'acq', 'lineitem' );
14059 CREATE INDEX acq_lineitem_hist_id_idx            ON acq.acq_lineitem_history( id );
14060
14061 ALTER TABLE acq.lineitem_detail
14062         ADD COLUMN cancel_reason        INT REFERENCES acq.cancel_reason( id )
14063                                             DEFERRABLE INITIALLY DEFERRED;
14064
14065 ALTER TABLE acq.lineitem_detail
14066         DROP CONSTRAINT lineitem_detail_lineitem_fkey;
14067
14068 ALTER TABLE acq.lineitem_detail
14069         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14070                 ON DELETE CASCADE
14071                 DEFERRABLE INITIALLY DEFERRED;
14072
14073 ALTER TABLE acq.lineitem_detail DROP CONSTRAINT lineitem_detail_eg_copy_id_fkey;
14074
14075 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14076         1, 1, 'invalid_isbn', oils_i18n_gettext( 1, 'ISBN is unrecognizable', 'acqcr', 'label' ));
14077
14078 INSERT INTO acq.cancel_reason ( id, org_unit, label, description ) VALUES (
14079         2, 1, 'postpone', oils_i18n_gettext( 2, 'Title has been postponed', 'acqcr', 'label' ));
14080
14081 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT, force_add INT ) RETURNS TEXT AS $_$
14082
14083     use MARC::Record;
14084     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14085     use strict;
14086
14087     my $target_xml = shift;
14088     my $source_xml = shift;
14089     my $field_spec = shift;
14090     my $force_add = shift || 0;
14091
14092     my $target_r = MARC::Record->new_from_xml( $target_xml );
14093     my $source_r = MARC::Record->new_from_xml( $source_xml );
14094
14095     return $target_xml unless ($target_r && $source_r);
14096
14097     my @field_list = split(',', $field_spec);
14098
14099     my %fields;
14100     for my $f (@field_list) {
14101         $f =~ s/^\s*//; $f =~ s/\s*$//;
14102         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14103             my $field = $1;
14104             $field =~ s/\s+//;
14105             my $sf = $2;
14106             $sf =~ s/\s+//;
14107             my $match = $3;
14108             $match =~ s/^\s*//; $match =~ s/\s*$//;
14109             $fields{$field} = { sf => [ split('', $sf) ] };
14110             if ($match) {
14111                 my ($msf,$mre) = split('~', $match);
14112                 if (length($msf) > 0 and length($mre) > 0) {
14113                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14114                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14115                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14116                 }
14117             }
14118         }
14119     }
14120
14121     for my $f ( keys %fields) {
14122         if ( @{$fields{$f}{sf}} ) {
14123             for my $from_field ($source_r->field( $f )) {
14124                 my @tos = $target_r->field( $f );
14125                 if (!@tos) {
14126                     next if (exists($fields{$f}{match}) and !$force_add);
14127                     my @new_fields = map { $_->clone } $source_r->field( $f );
14128                     $target_r->insert_fields_ordered( @new_fields );
14129                 } else {
14130                     for my $to_field (@tos) {
14131                         if (exists($fields{$f}{match})) {
14132                             next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14133                         }
14134                         my @new_sf = map { ($_ => $from_field->subfield($_)) } @{$fields{$f}{sf}};
14135                         $to_field->add_subfields( @new_sf );
14136                     }
14137                 }
14138             }
14139         } else {
14140             my @new_fields = map { $_->clone } $source_r->field( $f );
14141             $target_r->insert_fields_ordered( @new_fields );
14142         }
14143     }
14144
14145     $target_xml = $target_r->as_xml_record;
14146     $target_xml =~ s/^<\?.+?\?>$//mo;
14147     $target_xml =~ s/\n//sgo;
14148     $target_xml =~ s/>\s+</></sgo;
14149
14150     return $target_xml;
14151
14152 $_$ LANGUAGE PLPERLU;
14153
14154 CREATE OR REPLACE FUNCTION vandelay.add_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14155     SELECT vandelay.add_field( $1, $2, $3, 0 );
14156 $_$ LANGUAGE SQL;
14157
14158 CREATE OR REPLACE FUNCTION vandelay.strip_field ( xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14159
14160     use MARC::Record;
14161     use MARC::File::XML (BinaryEncoding => 'UTF-8');
14162     use strict;
14163
14164     my $xml = shift;
14165     my $r = MARC::Record->new_from_xml( $xml );
14166
14167     return $xml unless ($r);
14168
14169     my $field_spec = shift;
14170     my @field_list = split(',', $field_spec);
14171
14172     my %fields;
14173     for my $f (@field_list) {
14174         $f =~ s/^\s*//; $f =~ s/\s*$//;
14175         if ($f =~ /^(.{3})(\w*)(?:\[([^]]*)\])?$/) {
14176             my $field = $1;
14177             $field =~ s/\s+//;
14178             my $sf = $2;
14179             $sf =~ s/\s+//;
14180             my $match = $3;
14181             $match =~ s/^\s*//; $match =~ s/\s*$//;
14182             $fields{$field} = { sf => [ split('', $sf) ] };
14183             if ($match) {
14184                 my ($msf,$mre) = split('~', $match);
14185                 if (length($msf) > 0 and length($mre) > 0) {
14186                     $msf =~ s/^\s*//; $msf =~ s/\s*$//;
14187                     $mre =~ s/^\s*//; $mre =~ s/\s*$//;
14188                     $fields{$field}{match} = { sf => $msf, re => qr/$mre/ };
14189                 }
14190             }
14191         }
14192     }
14193
14194     for my $f ( keys %fields) {
14195         for my $to_field ($r->field( $f )) {
14196             if (exists($fields{$f}{match})) {
14197                 next unless (grep { $_ =~ $fields{$f}{match}{re} } $to_field->subfield($fields{$f}{match}{sf}));
14198             }
14199
14200             if ( @{$fields{$f}{sf}} ) {
14201                 $to_field->delete_subfield(code => $fields{$f}{sf});
14202             } else {
14203                 $r->delete_field( $to_field );
14204             }
14205         }
14206     }
14207
14208     $xml = $r->as_xml_record;
14209     $xml =~ s/^<\?.+?\?>$//mo;
14210     $xml =~ s/\n//sgo;
14211     $xml =~ s/>\s+</></sgo;
14212
14213     return $xml;
14214
14215 $_$ LANGUAGE PLPERLU;
14216
14217 CREATE OR REPLACE FUNCTION vandelay.replace_field ( target_xml TEXT, source_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14218 DECLARE
14219     xml_output TEXT;
14220     parsed_target TEXT;
14221 BEGIN
14222     parsed_target := vandelay.strip_field( target_xml, ''); -- this dance normalized the format of the xml for the IF below
14223     xml_output := vandelay.strip_field( parsed_target, field);
14224
14225     IF xml_output <> parsed_target  AND field ~ E'~' THEN
14226         -- we removed something, and there was a regexp restriction in the field definition, so proceed
14227         xml_output := vandelay.add_field( xml_output, source_xml, field, 1 );
14228     ELSIF field !~ E'~' THEN
14229         -- No regexp restriction, add the field
14230         xml_output := vandelay.add_field( xml_output, source_xml, field, 0 );
14231     END IF;
14232
14233     RETURN xml_output;
14234 END;
14235 $_$ LANGUAGE PLPGSQL;
14236
14237 CREATE OR REPLACE FUNCTION vandelay.preserve_field ( incumbent_xml TEXT, incoming_xml TEXT, field TEXT ) RETURNS TEXT AS $_$
14238     SELECT vandelay.add_field( vandelay.strip_field( $2, $3), $1, $3 );
14239 $_$ LANGUAGE SQL;
14240
14241 CREATE VIEW action.unfulfilled_hold_max_loop AS
14242         SELECT  hold,
14243                 max(count) AS max
14244         FROM    action.unfulfilled_hold_loops
14245         GROUP BY 1;
14246
14247 ALTER TABLE acq.lineitem_attr
14248         DROP CONSTRAINT lineitem_attr_lineitem_fkey;
14249
14250 ALTER TABLE acq.lineitem_attr
14251         ADD FOREIGN KEY (lineitem) REFERENCES acq.lineitem( id )
14252                 ON DELETE CASCADE
14253                 DEFERRABLE INITIALLY DEFERRED;
14254
14255 ALTER TABLE acq.po_note
14256         ADD COLUMN vendor_public BOOLEAN NOT NULL DEFAULT FALSE;
14257
14258 CREATE TABLE vandelay.merge_profile (
14259     id              BIGSERIAL   PRIMARY KEY,
14260     owner           INT         NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
14261     name            TEXT        NOT NULL,
14262     add_spec        TEXT,
14263     replace_spec    TEXT,
14264     strip_spec      TEXT,
14265     preserve_spec   TEXT,
14266     CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
14267     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))
14268 );
14269
14270 CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
14271 DECLARE
14272     attr        RECORD;
14273     attr_def    RECORD;
14274     eg_rec      RECORD;
14275     id_value    TEXT;
14276     exact_id    BIGINT;
14277 BEGIN
14278
14279     DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
14280
14281     SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
14282
14283     IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
14284         id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
14285
14286         IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
14287             SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
14288             SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
14289             IF exact_id IS NOT NULL THEN
14290                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
14291             END IF;
14292         END IF;
14293     END IF;
14294
14295     IF exact_id IS NULL THEN
14296         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
14297
14298             -- All numbers? check for an id match
14299             IF (attr.attr_value ~ $r$^\d+$$r$) THEN
14300                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
14301                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14302                 END LOOP;
14303             END IF;
14304
14305             -- Looks like an ISBN? check for an isbn match
14306             IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
14307                 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
14308                     PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
14309                     IF FOUND THEN
14310                         INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
14311                     END IF;
14312                 END LOOP;
14313
14314                 -- subcheck for isbn-as-tcn
14315                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
14316                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14317                 END LOOP;
14318             END IF;
14319
14320             -- check for an OCLC tcn_value match
14321             IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
14322                 FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
14323                     INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14324                 END LOOP;
14325             END IF;
14326
14327             -- check for a direct tcn_value match
14328             FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
14329                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
14330             END LOOP;
14331
14332             -- check for a direct item barcode match
14333             FOR eg_rec IN
14334                     SELECT  DISTINCT b.*
14335                       FROM  biblio.record_entry b
14336                             JOIN asset.call_number cn ON (cn.record = b.id)
14337                             JOIN asset.copy cp ON (cp.call_number = cn.id)
14338                       WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
14339             LOOP
14340                 INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
14341             END LOOP;
14342
14343         END LOOP;
14344     END IF;
14345
14346     RETURN NULL;
14347 END;
14348 $func$ LANGUAGE PLPGSQL;
14349
14350 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 $_$
14351     SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);
14352 $_$ LANGUAGE SQL;
14353
14354 CREATE TYPE vandelay.compile_profile AS (add_rule TEXT, replace_rule TEXT, preserve_rule TEXT, strip_rule TEXT);
14355 CREATE OR REPLACE FUNCTION vandelay.compile_profile ( incoming_xml TEXT ) RETURNS vandelay.compile_profile AS $_$
14356 DECLARE
14357     output              vandelay.compile_profile%ROWTYPE;
14358     profile             vandelay.merge_profile%ROWTYPE;
14359     profile_tmpl        TEXT;
14360     profile_tmpl_owner  TEXT;
14361     add_rule            TEXT := '';
14362     strip_rule          TEXT := '';
14363     replace_rule        TEXT := '';
14364     preserve_rule       TEXT := '';
14365
14366 BEGIN
14367
14368     profile_tmpl := (oils_xpath('//*[@tag="905"]/*[@code="t"]/text()',incoming_xml))[1];
14369     profile_tmpl_owner := (oils_xpath('//*[@tag="905"]/*[@code="o"]/text()',incoming_xml))[1];
14370
14371     IF profile_tmpl IS NOT NULL AND profile_tmpl <> '' AND profile_tmpl_owner IS NOT NULL AND profile_tmpl_owner <> '' THEN
14372         SELECT  p.* INTO profile
14373           FROM  vandelay.merge_profile p
14374                 JOIN actor.org_unit u ON (u.id = p.owner)
14375           WHERE p.name = profile_tmpl
14376                 AND u.shortname = profile_tmpl_owner;
14377
14378         IF profile.id IS NOT NULL THEN
14379             add_rule := COALESCE(profile.add_spec,'');
14380             strip_rule := COALESCE(profile.strip_spec,'');
14381             replace_rule := COALESCE(profile.replace_spec,'');
14382             preserve_rule := COALESCE(profile.preserve_spec,'');
14383         END IF;
14384     END IF;
14385
14386     add_rule := add_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="a"]/text()',incoming_xml),','),'');
14387     strip_rule := strip_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="d"]/text()',incoming_xml),','),'');
14388     replace_rule := replace_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="r"]/text()',incoming_xml),','),'');
14389     preserve_rule := preserve_rule || ',' || COALESCE(ARRAY_TO_STRING(oils_xpath('//*[@tag="905"]/*[@code="p"]/text()',incoming_xml),','),'');
14390
14391     output.add_rule := BTRIM(add_rule,',');
14392     output.replace_rule := BTRIM(replace_rule,',');
14393     output.strip_rule := BTRIM(strip_rule,',');
14394     output.preserve_rule := BTRIM(preserve_rule,',');
14395
14396     RETURN output;
14397 END;
14398 $_$ LANGUAGE PLPGSQL;
14399
14400 -- Template-based marc munging functions
14401 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14402 DECLARE
14403     merge_profile   vandelay.merge_profile%ROWTYPE;
14404     dyn_profile     vandelay.compile_profile%ROWTYPE;
14405     editor_string   TEXT;
14406     editor_id       INT;
14407     source_marc     TEXT;
14408     target_marc     TEXT;
14409     eg_marc         TEXT;
14410     replace_rule    TEXT;
14411     match_count     INT;
14412 BEGIN
14413
14414     SELECT  b.marc INTO eg_marc
14415       FROM  biblio.record_entry b
14416       WHERE b.id = eg_id
14417       LIMIT 1;
14418
14419     IF eg_marc IS NULL OR v_marc IS NULL THEN
14420         -- RAISE NOTICE 'no marc for template or bib record';
14421         RETURN FALSE;
14422     END IF;
14423
14424     dyn_profile := vandelay.compile_profile( v_marc );
14425
14426     IF merge_profile_id IS NOT NULL THEN
14427         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14428         IF FOUND THEN
14429             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14430             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14431             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14432             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14433         END IF;
14434     END IF;
14435
14436     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14437         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14438         RETURN FALSE;
14439     END IF;
14440
14441     IF dyn_profile.replace_rule <> '' THEN
14442         source_marc = v_marc;
14443         target_marc = eg_marc;
14444         replace_rule = dyn_profile.replace_rule;
14445     ELSE
14446         source_marc = eg_marc;
14447         target_marc = v_marc;
14448         replace_rule = dyn_profile.preserve_rule;
14449     END IF;
14450
14451     UPDATE  biblio.record_entry
14452       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14453       WHERE id = eg_id;
14454
14455     IF NOT FOUND THEN
14456         -- RAISE NOTICE 'update of biblio.record_entry failed';
14457         RETURN FALSE;
14458     END IF;
14459
14460     RETURN TRUE;
14461
14462 END;
14463 $$ LANGUAGE PLPGSQL;
14464
14465 CREATE OR REPLACE FUNCTION vandelay.template_overlay_bib_record ( v_marc TEXT, eg_id BIGINT) RETURNS BOOL AS $$
14466     SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);
14467 $$ LANGUAGE SQL;
14468
14469 CREATE OR REPLACE FUNCTION vandelay.overlay_bib_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14470 DECLARE
14471     merge_profile   vandelay.merge_profile%ROWTYPE;
14472     dyn_profile     vandelay.compile_profile%ROWTYPE;
14473     editor_string   TEXT;
14474     editor_id       INT;
14475     source_marc     TEXT;
14476     target_marc     TEXT;
14477     eg_marc         TEXT;
14478     v_marc          TEXT;
14479     replace_rule    TEXT;
14480     match_count     INT;
14481 BEGIN
14482
14483     SELECT  q.marc INTO v_marc
14484       FROM  vandelay.queued_record q
14485             JOIN vandelay.bib_match m ON (m.queued_record = q.id AND q.id = import_id)
14486       LIMIT 1;
14487
14488     IF v_marc IS NULL THEN
14489         -- RAISE NOTICE 'no marc for vandelay or bib record';
14490         RETURN FALSE;
14491     END IF;
14492
14493     IF vandelay.template_overlay_bib_record( v_marc, eg_id, merge_profile_id) THEN
14494         UPDATE  vandelay.queued_bib_record
14495           SET   imported_as = eg_id,
14496                 import_time = NOW()
14497           WHERE id = import_id;
14498
14499         editor_string := (oils_xpath('//*[@tag="905"]/*[@code="u"]/text()',v_marc))[1];
14500
14501         IF editor_string IS NOT NULL AND editor_string <> '' THEN
14502             SELECT usr INTO editor_id FROM actor.card WHERE barcode = editor_string;
14503
14504             IF editor_id IS NULL THEN
14505                 SELECT id INTO editor_id FROM actor.usr WHERE usrname = editor_string;
14506             END IF;
14507
14508             IF editor_id IS NOT NULL THEN
14509                 UPDATE biblio.record_entry SET editor = editor_id WHERE id = eg_id;
14510             END IF;
14511         END IF;
14512
14513         RETURN TRUE;
14514     END IF;
14515
14516     -- RAISE NOTICE 'update of biblio.record_entry failed';
14517
14518     RETURN FALSE;
14519
14520 END;
14521 $$ LANGUAGE PLPGSQL;
14522
14523 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14524 DECLARE
14525     eg_id           BIGINT;
14526     match_count     INT;
14527     match_attr      vandelay.bib_attr_definition%ROWTYPE;
14528 BEGIN
14529
14530     PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
14531
14532     IF FOUND THEN
14533         -- RAISE NOTICE 'already imported, cannot auto-overlay'
14534         RETURN FALSE;
14535     END IF;
14536
14537     SELECT COUNT(*) INTO match_count FROM vandelay.bib_match WHERE queued_record = import_id;
14538
14539     IF match_count <> 1 THEN
14540         -- RAISE NOTICE 'not an exact match';
14541         RETURN FALSE;
14542     END IF;
14543
14544     SELECT  d.* INTO match_attr
14545       FROM  vandelay.bib_attr_definition d
14546             JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
14547             JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
14548       WHERE m.queued_record = import_id;
14549
14550     IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
14551         -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
14552         RETURN FALSE;
14553     END IF;
14554
14555     SELECT  m.eg_record INTO eg_id
14556       FROM  vandelay.bib_match m
14557       WHERE m.queued_record = import_id
14558       LIMIT 1;
14559
14560     IF eg_id IS NULL THEN
14561         RETURN FALSE;
14562     END IF;
14563
14564     RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
14565 END;
14566 $$ LANGUAGE PLPGSQL;
14567
14568 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14569 DECLARE
14570     queued_record   vandelay.queued_bib_record%ROWTYPE;
14571 BEGIN
14572
14573     FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
14574
14575         IF vandelay.auto_overlay_bib_record( queued_record.id, merge_profile_id ) THEN
14576             RETURN NEXT queued_record.id;
14577         END IF;
14578
14579     END LOOP;
14580
14581     RETURN;
14582
14583 END;
14584 $$ LANGUAGE PLPGSQL;
14585
14586 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14587     SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
14588 $$ LANGUAGE SQL;
14589
14590 CREATE OR REPLACE FUNCTION vandelay.overlay_authority_record ( import_id BIGINT, eg_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14591 DECLARE
14592     merge_profile   vandelay.merge_profile%ROWTYPE;
14593     dyn_profile     vandelay.compile_profile%ROWTYPE;
14594     source_marc     TEXT;
14595     target_marc     TEXT;
14596     eg_marc         TEXT;
14597     v_marc          TEXT;
14598     replace_rule    TEXT;
14599     match_count     INT;
14600 BEGIN
14601
14602     SELECT  b.marc INTO eg_marc
14603       FROM  authority.record_entry b
14604             JOIN vandelay.authority_match m ON (m.eg_record = b.id AND m.queued_record = import_id)
14605       LIMIT 1;
14606
14607     SELECT  q.marc INTO v_marc
14608       FROM  vandelay.queued_record q
14609             JOIN vandelay.authority_match m ON (m.queued_record = q.id AND q.id = import_id)
14610       LIMIT 1;
14611
14612     IF eg_marc IS NULL OR v_marc IS NULL THEN
14613         -- RAISE NOTICE 'no marc for vandelay or authority record';
14614         RETURN FALSE;
14615     END IF;
14616
14617     dyn_profile := vandelay.compile_profile( v_marc );
14618
14619     IF merge_profile_id IS NOT NULL THEN
14620         SELECT * INTO merge_profile FROM vandelay.merge_profile WHERE id = merge_profile_id;
14621         IF FOUND THEN
14622             dyn_profile.add_rule := BTRIM( dyn_profile.add_rule || ',' || COALESCE(merge_profile.add_spec,''), ',');
14623             dyn_profile.strip_rule := BTRIM( dyn_profile.strip_rule || ',' || COALESCE(merge_profile.strip_spec,''), ',');
14624             dyn_profile.replace_rule := BTRIM( dyn_profile.replace_rule || ',' || COALESCE(merge_profile.replace_spec,''), ',');
14625             dyn_profile.preserve_rule := BTRIM( dyn_profile.preserve_rule || ',' || COALESCE(merge_profile.preserve_spec,''), ',');
14626         END IF;
14627     END IF;
14628
14629     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
14630         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
14631         RETURN FALSE;
14632     END IF;
14633
14634     IF dyn_profile.replace_rule <> '' THEN
14635         source_marc = v_marc;
14636         target_marc = eg_marc;
14637         replace_rule = dyn_profile.replace_rule;
14638     ELSE
14639         source_marc = eg_marc;
14640         target_marc = v_marc;
14641         replace_rule = dyn_profile.preserve_rule;
14642     END IF;
14643
14644     UPDATE  authority.record_entry
14645       SET   marc = vandelay.merge_record_xml( target_marc, source_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule )
14646       WHERE id = eg_id;
14647
14648     IF FOUND THEN
14649         UPDATE  vandelay.queued_authority_record
14650           SET   imported_as = eg_id,
14651                 import_time = NOW()
14652           WHERE id = import_id;
14653         RETURN TRUE;
14654     END IF;
14655
14656     -- RAISE NOTICE 'update of authority.record_entry failed';
14657
14658     RETURN FALSE;
14659
14660 END;
14661 $$ LANGUAGE PLPGSQL;
14662
14663 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
14664 DECLARE
14665     eg_id           BIGINT;
14666     match_count     INT;
14667 BEGIN
14668     SELECT COUNT(*) INTO match_count FROM vandelay.authority_match WHERE queued_record = import_id;
14669
14670     IF match_count <> 1 THEN
14671         -- RAISE NOTICE 'not an exact match';
14672         RETURN FALSE;
14673     END IF;
14674
14675     SELECT  m.eg_record INTO eg_id
14676       FROM  vandelay.authority_match m
14677       WHERE m.queued_record = import_id
14678       LIMIT 1;
14679
14680     IF eg_id IS NULL THEN
14681         RETURN FALSE;
14682     END IF;
14683
14684     RETURN vandelay.overlay_authority_record( import_id, eg_id, merge_profile_id );
14685 END;
14686 $$ LANGUAGE PLPGSQL;
14687
14688 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
14689 DECLARE
14690     queued_record   vandelay.queued_authority_record%ROWTYPE;
14691 BEGIN
14692
14693     FOR queued_record IN SELECT * FROM vandelay.queued_authority_record WHERE queue = queue_id AND import_time IS NULL LOOP
14694
14695         IF vandelay.auto_overlay_authority_record( queued_record.id, merge_profile_id ) THEN
14696             RETURN NEXT queued_record.id;
14697         END IF;
14698
14699     END LOOP;
14700
14701     RETURN;
14702
14703 END;
14704 $$ LANGUAGE PLPGSQL;
14705
14706 CREATE OR REPLACE FUNCTION vandelay.auto_overlay_authority_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
14707     SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );
14708 $$ LANGUAGE SQL;
14709
14710 CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
14711 CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
14712 DECLARE
14713     eg_tcn          TEXT;
14714     eg_tcn_source   TEXT;
14715     output          vandelay.tcn_data%ROWTYPE;
14716 BEGIN
14717
14718     -- 001/003
14719     eg_tcn := BTRIM((oils_xpath('//*[@tag="001"]/text()',xml))[1]);
14720     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14721
14722         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="003"]/text()',xml))[1]);
14723         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14724             eg_tcn_source := 'System Local';
14725         END IF;
14726
14727         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14728
14729         IF NOT FOUND THEN
14730             output.used := FALSE;
14731         ELSE
14732             output.used := TRUE;
14733         END IF;
14734
14735         output.tcn := eg_tcn;
14736         output.tcn_source := eg_tcn_source;
14737         RETURN NEXT output;
14738
14739     END IF;
14740
14741     -- 901 ab
14742     eg_tcn := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="a"]/text()',xml))[1]);
14743     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14744
14745         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="901"]/*[@code="b"]/text()',xml))[1]);
14746         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14747             eg_tcn_source := 'System Local';
14748         END IF;
14749
14750         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14751
14752         IF NOT FOUND THEN
14753             output.used := FALSE;
14754         ELSE
14755             output.used := TRUE;
14756         END IF;
14757
14758         output.tcn := eg_tcn;
14759         output.tcn_source := eg_tcn_source;
14760         RETURN NEXT output;
14761
14762     END IF;
14763
14764     -- 039 ab
14765     eg_tcn := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="a"]/text()',xml))[1]);
14766     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14767
14768         eg_tcn_source := BTRIM((oils_xpath('//*[@tag="039"]/*[@code="b"]/text()',xml))[1]);
14769         IF eg_tcn_source IS NULL OR eg_tcn_source = '' THEN
14770             eg_tcn_source := 'System Local';
14771         END IF;
14772
14773         PERFORM id FROM biblio.record_entry WHERE tcn_value = eg_tcn  AND NOT deleted;
14774
14775         IF NOT FOUND THEN
14776             output.used := FALSE;
14777         ELSE
14778             output.used := TRUE;
14779         END IF;
14780
14781         output.tcn := eg_tcn;
14782         output.tcn_source := eg_tcn_source;
14783         RETURN NEXT output;
14784
14785     END IF;
14786
14787     -- 020 a
14788     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="020"]/*[@code="a"]/text()',xml))[1], $re$^(\w+).*?$$re$, $re$\1$re$);
14789     IF eg_tcn IS NOT NULL AND eg_tcn <> '' THEN
14790
14791         eg_tcn_source := 'ISBN';
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     -- 022 a
14808     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="022"]/*[@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 := 'ISSN';
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     -- 010 a
14828     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="010"]/*[@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 := 'LCCN';
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     -- 035 a
14848     eg_tcn := REGEXP_REPLACE((oils_xpath('//*[@tag="035"]/*[@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 := 'System Legacy';
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     RETURN;
14868 END;
14869 $_$ LANGUAGE PLPGSQL;
14870
14871 CREATE INDEX claim_lid_idx ON acq.claim( lineitem_detail );
14872
14873 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);
14874
14875 CREATE INDEX metabib_title_field_entry_value_idx ON metabib.title_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14876 CREATE INDEX metabib_author_field_entry_value_idx ON metabib.author_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14877 CREATE INDEX metabib_subject_field_entry_value_idx ON metabib.subject_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14878 CREATE INDEX metabib_keyword_field_entry_value_idx ON metabib.keyword_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14879 CREATE INDEX metabib_series_field_entry_value_idx ON metabib.series_field_entry (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
14880
14881 CREATE INDEX metabib_author_field_entry_source_idx ON metabib.author_field_entry (source);
14882 CREATE INDEX metabib_keyword_field_entry_source_idx ON metabib.keyword_field_entry (source);
14883 CREATE INDEX metabib_title_field_entry_source_idx ON metabib.title_field_entry (source);
14884 CREATE INDEX metabib_series_field_entry_source_idx ON metabib.series_field_entry (source);
14885
14886 ALTER TABLE metabib.series_field_entry
14887         ADD CONSTRAINT metabib_series_field_entry_source_pkey FOREIGN KEY (source)
14888                 REFERENCES biblio.record_entry (id)
14889                 ON DELETE CASCADE
14890                 DEFERRABLE INITIALLY DEFERRED;
14891
14892 ALTER TABLE metabib.series_field_entry
14893         ADD CONSTRAINT metabib_series_field_entry_field_pkey FOREIGN KEY (field)
14894                 REFERENCES config.metabib_field (id)
14895                 ON DELETE CASCADE
14896                 DEFERRABLE INITIALLY DEFERRED;
14897
14898 CREATE TABLE acq.claim_policy_action (
14899         id              SERIAL       PRIMARY KEY,
14900         claim_policy    INT          NOT NULL REFERENCES acq.claim_policy
14901                                  ON DELETE CASCADE
14902                                      DEFERRABLE INITIALLY DEFERRED,
14903         action_interval INTERVAL     NOT NULL,
14904         action          INT          NOT NULL REFERENCES acq.claim_event_type
14905                                      DEFERRABLE INITIALLY DEFERRED,
14906         CONSTRAINT action_sequence UNIQUE (claim_policy, action_interval)
14907 );
14908
14909 CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $function$
14910 DECLARE
14911     value       TEXT; 
14912     atype       TEXT; 
14913     prov        INT;
14914     pos         INT;
14915     adef        RECORD;
14916     xpath_string    TEXT;
14917 BEGIN
14918     FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
14919     
14920         SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
14921       
14922         IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
14923             IF (atype = 'lineitem_provider_attr_definition') THEN
14924                 SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14925                 CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
14926             END IF;
14927             
14928             IF (atype = 'lineitem_provider_attr_definition') THEN
14929                 SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
14930             ELSIF (atype = 'lineitem_marc_attr_definition') THEN
14931                 SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
14932             ELSIF (atype = 'lineitem_generated_attr_definition') THEN
14933                 SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
14934             END IF;
14935       
14936             xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');
14937
14938             IF (adef.code = 'title' OR adef.code = 'author') THEN
14939                 -- title and author should not be split
14940                 -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
14941                 -- string-join in the xpath and remove this special case
14942                 SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14943                 IF (value IS NOT NULL AND value <> '') THEN
14944                     INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14945                         VALUES (NEW.id, adef.id, atype, adef.code, value);
14946                 END IF;
14947             ELSE
14948                 pos := 1;
14949
14950                 LOOP
14951                     SELECT extract_acq_marc_field(id, xpath_string || '[' || pos || ']', adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
14952       
14953                     IF (value IS NOT NULL AND value <> '') THEN
14954                         INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
14955                             VALUES (NEW.id, adef.id, atype, adef.code, value);
14956                     ELSE
14957                         EXIT;
14958                     END IF;
14959
14960                     pos := pos + 1;
14961                 END LOOP;
14962             END IF;
14963
14964         END IF;
14965
14966     END LOOP;
14967
14968     RETURN NULL;
14969 END;
14970 $function$ LANGUAGE PLPGSQL;
14971
14972 UPDATE config.metabib_field SET label = name;
14973 ALTER TABLE config.metabib_field ALTER COLUMN label SET NOT NULL;
14974
14975 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_field_class_fkey
14976          FOREIGN KEY (field_class) REFERENCES config.metabib_class (name);
14977
14978 ALTER TABLE config.metabib_field DROP CONSTRAINT metabib_field_field_class_check;
14979
14980 ALTER TABLE config.metabib_field ADD CONSTRAINT metabib_field_format_fkey FOREIGN KEY (format) REFERENCES config.xml_transform (name);
14981
14982 CREATE TABLE config.metabib_search_alias (
14983     alias       TEXT    PRIMARY KEY,
14984     field_class TEXT    NOT NULL REFERENCES config.metabib_class (name),
14985     field       INT     REFERENCES config.metabib_field (id)
14986 );
14987
14988 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('kw','keyword');
14989 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.keyword','keyword');
14990 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.publisher','keyword');
14991 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','keyword');
14992 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.subjecttitle','keyword');
14993 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.genre','keyword');
14994 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.edition','keyword');
14995 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('srw.serverchoice','keyword');
14996
14997 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('au','author');
14998 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('name','author');
14999 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('creator','author');
15000 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.author','author');
15001 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.name','author');
15002 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.creator','author');
15003 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.contributor','author');
15004 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('bib.name','author');
15005 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonal','author',8);
15006 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalfamily','author',8);
15007 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namepersonalgiven','author',8);
15008 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.namecorporate','author',7);
15009 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.nameconference','author',9);
15010
15011 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('ti','title');
15012 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.title','title');
15013 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.title','title');
15014 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleabbreviated','title',2);
15015 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleuniform','title',5);
15016 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titletranslated','title',3);
15017 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titlealternative','title',4);
15018 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.title','title',2);
15019
15020 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('su','subject');
15021 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.subject','subject');
15022 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.subject','subject');
15023 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectplace','subject',11);
15024 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectname','subject',12);
15025
15026 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('se','series');
15027 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('eg.series','series');
15028 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.titleseries','series',1);
15029
15030 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 1;
15031 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;
15032 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;
15033 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;
15034 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;
15035
15036 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 11;
15037 UPDATE config.metabib_field SET facet_field=TRUE , facet_xpath=$$*[local-name()='namePart']$$ WHERE id = 12;
15038 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 13;
15039 UPDATE config.metabib_field SET facet_field=TRUE WHERE id = 14;
15040
15041 CREATE INDEX metabib_rec_descriptor_item_type_idx ON metabib.rec_descriptor (item_type);
15042 CREATE INDEX metabib_rec_descriptor_item_form_idx ON metabib.rec_descriptor (item_form);
15043 CREATE INDEX metabib_rec_descriptor_bib_level_idx ON metabib.rec_descriptor (bib_level);
15044 CREATE INDEX metabib_rec_descriptor_control_type_idx ON metabib.rec_descriptor (control_type);
15045 CREATE INDEX metabib_rec_descriptor_char_encoding_idx ON metabib.rec_descriptor (char_encoding);
15046 CREATE INDEX metabib_rec_descriptor_enc_level_idx ON metabib.rec_descriptor (enc_level);
15047 CREATE INDEX metabib_rec_descriptor_audience_idx ON metabib.rec_descriptor (audience);
15048 CREATE INDEX metabib_rec_descriptor_lit_form_idx ON metabib.rec_descriptor (lit_form);
15049 CREATE INDEX metabib_rec_descriptor_cat_form_idx ON metabib.rec_descriptor (cat_form);
15050 CREATE INDEX metabib_rec_descriptor_pub_status_idx ON metabib.rec_descriptor (pub_status);
15051 CREATE INDEX metabib_rec_descriptor_item_lang_idx ON metabib.rec_descriptor (item_lang);
15052 CREATE INDEX metabib_rec_descriptor_vr_format_idx ON metabib.rec_descriptor (vr_format);
15053 CREATE INDEX metabib_rec_descriptor_date1_idx ON metabib.rec_descriptor (date1);
15054 CREATE INDEX metabib_rec_descriptor_dates_idx ON metabib.rec_descriptor (date1,date2);
15055
15056 CREATE TABLE asset.opac_visible_copies (
15057   id        BIGINT primary key, -- copy id
15058   record    BIGINT,
15059   circ_lib  INTEGER
15060 );
15061 COMMENT ON TABLE asset.opac_visible_copies IS $$
15062 Materialized view of copies that are visible in the OPAC, used by
15063 search.query_parser_fts() to speed up OPAC visibility checks on large
15064 databases.  Contents are maintained by a set of triggers.
15065 $$;
15066 CREATE INDEX opac_visible_copies_idx1 on asset.opac_visible_copies (record, circ_lib);
15067
15068 CREATE OR REPLACE FUNCTION search.query_parser_fts (
15069
15070     param_search_ou INT,
15071     param_depth     INT,
15072     param_query     TEXT,
15073     param_statuses  INT[],
15074     param_locations INT[],
15075     param_offset    INT,
15076     param_check     INT,
15077     param_limit     INT,
15078     metarecord      BOOL,
15079     staff           BOOL
15080  
15081 ) RETURNS SETOF search.search_result AS $func$
15082 DECLARE
15083
15084     current_res         search.search_result%ROWTYPE;
15085     search_org_list     INT[];
15086
15087     check_limit         INT;
15088     core_limit          INT;
15089     core_offset         INT;
15090     tmp_int             INT;
15091
15092     core_result         RECORD;
15093     core_cursor         REFCURSOR;
15094     core_rel_query      TEXT;
15095
15096     total_count         INT := 0;
15097     check_count         INT := 0;
15098     deleted_count       INT := 0;
15099     visible_count       INT := 0;
15100     excluded_count      INT := 0;
15101
15102 BEGIN
15103
15104     check_limit := COALESCE( param_check, 1000 );
15105     core_limit  := COALESCE( param_limit, 25000 );
15106     core_offset := COALESCE( param_offset, 0 );
15107
15108     -- core_skip_chk := COALESCE( param_skip_chk, 1 );
15109
15110     IF param_search_ou > 0 THEN
15111         IF param_depth IS NOT NULL THEN
15112             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou, param_depth );
15113         ELSE
15114             SELECT array_accum(distinct id) INTO search_org_list FROM actor.org_unit_descendants( param_search_ou );
15115         END IF;
15116     ELSIF param_search_ou < 0 THEN
15117         SELECT array_accum(distinct org_unit) INTO search_org_list FROM actor.org_lasso_map WHERE lasso = -param_search_ou;
15118     ELSIF param_search_ou = 0 THEN
15119         -- reserved for user lassos (ou_buckets/type='lasso') with ID passed in depth ... hack? sure.
15120     END IF;
15121
15122     OPEN core_cursor FOR EXECUTE param_query;
15123
15124     LOOP
15125
15126         FETCH core_cursor INTO core_result;
15127         EXIT WHEN NOT FOUND;
15128         EXIT WHEN total_count >= core_limit;
15129
15130         total_count := total_count + 1;
15131
15132         CONTINUE WHEN total_count NOT BETWEEN  core_offset + 1 AND check_limit + core_offset;
15133
15134         check_count := check_count + 1;
15135
15136         PERFORM 1 FROM biblio.record_entry b WHERE NOT b.deleted AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15137         IF NOT FOUND THEN
15138             -- RAISE NOTICE ' % were all deleted ... ', core_result.records;
15139             deleted_count := deleted_count + 1;
15140             CONTINUE;
15141         END IF;
15142
15143         PERFORM 1
15144           FROM  biblio.record_entry b
15145                 JOIN config.bib_source s ON (b.source = s.id)
15146           WHERE s.transcendant
15147                 AND b.id IN ( SELECT * FROM search.explode_array( core_result.records ) );
15148
15149         IF FOUND THEN
15150             -- RAISE NOTICE ' % were all transcendant ... ', core_result.records;
15151             visible_count := visible_count + 1;
15152
15153             current_res.id = core_result.id;
15154             current_res.rel = core_result.rel;
15155
15156             tmp_int := 1;
15157             IF metarecord THEN
15158                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15159             END IF;
15160
15161             IF tmp_int = 1 THEN
15162                 current_res.record = core_result.records[1];
15163             ELSE
15164                 current_res.record = NULL;
15165             END IF;
15166
15167             RETURN NEXT current_res;
15168
15169             CONTINUE;
15170         END IF;
15171
15172         PERFORM 1
15173           FROM  asset.call_number cn
15174                 JOIN asset.uri_call_number_map map ON (map.call_number = cn.id)
15175                 JOIN asset.uri uri ON (map.uri = uri.id)
15176           WHERE NOT cn.deleted
15177                 AND cn.label = '##URI##'
15178                 AND uri.active
15179                 AND ( param_locations IS NULL OR array_upper(param_locations, 1) IS NULL )
15180                 AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15181                 AND cn.owning_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15182           LIMIT 1;
15183
15184         IF FOUND THEN
15185             -- RAISE NOTICE ' % have at least one URI ... ', core_result.records;
15186             visible_count := visible_count + 1;
15187
15188             current_res.id = core_result.id;
15189             current_res.rel = core_result.rel;
15190
15191             tmp_int := 1;
15192             IF metarecord THEN
15193                 SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15194             END IF;
15195
15196             IF tmp_int = 1 THEN
15197                 current_res.record = core_result.records[1];
15198             ELSE
15199                 current_res.record = NULL;
15200             END IF;
15201
15202             RETURN NEXT current_res;
15203
15204             CONTINUE;
15205         END IF;
15206
15207         IF param_statuses IS NOT NULL AND array_upper(param_statuses, 1) > 0 THEN
15208
15209             PERFORM 1
15210               FROM  asset.call_number cn
15211                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15212               WHERE NOT cn.deleted
15213                     AND NOT cp.deleted
15214                     AND cp.status IN ( SELECT * FROM search.explode_array( param_statuses ) )
15215                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15216                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15217               LIMIT 1;
15218
15219             IF NOT FOUND THEN
15220                 -- RAISE NOTICE ' % were all status-excluded ... ', core_result.records;
15221                 excluded_count := excluded_count + 1;
15222                 CONTINUE;
15223             END IF;
15224
15225         END IF;
15226
15227         IF param_locations IS NOT NULL AND array_upper(param_locations, 1) > 0 THEN
15228
15229             PERFORM 1
15230               FROM  asset.call_number cn
15231                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15232               WHERE NOT cn.deleted
15233                     AND NOT cp.deleted
15234                     AND cp.location IN ( SELECT * FROM search.explode_array( param_locations ) )
15235                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15236                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15237               LIMIT 1;
15238
15239             IF NOT FOUND THEN
15240                 -- RAISE NOTICE ' % were all copy_location-excluded ... ', core_result.records;
15241                 excluded_count := excluded_count + 1;
15242                 CONTINUE;
15243             END IF;
15244
15245         END IF;
15246
15247         IF staff IS NULL OR NOT staff THEN
15248
15249             PERFORM 1
15250               FROM  asset.opac_visible_copies
15251               WHERE circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15252                     AND record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15253               LIMIT 1;
15254
15255             IF NOT FOUND THEN
15256                 -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15257                 excluded_count := excluded_count + 1;
15258                 CONTINUE;
15259             END IF;
15260
15261         ELSE
15262
15263             PERFORM 1
15264               FROM  asset.call_number cn
15265                     JOIN asset.copy cp ON (cp.call_number = cn.id)
15266                     JOIN actor.org_unit a ON (cp.circ_lib = a.id)
15267               WHERE NOT cn.deleted
15268                     AND NOT cp.deleted
15269                     AND cp.circ_lib IN ( SELECT * FROM search.explode_array( search_org_list ) )
15270                     AND cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15271               LIMIT 1;
15272
15273             IF NOT FOUND THEN
15274
15275                 PERFORM 1
15276                   FROM  asset.call_number cn
15277                   WHERE cn.record IN ( SELECT * FROM search.explode_array( core_result.records ) )
15278                   LIMIT 1;
15279
15280                 IF FOUND THEN
15281                     -- RAISE NOTICE ' % were all visibility-excluded ... ', core_result.records;
15282                     excluded_count := excluded_count + 1;
15283                     CONTINUE;
15284                 END IF;
15285
15286             END IF;
15287
15288         END IF;
15289
15290         visible_count := visible_count + 1;
15291
15292         current_res.id = core_result.id;
15293         current_res.rel = core_result.rel;
15294
15295         tmp_int := 1;
15296         IF metarecord THEN
15297             SELECT COUNT(DISTINCT s.source) INTO tmp_int FROM metabib.metarecord_source_map s WHERE s.metarecord = core_result.id;
15298         END IF;
15299
15300         IF tmp_int = 1 THEN
15301             current_res.record = core_result.records[1];
15302         ELSE
15303             current_res.record = NULL;
15304         END IF;
15305
15306         RETURN NEXT current_res;
15307
15308         IF visible_count % 1000 = 0 THEN
15309             -- RAISE NOTICE ' % visible so far ... ', visible_count;
15310         END IF;
15311
15312     END LOOP;
15313
15314     current_res.id = NULL;
15315     current_res.rel = NULL;
15316     current_res.record = NULL;
15317     current_res.total = total_count;
15318     current_res.checked = check_count;
15319     current_res.deleted = deleted_count;
15320     current_res.visible = visible_count;
15321     current_res.excluded = excluded_count;
15322
15323     CLOSE core_cursor;
15324
15325     RETURN NEXT current_res;
15326
15327 END;
15328 $func$ LANGUAGE PLPGSQL;
15329
15330 ALTER TABLE biblio.record_entry ADD COLUMN owner INT;
15331 ALTER TABLE biblio.record_entry
15332          ADD CONSTRAINT biblio_record_entry_owner_fkey FOREIGN KEY (owner)
15333          REFERENCES actor.org_unit (id)
15334          DEFERRABLE INITIALLY DEFERRED;
15335
15336 ALTER TABLE biblio.record_entry ADD COLUMN share_depth INT;
15337
15338 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN owner INT;
15339 ALTER TABLE auditor.biblio_record_entry_history ADD COLUMN share_depth INT;
15340
15341 DROP VIEW auditor.biblio_record_entry_lifecycle;
15342
15343 SELECT auditor.create_auditor_lifecycle( 'biblio', 'record_entry' );
15344
15345 CREATE OR REPLACE FUNCTION public.first_word ( TEXT ) RETURNS TEXT AS $$
15346         SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');
15347 $$ LANGUAGE SQL STRICT IMMUTABLE;
15348
15349 CREATE OR REPLACE FUNCTION public.normalize_space( TEXT ) RETURNS TEXT AS $$
15350     SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');
15351 $$ LANGUAGE SQL STRICT IMMUTABLE;
15352
15353 CREATE OR REPLACE FUNCTION public.lowercase( TEXT ) RETURNS TEXT AS $$
15354     return lc(shift);
15355 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15356
15357 CREATE OR REPLACE FUNCTION public.uppercase( TEXT ) RETURNS TEXT AS $$
15358     return uc(shift);
15359 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15360
15361 CREATE OR REPLACE FUNCTION public.remove_diacritics( TEXT ) RETURNS TEXT AS $$
15362     use Unicode::Normalize;
15363
15364     my $x = NFD(shift);
15365     $x =~ s/\pM+//go;
15366     return $x;
15367
15368 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15369
15370 CREATE OR REPLACE FUNCTION public.entityize( TEXT ) RETURNS TEXT AS $$
15371     use Unicode::Normalize;
15372
15373     my $x = NFC(shift);
15374     $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
15375     return $x;
15376
15377 $$ LANGUAGE PLPERLU STRICT IMMUTABLE;
15378
15379 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_setting( setting_name TEXT, org_id INT ) RETURNS SETOF actor.org_unit_setting AS $$
15380 DECLARE
15381     setting RECORD;
15382     cur_org INT;
15383 BEGIN
15384     cur_org := org_id;
15385     LOOP
15386         SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
15387         IF FOUND THEN
15388             RETURN NEXT setting;
15389         END IF;
15390         SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
15391         EXIT WHEN cur_org IS NULL;
15392     END LOOP;
15393     RETURN;
15394 END;
15395 $$ LANGUAGE plpgsql STABLE;
15396
15397 CREATE OR REPLACE FUNCTION acq.extract_holding_attr_table (lineitem int, tag text) RETURNS SETOF acq.flat_lineitem_holding_subfield AS $$
15398 DECLARE
15399     counter INT;
15400     lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
15401 BEGIN
15402
15403     SELECT  COUNT(*) INTO counter
15404       FROM  oils_xpath_table(
15405                 'id',
15406                 'marc',
15407                 'acq.lineitem',
15408                 '//*[@tag="' || tag || '"]',
15409                 'id=' || lineitem
15410             ) as t(i int,c text);
15411
15412     FOR i IN 1 .. counter LOOP
15413         FOR lida IN
15414             SELECT  *
15415               FROM  (   SELECT  id,i,t,v
15416                           FROM  oils_xpath_table(
15417                                     'id',
15418                                     'marc',
15419                                     'acq.lineitem',
15420                                     '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
15421                                         '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
15422                                     'id=' || lineitem
15423                                 ) as t(id int,t text,v text)
15424                     )x
15425         LOOP
15426             RETURN NEXT lida;
15427         END LOOP;
15428     END LOOP;
15429
15430     RETURN;
15431 END;
15432 $$ LANGUAGE PLPGSQL;
15433
15434 CREATE OR REPLACE FUNCTION oils_i18n_xlate ( keytable TEXT, keyclass TEXT, keycol TEXT, identcol TEXT, keyvalue TEXT, raw_locale TEXT ) RETURNS TEXT AS $func$
15435 DECLARE
15436     locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
15437     language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
15438     result      config.i18n_core%ROWTYPE;
15439     fallback    TEXT;
15440     keyfield    TEXT := keyclass || '.' || keycol;
15441 BEGIN
15442
15443     -- Try the full locale
15444     SELECT  * INTO result
15445       FROM  config.i18n_core
15446       WHERE fq_field = keyfield
15447             AND identity_value = keyvalue
15448             AND translation = locale;
15449
15450     -- Try just the language
15451     IF NOT FOUND THEN
15452         SELECT  * INTO result
15453           FROM  config.i18n_core
15454           WHERE fq_field = keyfield
15455                 AND identity_value = keyvalue
15456                 AND translation = language;
15457     END IF;
15458
15459     -- Fall back to the string we passed in in the first place
15460     IF NOT FOUND THEN
15461     EXECUTE
15462             'SELECT ' ||
15463                 keycol ||
15464             ' FROM ' || keytable ||
15465             ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
15466                 INTO fallback;
15467         RETURN fallback;
15468     END IF;
15469
15470     RETURN result.string;
15471 END;
15472 $func$ LANGUAGE PLPGSQL STABLE;
15473
15474 SELECT auditor.create_auditor ( 'acq', 'invoice' );
15475
15476 SELECT auditor.create_auditor ( 'acq', 'invoice_item' );
15477
15478 SELECT auditor.create_auditor ( 'acq', 'invoice_entry' );
15479
15480 INSERT INTO acq.cancel_reason ( id, org_unit, label, description, keep_debits ) VALUES (
15481     3, 1, 'delivered_but_lost',
15482     oils_i18n_gettext( 2, 'Delivered but not received; presumed lost', 'acqcr', 'label' ), TRUE );
15483
15484 CREATE TABLE config.global_flag (
15485     label   TEXT    NOT NULL
15486 ) INHERITS (config.internal_flag);
15487 ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
15488
15489 INSERT INTO config.global_flag (name, label, enabled)
15490     VALUES (
15491         'cat.bib.use_id_for_tcn',
15492         oils_i18n_gettext(
15493             'cat.bib.use_id_for_tcn',
15494             'Cat: Use Internal ID for TCN Value',
15495             'cgf', 
15496             'label'
15497         ),
15498         TRUE
15499     );
15500
15501 -- resolves performance issue noted by EG Indiana
15502
15503 CREATE INDEX scecm_owning_copy_idx ON asset.stat_cat_entry_copy_map(owning_copy);
15504
15505 INSERT INTO config.metabib_class ( name, label ) VALUES ( 'identifier', oils_i18n_gettext('identifier', 'Identifier', 'cmc', 'name') );
15506
15507 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath )
15508     SELECT  16, 'subject', 'complete', oils_i18n_gettext(16, 'All Subjects', 'cmf', 'label'), 'mods32', $$//mods32:mods/mods32:subject//text()$$
15509       WHERE NOT EXISTS (select id from config.metabib_field where field_class = 'subject' and name = 'complete'); -- in case it's already there
15510
15511 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15512     (17, 'identifier', 'accession', oils_i18n_gettext(17, 'Accession Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="001"]/text()$$, TRUE );
15513 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15514     (18, 'identifier', 'isbn', oils_i18n_gettext(18, 'ISBN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="020"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15515 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15516     (19, 'identifier', 'issn', oils_i18n_gettext(19, 'ISSN', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="022"]/marcxml:subfield[code="a" or code="z"]/text()$$, TRUE );
15517 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15518     (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 );
15519 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15520     (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 );
15521 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15522     (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 );
15523 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15524     (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 );
15525 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15526     (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 );
15527 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field ) VALUES
15528     (25, 'identifier', 'bibcn', oils_i18n_gettext(25, 'Local Free-Text Call Number', 'cmf', 'label'), 'marcxml', $$//marcxml:datafield[tag="099"]//text()$$, TRUE );
15529
15530 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
15531  
15532
15533 DELETE FROM config.metabib_search_alias WHERE alias = 'dc.identifier';
15534
15535 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('bib.subjectoccupation','subject',16);
15536 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('id','identifier');
15537 INSERT INTO config.metabib_search_alias (alias,field_class) VALUES ('dc.identifier','identifier');
15538 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.isbn','identifier', 18);
15539 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.issn','identifier', 19);
15540 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.upc','identifier', 20);
15541 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.callnumber','identifier', 25);
15542
15543 CREATE TABLE metabib.identifier_field_entry (
15544         id              BIGSERIAL       PRIMARY KEY,
15545         source          BIGINT          NOT NULL,
15546         field           INT             NOT NULL,
15547         value           TEXT            NOT NULL,
15548         index_vector    tsvector        NOT NULL
15549 );
15550 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15551         BEFORE UPDATE OR INSERT ON metabib.identifier_field_entry
15552         FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
15553
15554 CREATE INDEX metabib_identifier_field_entry_index_vector_idx ON metabib.identifier_field_entry USING GIST (index_vector);
15555 CREATE INDEX metabib_identifier_field_entry_value_idx ON metabib.identifier_field_entry
15556     (SUBSTRING(value,1,1024)) WHERE index_vector = ''::TSVECTOR;
15557 CREATE INDEX metabib_identifier_field_entry_source_idx ON metabib.identifier_field_entry (source);
15558
15559 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_source_pkey
15560     FOREIGN KEY (source) REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15561 ALTER TABLE metabib.identifier_field_entry ADD CONSTRAINT metabib_identifier_field_entry_field_pkey
15562     FOREIGN KEY (field) REFERENCES config.metabib_field (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
15563
15564 CREATE OR REPLACE FUNCTION public.translate_isbn1013( TEXT ) RETURNS TEXT AS $func$
15565     use Business::ISBN;
15566     use strict;
15567     use warnings;
15568
15569     # For each ISBN found in a single string containing a set of ISBNs:
15570     #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
15571     #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return
15572
15573     my $input = shift;
15574     my $output = '';
15575
15576     foreach my $word (split(/\s/, $input)) {
15577         my $isbn = Business::ISBN->new($word);
15578
15579         # First check the checksum; if it is not valid, fix it and add the original
15580         # bad-checksum ISBN to the output
15581         if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
15582             $output .= $isbn->isbn() . " ";
15583             $isbn->fix_checksum();
15584         }
15585
15586         # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
15587         # and add the normalized original ISBN to the output
15588         if ($isbn && $isbn->is_valid()) {
15589             my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
15590             $output .= $isbn->isbn . " ";
15591
15592             # If we successfully converted the ISBN to its counterpart, add the
15593             # converted ISBN to the output as well
15594             $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
15595         }
15596     }
15597     return $output if $output;
15598
15599     # If there were no valid ISBNs, just return the raw input
15600     return $input;
15601 $func$ LANGUAGE PLPERLU;
15602
15603 COMMENT ON FUNCTION public.translate_isbn1013(TEXT) IS $$
15604 /*
15605  * Copyright (C) 2010 Merrimack Valley Library Consortium
15606  * Jason Stephenson <jstephenson@mvlc.org>
15607  * Copyright (C) 2010 Laurentian University
15608  * Dan Scott <dscott@laurentian.ca>
15609  *
15610  * The translate_isbn1013 function takes an input ISBN and returns the
15611  * following in a single space-delimited string if the input ISBN is valid:
15612  *   - The normalized input ISBN (hyphens stripped)
15613  *   - The normalized input ISBN with a fixed checksum if the checksum was bad
15614  *   - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible
15615  */
15616 $$;
15617
15618 UPDATE config.metabib_field SET facet_field = FALSE WHERE id BETWEEN 17 AND 25;
15619 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'marcxml','marc') WHERE id BETWEEN 17 AND 25;
15620 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'tag','@tag') WHERE id BETWEEN 17 AND 25;
15621 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'code','@code') WHERE id BETWEEN 17 AND 25;
15622 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'"',E'\'') WHERE id BETWEEN 17 AND 25;
15623 UPDATE config.metabib_field SET xpath = REPLACE(xpath,'/text()','') WHERE id BETWEEN 17 AND 24;
15624
15625 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15626         'ISBN 10/13 conversion',
15627         'Translate ISBN10 to ISBN13, and vice versa, for indexing purposes.',
15628         'translate_isbn1013',
15629         0
15630 );
15631
15632 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
15633         'Replace',
15634         'Replace all occurences of first parameter in the string with the second parameter.',
15635         'replace',
15636         2
15637 );
15638
15639 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15640     SELECT  m.id, i.id, 1
15641       FROM  config.metabib_field m,
15642             config.index_normalizer i
15643       WHERE i.func IN ('first_word')
15644             AND m.id IN (18);
15645
15646 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
15647     SELECT  m.id, i.id, 2
15648       FROM  config.metabib_field m,
15649             config.index_normalizer i
15650       WHERE i.func IN ('translate_isbn1013')
15651             AND m.id IN (18);
15652
15653 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15654     SELECT  m.id, i.id, $$['-','']$$
15655       FROM  config.metabib_field m,
15656             config.index_normalizer i
15657       WHERE i.func IN ('replace')
15658             AND m.id IN (19);
15659
15660 INSERT INTO config.metabib_field_index_norm_map (field,norm,params)
15661     SELECT  m.id, i.id, $$[' ','']$$
15662       FROM  config.metabib_field m,
15663             config.index_normalizer i
15664       WHERE i.func IN ('replace')
15665             AND m.id IN (19);
15666
15667 DELETE FROM config.metabib_field_index_norm_map WHERE norm IN (1,2) and field > 16;
15668
15669 UPDATE  config.metabib_field_index_norm_map
15670   SET   params = REPLACE(params,E'\'','"')
15671   WHERE params IS NOT NULL AND params <> '';
15672
15673 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15674
15675 CREATE TEXT SEARCH CONFIGURATION identifier ( COPY = title );
15676
15677 ALTER TABLE config.circ_modifier
15678         ADD COLUMN avg_wait_time INTERVAL;
15679
15680 --CREATE TABLE actor.usr_password_reset (
15681 --  id SERIAL PRIMARY KEY,
15682 --  uuid TEXT NOT NULL, 
15683 --  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
15684 --  request_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), 
15685 --  has_been_reset BOOL NOT NULL DEFAULT false
15686 --);
15687 --COMMENT ON TABLE actor.usr_password_reset IS $$
15688 --/*
15689 -- * Copyright (C) 2010 Laurentian University
15690 -- * Dan Scott <dscott@laurentian.ca>
15691 -- *
15692 -- * Self-serve password reset requests
15693 -- *
15694 -- * ****
15695 -- *
15696 -- * This program is free software; you can redistribute it and/or
15697 -- * modify it under the terms of the GNU General Public License
15698 -- * as published by the Free Software Foundation; either version 2
15699 -- * of the License, or (at your option) any later version.
15700 -- *
15701 -- * This program is distributed in the hope that it will be useful,
15702 -- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15703 -- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15704 -- * GNU General Public License for more details.
15705 -- */
15706 --$$;
15707 --CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
15708 --CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
15709 --CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
15710 --CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
15711
15712 -- Use the identifier search class tsconfig
15713 DROP TRIGGER IF EXISTS metabib_identifier_field_entry_fti_trigger ON metabib.identifier_field_entry;
15714 CREATE TRIGGER metabib_identifier_field_entry_fti_trigger
15715     BEFORE INSERT OR UPDATE ON metabib.identifier_field_entry
15716     FOR EACH ROW
15717     EXECUTE PROCEDURE public.oils_tsearch2('identifier');
15718
15719 INSERT INTO config.global_flag (name,label,enabled)
15720     VALUES ('history.circ.retention_age',oils_i18n_gettext('history.circ.retention_age', 'Historical Circulation Retention Age', 'cgf', 'label'), TRUE);
15721 INSERT INTO config.global_flag (name,label,enabled)
15722     VALUES ('history.circ.retention_count',oils_i18n_gettext('history.circ.retention_count', 'Historical Circulations per Copy', 'cgf', 'label'), TRUE);
15723
15724 -- turn a JSON scalar into an SQL TEXT value
15725 CREATE OR REPLACE FUNCTION oils_json_to_text( TEXT ) RETURNS TEXT AS $f$
15726     use JSON::XS;                    
15727     my $json = shift();
15728     my $txt;
15729     eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };   
15730     return undef if ($@);
15731     return $txt
15732 $f$ LANGUAGE PLPERLU;
15733
15734 -- Return the list of circ chain heads in xact_start order that the user has chosen to "retain"
15735 CREATE OR REPLACE FUNCTION action.usr_visible_circs (usr_id INT) RETURNS SETOF action.circulation AS $func$
15736 DECLARE
15737     c               action.circulation%ROWTYPE;
15738     view_age        INTERVAL;
15739     usr_view_age    actor.usr_setting%ROWTYPE;
15740     usr_view_start  actor.usr_setting%ROWTYPE;
15741 BEGIN
15742     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_age';
15743     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.circ.retention_start';
15744
15745     IF usr_view_age.value IS NOT NULL AND usr_view_start.value IS NOT NULL THEN
15746         -- User opted in and supplied a retention age
15747         IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
15748             view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15749         ELSE
15750             view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
15751         END IF;
15752     ELSIF usr_view_start.value IS NOT NULL THEN
15753         -- User opted in
15754         view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
15755     ELSE
15756         -- User did not opt in
15757         RETURN;
15758     END IF;
15759
15760     FOR c IN
15761         SELECT  *
15762           FROM  action.circulation
15763           WHERE usr = usr_id
15764                 AND parent_circ IS NULL
15765                 AND xact_start > NOW() - view_age
15766           ORDER BY xact_start
15767     LOOP
15768         RETURN NEXT c;
15769     END LOOP;
15770
15771     RETURN;
15772 END;
15773 $func$ LANGUAGE PLPGSQL;
15774
15775 CREATE OR REPLACE FUNCTION action.purge_circulations () RETURNS INT AS $func$
15776 DECLARE
15777     usr_keep_age    actor.usr_setting%ROWTYPE;
15778     usr_keep_start  actor.usr_setting%ROWTYPE;
15779     org_keep_age    INTERVAL;
15780     org_keep_count  INT;
15781
15782     keep_age        INTERVAL;
15783
15784     target_acp      RECORD;
15785     circ_chain_head action.circulation%ROWTYPE;
15786     circ_chain_tail action.circulation%ROWTYPE;
15787
15788     purge_position  INT;
15789     count_purged    INT;
15790 BEGIN
15791
15792     count_purged := 0;
15793
15794     SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;
15795
15796     SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
15797     IF org_keep_count IS NULL THEN
15798         RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
15799     END IF;
15800
15801     -- First, find copies with more than keep_count non-renewal circs
15802     FOR target_acp IN
15803         SELECT  target_copy,
15804                 COUNT(*) AS total_real_circs
15805           FROM  action.circulation
15806           WHERE parent_circ IS NULL
15807                 AND xact_finish IS NOT NULL
15808           GROUP BY target_copy
15809           HAVING COUNT(*) > org_keep_count
15810     LOOP
15811         purge_position := 0;
15812         -- And, for those, select circs that are finished and older than keep_age
15813         FOR circ_chain_head IN
15814             SELECT  *
15815               FROM  action.circulation
15816               WHERE target_copy = target_acp.target_copy
15817                     AND parent_circ IS NULL
15818               ORDER BY xact_start
15819         LOOP
15820
15821             -- Stop once we've purged enough circs to hit org_keep_count
15822             EXIT WHEN target_acp.total_real_circs - purge_position <= org_keep_count;
15823
15824             SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
15825             EXIT WHEN circ_chain_tail.xact_finish IS NULL;
15826
15827             -- Now get the user settings, if any, to block purging if the user wants to keep more circs
15828             usr_keep_age.value := NULL;
15829             SELECT * INTO usr_keep_age FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_age';
15830
15831             usr_keep_start.value := NULL;
15832             SELECT * INTO usr_keep_start FROM actor.usr_setting WHERE usr = circ_chain_head.usr AND name = 'history.circ.retention_start';
15833
15834             IF usr_keep_age.value IS NOT NULL AND usr_keep_start.value IS NOT NULL THEN
15835                 IF oils_json_to_text(usr_keep_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ) THEN
15836                     keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15837                 ELSE
15838                     keep_age := oils_json_to_text(usr_keep_age.value)::INTERVAL;
15839                 END IF;
15840             ELSIF usr_keep_start.value IS NOT NULL THEN
15841                 keep_age := AGE(NOW(), oils_json_to_text(usr_keep_start.value)::TIMESTAMPTZ);
15842             ELSE
15843                 keep_age := COALESCE( org_keep_age::INTERVAL, '2000 years'::INTERVAL );
15844             END IF;
15845
15846             EXIT WHEN AGE(NOW(), circ_chain_tail.xact_finish) < keep_age;
15847
15848             -- We've passed the purging tests, purge the circ chain starting at the end
15849             DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15850             WHILE circ_chain_tail.parent_circ IS NOT NULL LOOP
15851                 SELECT * INTO circ_chain_tail FROM action.circulation WHERE id = circ_chain_tail.parent_circ;
15852                 DELETE FROM action.circulation WHERE id = circ_chain_tail.id;
15853             END LOOP;
15854
15855             count_purged := count_purged + 1;
15856             purge_position := purge_position + 1;
15857
15858         END LOOP;
15859     END LOOP;
15860 END;
15861 $func$ LANGUAGE PLPGSQL;
15862
15863 CREATE OR REPLACE FUNCTION action.usr_visible_holds (usr_id INT) RETURNS SETOF action.hold_request AS $func$
15864 DECLARE
15865     h               action.hold_request%ROWTYPE;
15866     view_age        INTERVAL;
15867     view_count      INT;
15868     usr_view_count  actor.usr_setting%ROWTYPE;
15869     usr_view_age    actor.usr_setting%ROWTYPE;
15870     usr_view_start  actor.usr_setting%ROWTYPE;
15871 BEGIN
15872     SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
15873     SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
15874     SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';
15875
15876     FOR h IN
15877         SELECT  *
15878           FROM  action.hold_request
15879           WHERE usr = usr_id
15880                 AND fulfillment_time IS NULL
15881                 AND cancel_time IS NULL
15882           ORDER BY request_time DESC
15883     LOOP
15884         RETURN NEXT h;
15885     END LOOP;
15886
15887     IF usr_view_start.value IS NULL THEN
15888         RETURN;
15889     END IF;
15890
15891     IF usr_view_age.value IS NOT NULL THEN
15892         -- User opted in and supplied a retention age
15893         IF oils_json_to_string(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ) THEN
15894             view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15895         ELSE
15896             view_age := oils_json_to_string(usr_view_age.value)::INTERVAL;
15897         END IF;
15898     ELSE
15899         -- User opted in
15900         view_age := AGE(NOW(), oils_json_to_string(usr_view_start.value)::TIMESTAMPTZ);
15901     END IF;
15902
15903     IF usr_view_count.value IS NOT NULL THEN
15904         view_count := oils_json_to_text(usr_view_count.value)::INT;
15905     ELSE
15906         view_count := 1000;
15907     END IF;
15908
15909     -- show some fulfilled/canceled holds
15910     FOR h IN
15911         SELECT  *
15912           FROM  action.hold_request
15913           WHERE usr = usr_id
15914                 AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
15915                 AND request_time > NOW() - view_age
15916           ORDER BY request_time DESC
15917           LIMIT view_count
15918     LOOP
15919         RETURN NEXT h;
15920     END LOOP;
15921
15922     RETURN;
15923 END;
15924 $func$ LANGUAGE PLPGSQL;
15925
15926 DROP TABLE IF EXISTS serial.bib_summary CASCADE;
15927
15928 DROP TABLE IF EXISTS serial.index_summary CASCADE;
15929
15930 DROP TABLE IF EXISTS serial.sup_summary CASCADE;
15931
15932 DROP TABLE IF EXISTS serial.issuance CASCADE;
15933
15934 DROP TABLE IF EXISTS serial.binding_unit CASCADE;
15935
15936 DROP TABLE IF EXISTS serial.subscription CASCADE;
15937
15938 CREATE TABLE asset.copy_template (
15939         id             SERIAL   PRIMARY KEY,
15940         owning_lib     INT      NOT NULL
15941                                 REFERENCES actor.org_unit (id)
15942                                 DEFERRABLE INITIALLY DEFERRED,
15943         creator        BIGINT   NOT NULL
15944                                 REFERENCES actor.usr (id)
15945                                 DEFERRABLE INITIALLY DEFERRED,
15946         editor         BIGINT   NOT NULL
15947                                 REFERENCES actor.usr (id)
15948                                 DEFERRABLE INITIALLY DEFERRED,
15949         create_date    TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15950         edit_date      TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
15951         name           TEXT     NOT NULL,
15952         -- columns above this point are attributes of the template itself
15953         -- columns after this point are attributes of the copy this template modifies/creates
15954         circ_lib       INT      REFERENCES actor.org_unit (id)
15955                                 DEFERRABLE INITIALLY DEFERRED,
15956         status         INT      REFERENCES config.copy_status (id)
15957                                 DEFERRABLE INITIALLY DEFERRED,
15958         location       INT      REFERENCES asset.copy_location (id)
15959                                 DEFERRABLE INITIALLY DEFERRED,
15960         loan_duration  INT      CONSTRAINT valid_loan_duration CHECK (
15961                                     loan_duration IS NULL OR loan_duration IN (1,2,3)),
15962         fine_level     INT      CONSTRAINT valid_fine_level CHECK (
15963                                     fine_level IS NULL OR loan_duration IN (1,2,3)),
15964         age_protect    INT,
15965         circulate      BOOL,
15966         deposit        BOOL,
15967         ref            BOOL,
15968         holdable       BOOL,
15969         deposit_amount NUMERIC(6,2),
15970         price          NUMERIC(8,2),
15971         circ_modifier  TEXT,
15972         circ_as_type   TEXT,
15973         alert_message  TEXT,
15974         opac_visible   BOOL,
15975         floating       BOOL,
15976         mint_condition BOOL
15977 );
15978
15979 CREATE TABLE serial.subscription (
15980         id                     SERIAL       PRIMARY KEY,
15981         owning_lib             INT          NOT NULL DEFAULT 1
15982                                             REFERENCES actor.org_unit (id)
15983                                             ON DELETE SET NULL
15984                                             DEFERRABLE INITIALLY DEFERRED,
15985         start_date             TIMESTAMP WITH TIME ZONE     NOT NULL,
15986         end_date               TIMESTAMP WITH TIME ZONE,    -- interpret NULL as current subscription
15987         record_entry           BIGINT       REFERENCES biblio.record_entry (id)
15988                                             ON DELETE SET NULL
15989                                             DEFERRABLE INITIALLY DEFERRED,
15990         expected_date_offset   INTERVAL
15991         -- acquisitions/business-side tables link to here
15992 );
15993 CREATE INDEX serial_subscription_record_idx ON serial.subscription (record_entry);
15994 CREATE INDEX serial_subscription_owner_idx ON serial.subscription (owning_lib);
15995
15996 --at least one distribution per org_unit holding issues
15997 CREATE TABLE serial.distribution (
15998         id                    SERIAL  PRIMARY KEY,
15999         record_entry          BIGINT  REFERENCES serial.record_entry (id)
16000                                       ON DELETE SET NULL
16001                                       DEFERRABLE INITIALLY DEFERRED,
16002         summary_method        TEXT    CONSTRAINT sdist_summary_method_check CHECK (
16003                                           summary_method IS NULL
16004                                           OR summary_method IN ( 'add_to_sre',
16005                                           'merge_with_sre', 'use_sre_only',
16006                                           'use_sdist_only')),
16007         subscription          INT     NOT NULL
16008                                       REFERENCES serial.subscription (id)
16009                                                                   ON DELETE CASCADE
16010                                                                   DEFERRABLE INITIALLY DEFERRED,
16011         holding_lib           INT     NOT NULL
16012                                       REFERENCES actor.org_unit (id)
16013                                                                   DEFERRABLE INITIALLY DEFERRED,
16014         label                 TEXT    NOT NULL,
16015         receive_call_number   BIGINT  REFERENCES asset.call_number (id)
16016                                       DEFERRABLE INITIALLY DEFERRED,
16017         receive_unit_template INT     REFERENCES asset.copy_template (id)
16018                                       DEFERRABLE INITIALLY DEFERRED,
16019         bind_call_number      BIGINT  REFERENCES asset.call_number (id)
16020                                       DEFERRABLE INITIALLY DEFERRED,
16021         bind_unit_template    INT     REFERENCES asset.copy_template (id)
16022                                       DEFERRABLE INITIALLY DEFERRED,
16023         unit_label_prefix     TEXT,
16024         unit_label_suffix     TEXT
16025 );
16026 CREATE INDEX serial_distribution_sub_idx ON serial.distribution (subscription);
16027 CREATE INDEX serial_distribution_holding_lib_idx ON serial.distribution (holding_lib);
16028
16029 CREATE UNIQUE INDEX one_dist_per_sre_idx ON serial.distribution (record_entry);
16030
16031 CREATE TABLE serial.stream (
16032         id              SERIAL  PRIMARY KEY,
16033         distribution    INT     NOT NULL
16034                                 REFERENCES serial.distribution (id)
16035                                 ON DELETE CASCADE
16036                                 DEFERRABLE INITIALLY DEFERRED,
16037         routing_label   TEXT
16038 );
16039 CREATE INDEX serial_stream_dist_idx ON serial.stream (distribution);
16040
16041 CREATE UNIQUE INDEX label_once_per_dist
16042         ON serial.stream (distribution, routing_label)
16043         WHERE routing_label IS NOT NULL;
16044
16045 CREATE TABLE serial.routing_list_user (
16046         id             SERIAL       PRIMARY KEY,
16047         stream         INT          NOT NULL
16048                                     REFERENCES serial.stream
16049                                     ON DELETE CASCADE
16050                                     DEFERRABLE INITIALLY DEFERRED,
16051         pos            INT          NOT NULL DEFAULT 1,
16052         reader         INT          REFERENCES actor.usr
16053                                     ON DELETE CASCADE
16054                                     DEFERRABLE INITIALLY DEFERRED,
16055         department     TEXT,
16056         note           TEXT,
16057         CONSTRAINT one_pos_per_routing_list UNIQUE ( stream, pos ),
16058         CONSTRAINT reader_or_dept CHECK
16059         (
16060             -- Recipient is a person or a department, but not both
16061                 (reader IS NOT NULL AND department IS NULL) OR
16062                 (reader IS NULL AND department IS NOT NULL)
16063         )
16064 );
16065 CREATE INDEX serial_routing_list_user_stream_idx ON serial.routing_list_user (stream);
16066 CREATE INDEX serial_routing_list_user_reader_idx ON serial.routing_list_user (reader);
16067
16068 CREATE TABLE serial.caption_and_pattern (
16069         id           SERIAL       PRIMARY KEY,
16070         subscription INT          NOT NULL REFERENCES serial.subscription (id)
16071                                   ON DELETE CASCADE
16072                                   DEFERRABLE INITIALLY DEFERRED,
16073         type         TEXT         NOT NULL
16074                                   CONSTRAINT cap_type CHECK ( type in
16075                                   ( 'basic', 'supplement', 'index' )),
16076         create_date  TIMESTAMPTZ  NOT NULL DEFAULT now(),
16077         start_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
16078         end_date     TIMESTAMP WITH TIME ZONE,
16079         active       BOOL         NOT NULL DEFAULT FALSE,
16080         pattern_code TEXT         NOT NULL,       -- must contain JSON
16081         enum_1       TEXT,
16082         enum_2       TEXT,
16083         enum_3       TEXT,
16084         enum_4       TEXT,
16085         enum_5       TEXT,
16086         enum_6       TEXT,
16087         chron_1      TEXT,
16088         chron_2      TEXT,
16089         chron_3      TEXT,
16090         chron_4      TEXT,
16091         chron_5      TEXT
16092 );
16093 CREATE INDEX serial_caption_and_pattern_sub_idx ON serial.caption_and_pattern (subscription);
16094
16095 CREATE TABLE serial.issuance (
16096         id              SERIAL    PRIMARY KEY,
16097         creator         INT       NOT NULL
16098                                   REFERENCES actor.usr (id)
16099                                                           DEFERRABLE INITIALLY DEFERRED,
16100         editor          INT       NOT NULL
16101                                   REFERENCES actor.usr (id)
16102                                   DEFERRABLE INITIALLY DEFERRED,
16103         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16104         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16105         subscription    INT       NOT NULL
16106                                   REFERENCES serial.subscription (id)
16107                                   ON DELETE CASCADE
16108                                   DEFERRABLE INITIALLY DEFERRED,
16109         label           TEXT,
16110         date_published  TIMESTAMP WITH TIME ZONE,
16111         caption_and_pattern  INT  REFERENCES serial.caption_and_pattern (id)
16112                               DEFERRABLE INITIALLY DEFERRED,
16113         holding_code    TEXT,
16114         holding_type    TEXT      CONSTRAINT valid_holding_type CHECK
16115                                   (
16116                                       holding_type IS NULL
16117                                       OR holding_type IN ('basic','supplement','index')
16118                                   ),
16119         holding_link_id INT
16120         -- TODO: add columns for separate enumeration/chronology values
16121 );
16122 CREATE INDEX serial_issuance_sub_idx ON serial.issuance (subscription);
16123 CREATE INDEX serial_issuance_caption_and_pattern_idx ON serial.issuance (caption_and_pattern);
16124 CREATE INDEX serial_issuance_date_published_idx ON serial.issuance (date_published);
16125
16126 CREATE TABLE serial.unit (
16127         label           TEXT,
16128         label_sort_key  TEXT,
16129         contents        TEXT    NOT NULL
16130 ) INHERITS (asset.copy);
16131 CREATE UNIQUE INDEX unit_barcode_key ON serial.unit (barcode) WHERE deleted = FALSE OR deleted IS FALSE;
16132 CREATE INDEX unit_cn_idx ON serial.unit (call_number);
16133 CREATE INDEX unit_avail_cn_idx ON serial.unit (call_number);
16134 CREATE INDEX unit_creator_idx  ON serial.unit ( creator );
16135 CREATE INDEX unit_editor_idx   ON serial.unit ( editor );
16136
16137 ALTER TABLE serial.unit ADD PRIMARY KEY (id);
16138
16139 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_call_number_fkey FOREIGN KEY (call_number) REFERENCES asset.call_number (id) DEFERRABLE INITIALLY DEFERRED;
16140
16141 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16142
16143 ALTER TABLE serial.unit ADD CONSTRAINT serial_unit_editor_fkey FOREIGN KEY (editor) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
16144
16145 CREATE TABLE serial.item (
16146         id              SERIAL  PRIMARY KEY,
16147         creator         INT     NOT NULL
16148                                 REFERENCES actor.usr (id)
16149                                 DEFERRABLE INITIALLY DEFERRED,
16150         editor          INT     NOT NULL
16151                                 REFERENCES actor.usr (id)
16152                                 DEFERRABLE INITIALLY DEFERRED,
16153         create_date     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16154         edit_date       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT now(),
16155         issuance        INT     NOT NULL
16156                                 REFERENCES serial.issuance (id)
16157                                 ON DELETE CASCADE
16158                                 DEFERRABLE INITIALLY DEFERRED,
16159         stream          INT     NOT NULL
16160                                 REFERENCES serial.stream (id)
16161                                 ON DELETE CASCADE
16162                                 DEFERRABLE INITIALLY DEFERRED,
16163         unit            INT     REFERENCES serial.unit (id)
16164                                 ON DELETE SET NULL
16165                                 DEFERRABLE INITIALLY DEFERRED,
16166         uri             INT     REFERENCES asset.uri (id)
16167                                 ON DELETE SET NULL
16168                                 DEFERRABLE INITIALLY DEFERRED,
16169         date_expected   TIMESTAMP WITH TIME ZONE,
16170         date_received   TIMESTAMP WITH TIME ZONE,
16171         status          TEXT    CONSTRAINT valid_status CHECK (
16172                                status IN ( 'Bindery', 'Bound', 'Claimed', 'Discarded',
16173                                'Expected', 'Not Held', 'Not Published', 'Received'))
16174                             DEFAULT 'Expected',
16175         shadowed        BOOL    NOT NULL DEFAULT FALSE
16176 );
16177 CREATE INDEX serial_item_stream_idx ON serial.item (stream);
16178 CREATE INDEX serial_item_issuance_idx ON serial.item (issuance);
16179 CREATE INDEX serial_item_unit_idx ON serial.item (unit);
16180 CREATE INDEX serial_item_uri_idx ON serial.item (uri);
16181 CREATE INDEX serial_item_date_received_idx ON serial.item (date_received);
16182 CREATE INDEX serial_item_status_idx ON serial.item (status);
16183
16184 CREATE TABLE serial.item_note (
16185         id          SERIAL  PRIMARY KEY,
16186         item        INT     NOT NULL
16187                             REFERENCES serial.item (id)
16188                             ON DELETE CASCADE
16189                             DEFERRABLE INITIALLY DEFERRED,
16190         creator     INT     NOT NULL
16191                             REFERENCES actor.usr (id)
16192                             DEFERRABLE INITIALLY DEFERRED,
16193         create_date TIMESTAMP WITH TIME ZONE    DEFAULT NOW(),
16194         pub         BOOL    NOT NULL    DEFAULT FALSE,
16195         title       TEXT    NOT NULL,
16196         value       TEXT    NOT NULL
16197 );
16198 CREATE INDEX serial_item_note_item_idx ON serial.item_note (item);
16199
16200 CREATE TABLE serial.basic_summary (
16201         id                  SERIAL  PRIMARY KEY,
16202         distribution        INT     NOT NULL
16203                                     REFERENCES serial.distribution (id)
16204                                     ON DELETE CASCADE
16205                                     DEFERRABLE INITIALLY DEFERRED,
16206         generated_coverage  TEXT    NOT NULL,
16207         textual_holdings    TEXT,
16208         show_generated      BOOL    NOT NULL DEFAULT TRUE
16209 );
16210 CREATE INDEX serial_basic_summary_dist_idx ON serial.basic_summary (distribution);
16211
16212 CREATE TABLE serial.supplement_summary (
16213         id                  SERIAL  PRIMARY KEY,
16214         distribution        INT     NOT NULL
16215                                     REFERENCES serial.distribution (id)
16216                                     ON DELETE CASCADE
16217                                     DEFERRABLE INITIALLY DEFERRED,
16218         generated_coverage  TEXT    NOT NULL,
16219         textual_holdings    TEXT,
16220         show_generated      BOOL    NOT NULL DEFAULT TRUE
16221 );
16222 CREATE INDEX serial_supplement_summary_dist_idx ON serial.supplement_summary (distribution);
16223
16224 CREATE TABLE serial.index_summary (
16225         id                  SERIAL  PRIMARY KEY,
16226         distribution        INT     NOT NULL
16227                                     REFERENCES serial.distribution (id)
16228                                     ON DELETE CASCADE
16229                                     DEFERRABLE INITIALLY DEFERRED,
16230         generated_coverage  TEXT    NOT NULL,
16231         textual_holdings    TEXT,
16232         show_generated      BOOL    NOT NULL DEFAULT TRUE
16233 );
16234 CREATE INDEX serial_index_summary_dist_idx ON serial.index_summary (distribution);
16235
16236 -- 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.
16237
16238 DROP INDEX IF EXISTS authority.authority_record_unique_tcn;
16239 CREATE UNIQUE INDEX authority_record_unique_tcn ON authority.record_entry (arn_source,arn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16240
16241 DROP INDEX IF EXISTS asset.asset_call_number_label_once_per_lib;
16242 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;
16243
16244 DROP INDEX IF EXISTS biblio.biblio_record_unique_tcn;
16245 CREATE UNIQUE INDEX biblio_record_unique_tcn ON biblio.record_entry (tcn_value) WHERE deleted = FALSE OR deleted IS FALSE;
16246
16247 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_val INTERVAL )
16248 RETURNS INTEGER AS $$
16249 BEGIN
16250         RETURN EXTRACT( EPOCH FROM interval_val );
16251 END;
16252 $$ LANGUAGE plpgsql;
16253
16254 CREATE OR REPLACE FUNCTION config.interval_to_seconds( interval_string TEXT )
16255 RETURNS INTEGER AS $$
16256 BEGIN
16257         RETURN config.interval_to_seconds( interval_string::INTERVAL );
16258 END;
16259 $$ LANGUAGE plpgsql;
16260
16261 INSERT INTO container.biblio_record_entry_bucket_type( code, label ) VALUES (
16262     'temp',
16263     oils_i18n_gettext(
16264         'temp',
16265         'Temporary bucket which gets deleted after use.',
16266         'cbrebt',
16267         'label'
16268     )
16269 );
16270
16271 -- 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.
16272
16273 CREATE OR REPLACE FUNCTION biblio.check_marcxml_well_formed () RETURNS TRIGGER AS $func$
16274 BEGIN
16275
16276     IF xml_is_well_formed(NEW.marc) THEN
16277         RETURN NEW;
16278     ELSE
16279         RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
16280     END IF;
16281     
16282 END;
16283 $func$ LANGUAGE PLPGSQL;
16284
16285 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();
16286
16287 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();
16288
16289 ALTER TABLE serial.record_entry
16290         ALTER COLUMN marc DROP NOT NULL;
16291
16292 insert INTO CONFIG.xml_transform(name, namespace_uri, prefix, xslt)
16293 VALUES ('marc21expand880', 'http://www.loc.gov/MARC21/slim', 'marc', $$<?xml version="1.0" encoding="UTF-8"?>
16294 <xsl:stylesheet
16295     xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
16296     xmlns:marc="http://www.loc.gov/MARC21/slim"
16297     version="1.0">
16298 <!--
16299 Copyright (C) 2010  Equinox Software, Inc.
16300 Galen Charlton <gmc@esilibrary.cOM.
16301
16302 This program is free software; you can redistribute it and/or
16303 modify it under the terms of the GNU General Public License
16304 as published by the Free Software Foundation; either version 2
16305 of the License, or (at your option) any later version.
16306
16307 This program is distributed in the hope that it will be useful,
16308 but WITHOUT ANY WARRANTY; without even the implied warranty of
16309 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16310 GNU General Public License for more details.
16311
16312 marc21_expand_880.xsl - stylesheet used during indexing to
16313                         map alternative graphical representations
16314                         of MARC fields stored in 880 fields
16315                         to the corresponding tag name and value.
16316
16317 For example, if a MARC record for a Chinese book has
16318
16319 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16320 880.00 $6 245-01/$1 $a八十三年短篇小說選
16321
16322 this stylesheet will transform it to the equivalent of
16323
16324 245.00 $6 880-01 $a Ba shi san nian duan pian xiao shuo xuan
16325 245.00 $6 245-01/$1 $a八十三年短篇小說選
16326
16327 -->
16328     <xsl:output encoding="UTF-8" indent="yes" method="xml"/>
16329
16330     <xsl:template match="@*|node()">
16331         <xsl:copy>
16332             <xsl:apply-templates select="@*|node()"/>
16333         </xsl:copy>
16334     </xsl:template>
16335
16336     <xsl:template match="//marc:datafield[@tag='880']">
16337         <xsl:if test="./marc:subfield[@code='6'] and string-length(./marc:subfield[@code='6']) &gt;= 6">
16338             <marc:datafield>
16339                 <xsl:attribute name="tag">
16340                     <xsl:value-of select="substring(./marc:subfield[@code='6'], 1, 3)" />
16341                 </xsl:attribute>
16342                 <xsl:attribute name="ind1">
16343                     <xsl:value-of select="@ind1" />
16344                 </xsl:attribute>
16345                 <xsl:attribute name="ind2">
16346                     <xsl:value-of select="@ind2" />
16347                 </xsl:attribute>
16348                 <xsl:apply-templates />
16349             </marc:datafield>
16350         </xsl:if>
16351     </xsl:template>
16352     
16353 </xsl:stylesheet>$$);
16354
16355 -- fix broken prefix and namespace URI for the
16356 -- mods32 transform found in some databases
16357 -- that started out at version 1.2 or earlier
16358 UPDATE config.xml_transform
16359 SET namespace_uri = 'http://www.loc.gov/mods/v3'
16360 WHERE name = 'mods32'
16361 AND namespace_uri = 'http://www.loc.gov/mods/'
16362 AND xslt LIKE '%xmlns="http://www.loc.gov/mods/v3"%';
16363
16364 UPDATE config.xml_transform
16365 SET prefix = 'mods32'
16366 WHERE name = 'mods32'
16367 AND prefix = 'mods'
16368 AND EXISTS (SELECT xpath FROM config.metabib_field WHERE xpath ~ 'mods32:');
16369
16370 -- Splitting the ingest trigger up into little bits
16371
16372 CREATE TEMPORARY TABLE eg_0301_check_if_has_contents (
16373     flag INTEGER PRIMARY KEY
16374 ) ON COMMIT DROP;
16375 INSERT INTO eg_0301_check_if_has_contents VALUES (1);
16376
16377 -- cause failure if either of the tables we want to drop have rows
16378 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency LIMIT 1;
16379 INSERT INTO eg_0301_check_if_has_contents SELECT 1 FROM asset.copy_transparency_map LIMIT 1;
16380
16381 DROP TABLE IF EXISTS asset.copy_transparency_map;
16382 DROP TABLE IF EXISTS asset.copy_transparency;
16383
16384 UPDATE config.metabib_field SET facet_xpath = '//' || facet_xpath WHERE facet_xpath IS NOT NULL;
16385
16386 -- We won't necessarily use all of these, but they are here for completeness.
16387 -- Source is the EDI spec 1229 codelist, eg: http://www.stylusstudio.com/edifact/D04B/1229.htm
16388 -- Values are the EDI code value + 1000
16389
16390 INSERT INTO acq.cancel_reason (keep_debits, id, org_unit, label, description) VALUES 
16391 ('t',(  1+1000), 1, 'Added',     'The information is to be or has been added.'),
16392 ('f',(  2+1000), 1, 'Deleted',   'The information is to be or has been deleted.'),
16393 ('t',(  3+1000), 1, 'Changed',   'The information is to be or has been changed.'),
16394 ('t',(  4+1000), 1, 'No action',                  'This line item is not affected by the actual message.'),
16395 ('t',(  5+1000), 1, 'Accepted without amendment', 'This line item is entirely accepted by the seller.'),
16396 ('t',(  6+1000), 1, 'Accepted with amendment',    'This line item is accepted but amended by the seller.'),
16397 ('f',(  7+1000), 1, 'Not accepted',               'This line item is not accepted by the seller.'),
16398 ('t',(  8+1000), 1, 'Schedule only', 'Code specifying that the message is a schedule only.'),
16399 ('t',(  9+1000), 1, 'Amendments',    'Code specifying that amendments are requested/notified.'),
16400 ('f',( 10+1000), 1, 'Not found',   'This line item is not found in the referenced message.'),
16401 ('t',( 11+1000), 1, 'Not amended', 'This line is not amended by the buyer.'),
16402 ('t',( 12+1000), 1, 'Line item numbers changed', 'Code specifying that the line item numbers have changed.'),
16403 ('t',( 13+1000), 1, 'Buyer has deducted amount', 'Buyer has deducted amount from payment.'),
16404 ('t',( 14+1000), 1, 'Buyer claims against invoice', 'Buyer has a claim against an outstanding invoice.'),
16405 ('t',( 15+1000), 1, 'Charge back by seller', 'Factor has been requested to charge back the outstanding item.'),
16406 ('t',( 16+1000), 1, 'Seller will issue credit note', 'Seller agrees to issue a credit note.'),
16407 ('t',( 17+1000), 1, 'Terms changed for new terms', 'New settlement terms have been agreed.'),
16408 ('t',( 18+1000), 1, 'Abide outcome of negotiations', 'Factor agrees to abide by the outcome of negotiations between seller and buyer.'),
16409 ('t',( 19+1000), 1, 'Seller rejects dispute', 'Seller does not accept validity of dispute.'),
16410 ('t',( 20+1000), 1, 'Settlement', 'The reported situation is settled.'),
16411 ('t',( 21+1000), 1, 'No delivery', 'Code indicating that no delivery will be required.'),
16412 ('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).'),
16413 ('t',( 23+1000), 1, 'Proposed amendment', 'A code used to indicate an amendment suggested by the sender.'),
16414 ('t',( 24+1000), 1, 'Accepted with amendment, no confirmation required', 'Accepted with changes which require no confirmation.'),
16415 ('t',( 25+1000), 1, 'Equipment provisionally repaired', 'The equipment or component has been provisionally repaired.'),
16416 ('t',( 26+1000), 1, 'Included', 'Code indicating that the entity is included.'),
16417 ('t',( 27+1000), 1, 'Verified documents for coverage', 'Upon receipt and verification of documents we shall cover you when due as per your instructions.'),
16418 ('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.'),
16419 ('t',( 29+1000), 1, 'Authenticated advice for coverage',      'On receipt of your authenticated advice we shall cover you when due as per your instructions.'),
16420 ('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.'),
16421 ('t',( 31+1000), 1, 'Authenticated advice for credit',        'On receipt of your authenticated advice we shall credit your account with us when due.'),
16422 ('t',( 32+1000), 1, 'Credit advice requested for direct debit',           'A credit advice is requested for the direct debit.'),
16423 ('t',( 33+1000), 1, 'Credit advice and acknowledgement for direct debit', 'A credit advice and acknowledgement are requested for the direct debit.'),
16424 ('t',( 34+1000), 1, 'Inquiry',     'Request for information.'),
16425 ('t',( 35+1000), 1, 'Checked',     'Checked.'),
16426 ('t',( 36+1000), 1, 'Not checked', 'Not checked.'),
16427 ('f',( 37+1000), 1, 'Cancelled',   'Discontinued.'),
16428 ('t',( 38+1000), 1, 'Replaced',    'Provide a replacement.'),
16429 ('t',( 39+1000), 1, 'New',         'Not existing before.'),
16430 ('t',( 40+1000), 1, 'Agreed',      'Consent.'),
16431 ('t',( 41+1000), 1, 'Proposed',    'Put forward for consideration.'),
16432 ('t',( 42+1000), 1, 'Already delivered', 'Delivery has taken place.'),
16433 ('t',( 43+1000), 1, 'Additional subordinate structures will follow', 'Additional subordinate structures will follow the current hierarchy level.'),
16434 ('t',( 44+1000), 1, 'Additional subordinate structures will not follow', 'No additional subordinate structures will follow the current hierarchy level.'),
16435 ('t',( 45+1000), 1, 'Result opposed',         'A notification that the result is opposed.'),
16436 ('t',( 46+1000), 1, 'Auction held',           'A notification that an auction was held.'),
16437 ('t',( 47+1000), 1, 'Legal action pursued',   'A notification that legal action has been pursued.'),
16438 ('t',( 48+1000), 1, 'Meeting held',           'A notification that a meeting was held.'),
16439 ('t',( 49+1000), 1, 'Result set aside',       'A notification that the result has been set aside.'),
16440 ('t',( 50+1000), 1, 'Result disputed',        'A notification that the result has been disputed.'),
16441 ('t',( 51+1000), 1, 'Countersued',            'A notification that a countersuit has been filed.'),
16442 ('t',( 52+1000), 1, 'Pending',                'A notification that an action is awaiting settlement.'),
16443 ('f',( 53+1000), 1, 'Court action dismissed', 'A notification that a court action will no longer be heard.'),
16444 ('t',( 54+1000), 1, 'Referred item, accepted', 'The item being referred to has been accepted.'),
16445 ('f',( 55+1000), 1, 'Referred item, rejected', 'The item being referred to has been rejected.'),
16446 ('t',( 56+1000), 1, 'Debit advice statement line',  'Notification that the statement line is a debit advice.'),
16447 ('t',( 57+1000), 1, 'Credit advice statement line', 'Notification that the statement line is a credit advice.'),
16448 ('t',( 58+1000), 1, 'Grouped credit advices',       'Notification that the credit advices are grouped.'),
16449 ('t',( 59+1000), 1, 'Grouped debit advices',        'Notification that the debit advices are grouped.'),
16450 ('t',( 60+1000), 1, 'Registered', 'The name is registered.'),
16451 ('f',( 61+1000), 1, 'Payment denied', 'The payment has been denied.'),
16452 ('t',( 62+1000), 1, 'Approved as amended', 'Approved with modifications.'),
16453 ('t',( 63+1000), 1, 'Approved as submitted', 'The request has been approved as submitted.'),
16454 ('f',( 64+1000), 1, 'Cancelled, no activity', 'Cancelled due to the lack of activity.'),
16455 ('t',( 65+1000), 1, 'Under investigation', 'Investigation is being done.'),
16456 ('t',( 66+1000), 1, 'Initial claim received', 'Notification that the initial claim was received.'),
16457 ('f',( 67+1000), 1, 'Not in process', 'Not in process.'),
16458 ('f',( 68+1000), 1, 'Rejected, duplicate', 'Rejected because it is a duplicate.'),
16459 ('f',( 69+1000), 1, 'Rejected, resubmit with corrections', 'Rejected but may be resubmitted when corrected.'),
16460 ('t',( 70+1000), 1, 'Pending, incomplete', 'Pending because of incomplete information.'),
16461 ('t',( 71+1000), 1, 'Under field office investigation', 'Investigation by the field is being done.'),
16462 ('t',( 72+1000), 1, 'Pending, awaiting additional material', 'Pending awaiting receipt of additional material.'),
16463 ('t',( 73+1000), 1, 'Pending, awaiting review', 'Pending while awaiting review.'),
16464 ('t',( 74+1000), 1, 'Reopened', 'Opened again.'),
16465 ('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).'),
16466 ('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).'),
16467 ('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).'),
16468 ('t',( 78+1000), 1, 'Previous payment decision reversed', 'A previous payment decision has been reversed.'),
16469 ('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).'),
16470 ('t',( 80+1000), 1, 'Transferred to correct insurance carrier', 'The request has been transferred to the correct insurance carrier for processing.'),
16471 ('t',( 81+1000), 1, 'Not paid, predetermination pricing only', 'Payment has not been made and the enclosed response is predetermination pricing only.'),
16472 ('t',( 82+1000), 1, 'Documentation claim', 'The claim is for documentation purposes only, no payment required.'),
16473 ('t',( 83+1000), 1, 'Reviewed', 'Assessed.'),
16474 ('f',( 84+1000), 1, 'Repriced', 'This price was changed.'),
16475 ('t',( 85+1000), 1, 'Audited', 'An official examination has occurred.'),
16476 ('t',( 86+1000), 1, 'Conditionally paid', 'Payment has been conditionally made.'),
16477 ('t',( 87+1000), 1, 'On appeal', 'Reconsideration of the decision has been applied for.'),
16478 ('t',( 88+1000), 1, 'Closed', 'Shut.'),
16479 ('t',( 89+1000), 1, 'Reaudited', 'A subsequent official examination has occurred.'),
16480 ('t',( 90+1000), 1, 'Reissued', 'Issued again.'),
16481 ('t',( 91+1000), 1, 'Closed after reopening', 'Reopened and then closed.'),
16482 ('t',( 92+1000), 1, 'Redetermined', 'Determined again or differently.'),
16483 ('t',( 93+1000), 1, 'Processed as primary',   'Processed as the first.'),
16484 ('t',( 94+1000), 1, 'Processed as secondary', 'Processed as the second.'),
16485 ('t',( 95+1000), 1, 'Processed as tertiary',  'Processed as the third.'),
16486 ('t',( 96+1000), 1, 'Correction of error', 'A correction to information previously communicated which contained an error.'),
16487 ('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.'),
16488 ('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.'),
16489 ('t',( 99+1000), 1, 'Interim response', 'The response is an interim one.'),
16490 ('t',(100+1000), 1, 'Final response',   'The response is an final one.'),
16491 ('t',(101+1000), 1, 'Debit advice requested', 'A debit advice is requested for the transaction.'),
16492 ('t',(102+1000), 1, 'Transaction not impacted', 'Advice that the transaction is not impacted.'),
16493 ('t',(103+1000), 1, 'Patient to be notified',                    'The action to take is to notify the patient.'),
16494 ('t',(104+1000), 1, 'Healthcare provider to be notified',        'The action to take is to notify the healthcare provider.'),
16495 ('t',(105+1000), 1, 'Usual general practitioner to be notified', 'The action to take is to notify the usual general practitioner.'),
16496 ('t',(106+1000), 1, 'Advice without details', 'An advice without details is requested or notified.'),
16497 ('t',(107+1000), 1, 'Advice with details', 'An advice with details is requested or notified.'),
16498 ('t',(108+1000), 1, 'Amendment requested', 'An amendment is requested.'),
16499 ('t',(109+1000), 1, 'For information', 'Included for information only.'),
16500 ('f',(110+1000), 1, 'Withdraw', 'A code indicating discontinuance or retraction.'),
16501 ('t',(111+1000), 1, 'Delivery date change', 'The action / notiification is a change of the delivery date.'),
16502 ('f',(112+1000), 1, 'Quantity change',      'The action / notification is a change of quantity.'),
16503 ('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.'),
16504 ('t',(114+1000), 1, 'Resale',           'The identified items have been sold by the distributor to the end customer.'),
16505 ('t',(115+1000), 1, 'Prior addition', 'This existing line item becomes available at an earlier date.');
16506
16507 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath, facet_field, search_field ) VALUES
16508     (26, 'identifier', 'arcn', oils_i18n_gettext(26, 'Authority record control number', 'cmf', 'label'), 'marcxml', $$//marc:subfield[@code='0']$$, TRUE, FALSE );
16509  
16510 SELECT SETVAL('config.metabib_field_id_seq'::TEXT, (SELECT MAX(id) FROM config.metabib_field), TRUE);
16511  
16512 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16513         'Remove Parenthesized Substring',
16514         'Remove any parenthesized substrings from the extracted text, such as the agency code preceding authority record control numbers in subfield 0.',
16515         'remove_paren_substring',
16516         0
16517 );
16518
16519 INSERT INTO config.index_normalizer (name, description, func, param_count) VALUES (
16520         'Trim Surrounding Space',
16521         'Trim leading and trailing spaces from extracted text.',
16522         'btrim',
16523         0
16524 );
16525
16526 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16527     SELECT  m.id,
16528             i.id,
16529             -2
16530       FROM  config.metabib_field m,
16531             config.index_normalizer i
16532       WHERE i.func IN ('remove_paren_substring')
16533             AND m.id IN (26);
16534
16535 INSERT INTO config.metabib_field_index_norm_map (field,norm,pos)
16536     SELECT  m.id,
16537             i.id,
16538             -1
16539       FROM  config.metabib_field m,
16540             config.index_normalizer i
16541       WHERE i.func IN ('btrim')
16542             AND m.id IN (26);
16543
16544 -- Function that takes, and returns, marcxml and compiles an embedded ruleset for you, and they applys it
16545 CREATE OR REPLACE FUNCTION vandelay.merge_record_xml ( target_marc TEXT, template_marc TEXT ) RETURNS TEXT AS $$
16546 DECLARE
16547     dyn_profile     vandelay.compile_profile%ROWTYPE;
16548     replace_rule    TEXT;
16549     tmp_marc        TEXT;
16550     trgt_marc        TEXT;
16551     tmpl_marc        TEXT;
16552     match_count     INT;
16553 BEGIN
16554
16555     IF target_marc IS NULL OR template_marc IS NULL THEN
16556         -- RAISE NOTICE 'no marc for target or template record';
16557         RETURN NULL;
16558     END IF;
16559
16560     dyn_profile := vandelay.compile_profile( template_marc );
16561
16562     IF dyn_profile.replace_rule <> '' AND dyn_profile.preserve_rule <> '' THEN
16563         -- RAISE NOTICE 'both replace [%] and preserve [%] specified', dyn_profile.replace_rule, dyn_profile.preserve_rule;
16564         RETURN NULL;
16565     END IF;
16566
16567     IF dyn_profile.replace_rule <> '' THEN
16568         trgt_marc = target_marc;
16569         tmpl_marc = template_marc;
16570         replace_rule = dyn_profile.replace_rule;
16571     ELSE
16572         tmp_marc = target_marc;
16573         trgt_marc = template_marc;
16574         tmpl_marc = tmp_marc;
16575         replace_rule = dyn_profile.preserve_rule;
16576     END IF;
16577
16578     RETURN vandelay.merge_record_xml( trgt_marc, tmpl_marc, dyn_profile.add_rule, replace_rule, dyn_profile.strip_rule );
16579
16580 END;
16581 $$ LANGUAGE PLPGSQL;
16582
16583 -- Function to generate an ephemeral overlay template from an authority record
16584 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT, BIGINT ) RETURNS TEXT AS $func$
16585
16586     use MARC::Record;
16587     use MARC::File::XML (BinaryEncoding => 'UTF-8');
16588
16589     my $xml = shift;
16590     my $r = MARC::Record->new_from_xml( $xml );
16591
16592     return undef unless ($r);
16593
16594     my $id = shift() || $r->subfield( '901' => 'c' );
16595     $id =~ s/^\s*(?:\([^)]+\))?\s*(.+)\s*?$/$1/;
16596     return undef unless ($id); # We need an ID!
16597
16598     my $tmpl = MARC::Record->new();
16599     $tmpl->encoding( 'UTF-8' );
16600
16601     my @rule_fields;
16602     for my $field ( $r->field( '1..' ) ) { # Get main entry fields from the authority record
16603
16604         my $tag = $field->tag;
16605         my $i1 = $field->indicator(1);
16606         my $i2 = $field->indicator(2);
16607         my $sf = join '', map { $_->[0] } $field->subfields;
16608         my @data = map { @$_ } $field->subfields;
16609
16610         my @replace_them;
16611
16612         # Map the authority field to bib fields it can control.
16613         if ($tag >= 100 and $tag <= 111) {       # names
16614             @replace_them = map { $tag + $_ } (0, 300, 500, 600, 700);
16615         } elsif ($tag eq '130') {                # uniform title
16616             @replace_them = qw/130 240 440 730 830/;
16617         } elsif ($tag >= 150 and $tag <= 155) {  # subjects
16618             @replace_them = ($tag + 500);
16619         } elsif ($tag >= 180 and $tag <= 185) {  # floating subdivisions
16620             @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/;
16621         } else {
16622             next;
16623         }
16624
16625         # Dummy up the bib-side data
16626         $tmpl->append_fields(
16627             map {
16628                 MARC::Field->new( $_, $i1, $i2, @data )
16629             } @replace_them
16630         );
16631
16632         # Construct some 'replace' rules
16633         push @rule_fields, map { $_ . $sf . '[0~\)' .$id . '$]' } @replace_them;
16634     }
16635
16636     # Insert the replace rules into the template
16637     $tmpl->append_fields(
16638         MARC::Field->new( '905' => ' ' => ' ' => 'r' => join(',', @rule_fields ) )
16639     );
16640
16641     $xml = $tmpl->as_xml_record;
16642     $xml =~ s/^<\?.+?\?>$//mo;
16643     $xml =~ s/\n//sgo;
16644     $xml =~ s/>\s+</></sgo;
16645
16646     return $xml;
16647
16648 $func$ LANGUAGE PLPERLU;
16649
16650 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
16651     SELECT authority.generate_overlay_template( marc, id ) FROM authority.record_entry WHERE id = $1;
16652 $func$ LANGUAGE SQL;
16653
16654 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( TEXT ) RETURNS TEXT AS $func$
16655     SELECT authority.generate_overlay_template( $1, NULL );
16656 $func$ LANGUAGE SQL;
16657
16658 DELETE FROM config.metabib_field_index_norm_map WHERE field = 26;
16659 DELETE FROM config.metabib_field WHERE id = 26;
16660
16661 -- Making this a global_flag (UI accessible) instead of an internal_flag
16662 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16663     VALUES (
16664         'ingest.disable_authority_linking',
16665         oils_i18n_gettext(
16666             'ingest.disable_authority_linking',
16667             'Authority Automation: Disable bib-authority link tracking',
16668             'cgf', 
16669             'label'
16670         )
16671     );
16672 UPDATE config.global_flag SET enabled = (SELECT enabled FROM ONLY config.internal_flag WHERE name = 'ingest.disable_authority_linking');
16673 DELETE FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking';
16674
16675 INSERT INTO config.global_flag (name, label) -- defaults to enabled=FALSE
16676     VALUES (
16677         'ingest.disable_authority_auto_update',
16678         oils_i18n_gettext(
16679             'ingest.disable_authority_auto_update',
16680             'Authority Automation: Disable automatic authority updating (requires link tracking)',
16681             'cgf', 
16682             'label'
16683         )
16684     );
16685
16686 -- Enable automated ingest of authority records; just insert the row into
16687 -- authority.record_entry and authority.full_rec will automatically be populated
16688
16689 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT, bid BIGINT) RETURNS BIGINT AS $func$
16690     UPDATE  biblio.record_entry
16691       SET   marc = vandelay.merge_record_xml( marc, authority.generate_overlay_template( $1 ) )
16692       WHERE id = $2;
16693     SELECT $1;
16694 $func$ LANGUAGE SQL;
16695
16696 CREATE OR REPLACE FUNCTION authority.propagate_changes (aid BIGINT) RETURNS SETOF BIGINT AS $func$
16697     SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
16698 $func$ LANGUAGE SQL;
16699
16700 CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
16701
16702 use MARC::Record;
16703 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16704
16705 my $xml = shift;
16706 my $r = MARC::Record->new_from_xml( $xml );
16707
16708 return_next( { tag => 'LDR', value => $r->leader } );
16709
16710 for my $f ( $r->fields ) {
16711     if ($f->is_control_field) {
16712         return_next({ tag => $f->tag, value => $f->data });
16713     } else {
16714         for my $s ($f->subfields) {
16715             return_next({
16716                 tag      => $f->tag,
16717                 ind1     => $f->indicator(1),
16718                 ind2     => $f->indicator(2),
16719                 subfield => $s->[0],
16720                 value    => $s->[1]
16721             });
16722
16723         }
16724     }
16725 }
16726
16727 return undef;
16728
16729 $func$ LANGUAGE PLPERLU;
16730
16731 CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
16732 DECLARE
16733     auth    authority.record_entry%ROWTYPE;
16734     output    authority.full_rec%ROWTYPE;
16735     field    RECORD;
16736 BEGIN
16737     SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
16738
16739     FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
16740         output.record := rid;
16741         output.ind1 := field.ind1;
16742         output.ind2 := field.ind2;
16743         output.tag := field.tag;
16744         output.subfield := field.subfield;
16745         IF field.subfield IS NOT NULL THEN
16746             output.value := naco_normalize(field.value, field.subfield);
16747         ELSE
16748             output.value := field.value;
16749         END IF;
16750
16751         CONTINUE WHEN output.value IS NULL;
16752
16753         RETURN NEXT output;
16754     END LOOP;
16755 END;
16756 $func$ LANGUAGE PLPGSQL;
16757
16758 -- authority.rec_descriptor appears to be unused currently
16759 CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
16760 BEGIN
16761     DELETE FROM authority.rec_descriptor WHERE record = auth_id;
16762 --    INSERT INTO authority.rec_descriptor (record, record_status, char_encoding)
16763 --        SELECT  auth_id, ;
16764
16765     RETURN;
16766 END;
16767 $func$ LANGUAGE PLPGSQL;
16768
16769 CREATE OR REPLACE FUNCTION authority.reingest_authority_full_rec( auth_id BIGINT ) RETURNS VOID AS $func$
16770 BEGIN
16771     DELETE FROM authority.full_rec WHERE record = auth_id;
16772     INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
16773         SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );
16774
16775     RETURN;
16776 END;
16777 $func$ LANGUAGE PLPGSQL;
16778
16779 -- AFTER UPDATE OR INSERT trigger for authority.record_entry
16780 CREATE OR REPLACE FUNCTION authority.indexing_ingest_or_delete () RETURNS TRIGGER AS $func$
16781 BEGIN
16782
16783     IF NEW.deleted IS TRUE THEN -- If this authority is deleted
16784         DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
16785         DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
16786           -- Should remove matching $0 from controlled fields at the same time?
16787         RETURN NEW; -- and we're done
16788     END IF;
16789
16790     IF TG_OP = 'UPDATE' THEN -- re-ingest?
16791         PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;
16792
16793         IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
16794             RETURN NEW;
16795         END IF;
16796     END IF;
16797
16798     -- Flatten and insert the afr data
16799     PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
16800     IF NOT FOUND THEN
16801         PERFORM authority.reingest_authority_full_rec(NEW.id);
16802 -- authority.rec_descriptor is not currently used
16803 --        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
16804 --        IF NOT FOUND THEN
16805 --            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
16806 --        END IF;
16807     END IF;
16808
16809     RETURN NEW;
16810 END;
16811 $func$ LANGUAGE PLPGSQL;
16812
16813 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 ();
16814
16815 -- Some records manage to get XML namespace declarations into each element,
16816 -- like <datafield xmlns:marc="http://www.loc.gov/MARC21/slim"
16817 -- This broke the old maintain_901(), so we'll make the regex more robust
16818
16819 CREATE OR REPLACE FUNCTION maintain_901 () RETURNS TRIGGER AS $func$
16820 BEGIN
16821     -- Remove any existing 901 fields before we insert the authoritative one
16822     NEW.marc := REGEXP_REPLACE(NEW.marc, E'<datafield\s*[^<>]*?\s*tag="901".+?</datafield>', '', 'g');
16823     IF TG_TABLE_SCHEMA = 'biblio' THEN
16824         NEW.marc := REGEXP_REPLACE(
16825             NEW.marc,
16826             E'(</(?:[^:]*?:)?record>)',
16827             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16828                 '<subfield code="a">' || NEW.tcn_value || E'</subfield>' ||
16829                 '<subfield code="b">' || NEW.tcn_source || E'</subfield>' ||
16830                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16831                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16832                 CASE WHEN NEW.owner IS NOT NULL THEN '<subfield code="o">' || NEW.owner || E'</subfield>' ELSE '' END ||
16833                 CASE WHEN NEW.share_depth IS NOT NULL THEN '<subfield code="d">' || NEW.share_depth || E'</subfield>' ELSE '' END ||
16834              E'</datafield>\\1'
16835         );
16836     ELSIF TG_TABLE_SCHEMA = 'authority' THEN
16837         NEW.marc := REGEXP_REPLACE(
16838             NEW.marc,
16839             E'(</(?:[^:]*?:)?record>)',
16840             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16841                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16842                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16843              E'</datafield>\\1'
16844         );
16845     ELSIF TG_TABLE_SCHEMA = 'serial' THEN
16846         NEW.marc := REGEXP_REPLACE(
16847             NEW.marc,
16848             E'(</(?:[^:]*?:)?record>)',
16849             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16850                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16851                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16852                 '<subfield code="o">' || NEW.owning_lib || E'</subfield>' ||
16853                 CASE WHEN NEW.record IS NOT NULL THEN '<subfield code="r">' || NEW.record || E'</subfield>' ELSE '' END ||
16854              E'</datafield>\\1'
16855         );
16856     ELSE
16857         NEW.marc := REGEXP_REPLACE(
16858             NEW.marc,
16859             E'(</(?:[^:]*?:)?record>)',
16860             E'<datafield tag="901" ind1=" " ind2=" ">' ||
16861                 '<subfield code="c">' || NEW.id || E'</subfield>' ||
16862                 '<subfield code="t">' || TG_TABLE_SCHEMA || E'</subfield>' ||
16863              E'</datafield>\\1'
16864         );
16865     END IF;
16866
16867     RETURN NEW;
16868 END;
16869 $func$ LANGUAGE PLPGSQL;
16870
16871 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16872 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16873 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_901();
16874  
16875 -- In booking, elbow room defines:
16876 --  a) how far in the future you must make a reservation on a given item if
16877 --      that item will have to transit somewhere to fulfill the reservation.
16878 --  b) how soon a reservation must be starting for the reserved item to
16879 --      be op-captured by the checkin interface.
16880
16881 -- We don't want to clobber any default_elbow room at any level:
16882
16883 CREATE OR REPLACE FUNCTION pg_temp.default_elbow() RETURNS INTEGER AS $$
16884 DECLARE
16885     existing    actor.org_unit_setting%ROWTYPE;
16886 BEGIN
16887     SELECT INTO existing id FROM actor.org_unit_setting WHERE name = 'circ.booking_reservation.default_elbow_room';
16888     IF NOT FOUND THEN
16889         INSERT INTO actor.org_unit_setting (org_unit, name, value) VALUES (
16890             (SELECT id FROM actor.org_unit WHERE parent_ou IS NULL),
16891             'circ.booking_reservation.default_elbow_room',
16892             '"1 day"'
16893         );
16894         RETURN 1;
16895     END IF;
16896     RETURN 0;
16897 END;
16898 $$ LANGUAGE plpgsql;
16899
16900 SELECT pg_temp.default_elbow();
16901
16902 DROP FUNCTION IF EXISTS action.usr_visible_circ_copies( INTEGER );
16903
16904 -- returns the distinct set of target copy IDs from a user's visible circulation history
16905 CREATE OR REPLACE FUNCTION action.usr_visible_circ_copies( INTEGER ) RETURNS SETOF BIGINT AS $$
16906     SELECT DISTINCT(target_copy) FROM action.usr_visible_circs($1)
16907 $$ LANGUAGE SQL;
16908
16909 ALTER TABLE action.in_house_use DROP CONSTRAINT in_house_use_item_fkey;
16910 ALTER TABLE action.transit_copy DROP CONSTRAINT transit_copy_target_copy_fkey;
16911 ALTER TABLE action.hold_transit_copy DROP CONSTRAINT ahtc_tc_fkey;
16912 ALTER TABLE action.hold_copy_map DROP CONSTRAINT hold_copy_map_target_copy_fkey;
16913
16914 ALTER TABLE asset.stat_cat_entry_copy_map DROP CONSTRAINT a_sc_oc_fkey;
16915
16916 ALTER TABLE authority.record_entry ADD COLUMN owner INT;
16917 ALTER TABLE serial.record_entry ADD COLUMN owner INT;
16918
16919 INSERT INTO config.global_flag (name, label, enabled)
16920     VALUES (
16921         'cat.maintain_control_numbers',
16922         oils_i18n_gettext(
16923             'cat.maintain_control_numbers',
16924             'Cat: Maintain 001/003/035 according to the MARC21 specification',
16925             'cgf', 
16926             'label'
16927         ),
16928         TRUE
16929     );
16930
16931 INSERT INTO config.global_flag (name, label, enabled)
16932     VALUES (
16933         'circ.holds.empty_issuance_ok',
16934         oils_i18n_gettext(
16935             'circ.holds.empty_issuance_ok',
16936             'Holds: Allow holds on empty issuances',
16937             'cgf',
16938             'label'
16939         ),
16940         TRUE
16941     );
16942
16943 INSERT INTO config.global_flag (name, label, enabled)
16944     VALUES (
16945         'circ.holds.usr_not_requestor',
16946         oils_i18n_gettext(
16947             'circ.holds.usr_not_requestor',
16948             'Holds: When testing hold matrix matchpoints, use the profile group of the receiving user instead of that of the requestor (affects staff-placed holds)',
16949             'cgf',
16950             'label'
16951         ),
16952         TRUE
16953     );
16954
16955 CREATE OR REPLACE FUNCTION maintain_control_numbers() RETURNS TRIGGER AS $func$
16956 use strict;
16957 use MARC::Record;
16958 use MARC::File::XML (BinaryEncoding => 'UTF-8');
16959 use Encode;
16960 use Unicode::Normalize;
16961
16962 my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
16963 my $schema = $_TD->{table_schema};
16964 my $rec_id = $_TD->{new}{id};
16965
16966 # Short-circuit if maintaining control numbers per MARC21 spec is not enabled
16967 my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
16968 if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
16969     return;
16970 }
16971
16972 # Get the control number identifier from an OU setting based on $_TD->{new}{owner}
16973 my $ou_cni = 'EVRGRN';
16974
16975 my $owner;
16976 if ($schema eq 'serial') {
16977     $owner = $_TD->{new}{owning_lib};
16978 } else {
16979     # are.owner and bre.owner can be null, so fall back to the consortial setting
16980     $owner = $_TD->{new}{owner} || 1;
16981 }
16982
16983 my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
16984 if ($ous_rv->{processed}) {
16985     $ou_cni = $ous_rv->{rows}[0]->{value};
16986     $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
16987 } else {
16988     # Fall back to the shortname of the OU if there was no OU setting
16989     $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
16990     if ($ous_rv->{processed}) {
16991         $ou_cni = $ous_rv->{rows}[0]->{shortname};
16992     }
16993 }
16994
16995 my ($create, $munge) = (0, 0);
16996
16997 my @scns = $record->field('035');
16998
16999 foreach my $id_field ('001', '003') {
17000     my $spec_value;
17001     my @controls = $record->field($id_field);
17002
17003     if ($id_field eq '001') {
17004         $spec_value = $rec_id;
17005     } else {
17006         $spec_value = $ou_cni;
17007     }
17008
17009     # Create the 001/003 if none exist
17010     if (scalar(@controls) == 1) {
17011         # Only one field; check to see if we need to munge it
17012         unless (grep $_->data() eq $spec_value, @controls) {
17013             $munge = 1;
17014         }
17015     } else {
17016         # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
17017         foreach my $control (@controls) {
17018             unless ($control->data() eq $spec_value) {
17019                 $record->delete_field($control);
17020             }
17021         }
17022         $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
17023         $create = 1;
17024     }
17025 }
17026
17027 # Now, if we need to munge the 001, we will first push the existing 001/003
17028 # into the 035; but if the record did not have one (and one only) 001 and 003
17029 # to begin with, skip this process
17030 if ($munge and not $create) {
17031     my $scn = "(" . $record->field('003')->data() . ")" . $record->field('001')->data();
17032
17033     # Do not create duplicate 035 fields
17034     unless (grep $_->subfield('a') eq $scn, @scns) {
17035         $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
17036     }
17037 }
17038
17039 # Set the 001/003 and update the MARC
17040 if ($create or $munge) {
17041     $record->field('001')->data($rec_id);
17042     $record->field('003')->data($ou_cni);
17043
17044     my $xml = $record->as_xml_record();
17045     $xml =~ s/\n//sgo;
17046     $xml =~ s/^<\?xml.+\?\s*>//go;
17047     $xml =~ s/>\s+</></go;
17048     $xml =~ s/\p{Cc}//go;
17049
17050     # Embed a version of OpenILS::Application::AppUtils->entityize()
17051     # to avoid having to set PERL5LIB for PostgreSQL as well
17052
17053     # If we are going to convert non-ASCII characters to XML entities,
17054     # we had better be dealing with a UTF8 string to begin with
17055     $xml = decode_utf8($xml);
17056
17057     $xml = NFC($xml);
17058
17059     # Convert raw ampersands to entities
17060     $xml =~ s/&(?!\S+;)/&amp;/gso;
17061
17062     # Convert Unicode characters to entities
17063     $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
17064
17065     $xml =~ s/[\x00-\x1f]//go;
17066     $_TD->{new}{marc} = $xml;
17067
17068     return "MODIFY";
17069 }
17070
17071 return;
17072 $func$ LANGUAGE PLPERLU;
17073
17074 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17075 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON biblio.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17076 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON serial.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
17077
17078 INSERT INTO metabib.facet_entry (source, field, value)
17079     SELECT source, field, value FROM (
17080         SELECT * FROM metabib.author_field_entry
17081             UNION ALL
17082         SELECT * FROM metabib.keyword_field_entry
17083             UNION ALL
17084         SELECT * FROM metabib.identifier_field_entry
17085             UNION ALL
17086         SELECT * FROM metabib.title_field_entry
17087             UNION ALL
17088         SELECT * FROM metabib.subject_field_entry
17089             UNION ALL
17090         SELECT * FROM metabib.series_field_entry
17091         )x
17092     WHERE x.index_vector = '';
17093         
17094 DELETE FROM metabib.author_field_entry WHERE index_vector = '';
17095 DELETE FROM metabib.keyword_field_entry WHERE index_vector = '';
17096 DELETE FROM metabib.identifier_field_entry WHERE index_vector = '';
17097 DELETE FROM metabib.title_field_entry WHERE index_vector = '';
17098 DELETE FROM metabib.subject_field_entry WHERE index_vector = '';
17099 DELETE FROM metabib.series_field_entry WHERE index_vector = '';
17100
17101 CREATE INDEX metabib_facet_entry_field_idx ON metabib.facet_entry (field);
17102 CREATE INDEX metabib_facet_entry_value_idx ON metabib.facet_entry (SUBSTRING(value,1,1024));
17103 CREATE INDEX metabib_facet_entry_source_idx ON metabib.facet_entry (source);
17104
17105 -- copy OPAC visibility materialized view
17106 CREATE OR REPLACE FUNCTION asset.refresh_opac_visible_copies_mat_view () RETURNS VOID AS $$
17107
17108     TRUNCATE TABLE asset.opac_visible_copies;
17109
17110     INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17111     SELECT  cp.id, cp.circ_lib, cn.record
17112     FROM  asset.copy cp
17113         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17114         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17115         JOIN asset.copy_location cl ON (cp.location = cl.id)
17116         JOIN config.copy_status cs ON (cp.status = cs.id)
17117         JOIN biblio.record_entry b ON (cn.record = b.id)
17118     WHERE NOT cp.deleted
17119         AND NOT cn.deleted
17120         AND NOT b.deleted
17121         AND cs.opac_visible
17122         AND cl.opac_visible
17123         AND cp.opac_visible
17124         AND a.opac_visible;
17125
17126 $$ LANGUAGE SQL;
17127 COMMENT ON FUNCTION asset.refresh_opac_visible_copies_mat_view() IS $$
17128 Rebuild the copy OPAC visibility cache.  Useful during migrations.
17129 $$;
17130
17131 -- and actually populate the table
17132 SELECT asset.refresh_opac_visible_copies_mat_view();
17133
17134 CREATE OR REPLACE FUNCTION asset.cache_copy_visibility () RETURNS TRIGGER as $func$
17135 DECLARE
17136     add_query       TEXT;
17137     remove_query    TEXT;
17138     do_add          BOOLEAN := false;
17139     do_remove       BOOLEAN := false;
17140 BEGIN
17141     add_query := $$
17142             INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
17143                 SELECT  cp.id, cp.circ_lib, cn.record
17144                   FROM  asset.copy cp
17145                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
17146                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
17147                         JOIN asset.copy_location cl ON (cp.location = cl.id)
17148                         JOIN config.copy_status cs ON (cp.status = cs.id)
17149                         JOIN biblio.record_entry b ON (cn.record = b.id)
17150                   WHERE NOT cp.deleted
17151                         AND NOT cn.deleted
17152                         AND NOT b.deleted
17153                         AND cs.opac_visible
17154                         AND cl.opac_visible
17155                         AND cp.opac_visible
17156                         AND a.opac_visible
17157     $$;
17158  
17159     remove_query := $$ DELETE FROM asset.opac_visible_copies WHERE id IN ( SELECT id FROM asset.copy WHERE $$;
17160
17161     IF TG_OP = 'INSERT' THEN
17162
17163         IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17164             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17165             EXECUTE add_query;
17166         END IF;
17167
17168         RETURN NEW;
17169
17170     END IF;
17171
17172     -- handle items first, since with circulation activity
17173     -- their statuses change frequently
17174     IF TG_TABLE_NAME IN ('copy', 'unit') THEN
17175
17176         IF OLD.location    <> NEW.location OR
17177            OLD.call_number <> NEW.call_number OR
17178            OLD.status      <> NEW.status OR
17179            OLD.circ_lib    <> NEW.circ_lib THEN
17180             -- any of these could change visibility, but
17181             -- we'll save some queries and not try to calculate
17182             -- the change directly
17183             do_remove := true;
17184             do_add := true;
17185         ELSE
17186
17187             IF OLD.deleted <> NEW.deleted THEN
17188                 IF NEW.deleted THEN
17189                     do_remove := true;
17190                 ELSE
17191                     do_add := true;
17192                 END IF;
17193             END IF;
17194
17195             IF OLD.opac_visible <> NEW.opac_visible THEN
17196                 IF OLD.opac_visible THEN
17197                     do_remove := true;
17198                 ELSIF NOT do_remove THEN -- handle edge case where deleted item
17199                                         -- is also marked opac_visible
17200                     do_add := true;
17201                 END IF;
17202             END IF;
17203
17204         END IF;
17205
17206         IF do_remove THEN
17207             DELETE FROM asset.opac_visible_copies WHERE id = NEW.id;
17208         END IF;
17209         IF do_add THEN
17210             add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17211             EXECUTE add_query;
17212         END IF;
17213
17214         RETURN NEW;
17215
17216     END IF;
17217
17218     IF TG_TABLE_NAME IN ('call_number', 'record_entry') THEN -- these have a 'deleted' column
17219  
17220         IF OLD.deleted AND NEW.deleted THEN -- do nothing
17221
17222             RETURN NEW;
17223  
17224         ELSIF NEW.deleted THEN -- remove rows
17225  
17226             IF TG_TABLE_NAME = 'call_number' THEN
17227                 DELETE FROM asset.opac_visible_copies WHERE id IN (SELECT id FROM asset.copy WHERE call_number = NEW.id);
17228             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17229                 DELETE FROM asset.opac_visible_copies WHERE record = NEW.id;
17230             END IF;
17231  
17232             RETURN NEW;
17233  
17234         ELSIF OLD.deleted THEN -- add rows
17235  
17236             IF TG_TABLE_NAME IN ('copy','unit') THEN
17237                 add_query := add_query || 'AND cp.id = ' || NEW.id || ';';
17238             ELSIF TG_TABLE_NAME = 'call_number' THEN
17239                 add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17240             ELSIF TG_TABLE_NAME = 'record_entry' THEN
17241                 add_query := add_query || 'AND cn.record = ' || NEW.id || ';';
17242             END IF;
17243  
17244             EXECUTE add_query;
17245             RETURN NEW;
17246  
17247         END IF;
17248  
17249     END IF;
17250
17251     IF TG_TABLE_NAME = 'call_number' THEN
17252
17253         IF OLD.record <> NEW.record THEN
17254             -- call number is linked to different bib
17255             remove_query := remove_query || 'call_number = ' || NEW.id || ');';
17256             EXECUTE remove_query;
17257             add_query := add_query || 'AND cp.call_number = ' || NEW.id || ';';
17258             EXECUTE add_query;
17259         END IF;
17260
17261         RETURN NEW;
17262
17263     END IF;
17264
17265     IF TG_TABLE_NAME IN ('record_entry') THEN
17266         RETURN NEW; -- don't have 'opac_visible'
17267     END IF;
17268
17269     -- actor.org_unit, asset.copy_location, asset.copy_status
17270     IF NEW.opac_visible = OLD.opac_visible THEN -- do nothing
17271
17272         RETURN NEW;
17273
17274     ELSIF NEW.opac_visible THEN -- add rows
17275
17276         IF TG_TABLE_NAME = 'org_unit' THEN
17277             add_query := add_query || 'AND cp.circ_lib = ' || NEW.id || ';';
17278         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17279             add_query := add_query || 'AND cp.location = ' || NEW.id || ';';
17280         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17281             add_query := add_query || 'AND cp.status = ' || NEW.id || ';';
17282         END IF;
17283  
17284         EXECUTE add_query;
17285  
17286     ELSE -- delete rows
17287
17288         IF TG_TABLE_NAME = 'org_unit' THEN
17289             remove_query := 'DELETE FROM asset.opac_visible_copies WHERE circ_lib = ' || NEW.id || ';';
17290         ELSIF TG_TABLE_NAME = 'copy_location' THEN
17291             remove_query := remove_query || 'location = ' || NEW.id || ');';
17292         ELSIF TG_TABLE_NAME = 'copy_status' THEN
17293             remove_query := remove_query || 'status = ' || NEW.id || ');';
17294         END IF;
17295  
17296         EXECUTE remove_query;
17297  
17298     END IF;
17299  
17300     RETURN NEW;
17301 END;
17302 $func$ LANGUAGE PLPGSQL;
17303 COMMENT ON FUNCTION asset.cache_copy_visibility() IS $$
17304 Trigger function to update the copy OPAC visiblity cache.
17305 $$;
17306 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();
17307 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17308 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();
17309 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();
17310 CREATE TRIGGER a_opac_vis_mat_view_tgr AFTER INSERT OR UPDATE ON serial.unit FOR EACH ROW EXECUTE PROCEDURE asset.cache_copy_visibility();
17311 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();
17312 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();
17313
17314 -- must create this rule explicitly; it is not inherited from asset.copy
17315 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;
17316
17317 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);
17318
17319 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
17320 DECLARE
17321     moved_objects INT := 0;
17322     bib_id        INT := 0;
17323     bib_rec       biblio.record_entry%ROWTYPE;
17324     auth_link     authority.bib_linking%ROWTYPE;
17325     ingest_same   boolean;
17326 BEGIN
17327
17328     -- 1. Update all bib records with the ID from target_record in their $0
17329     FOR bib_rec IN SELECT bre.* FROM biblio.record_entry bre
17330       INNER JOIN authority.bib_linking abl ON abl.bib = bre.id
17331       WHERE abl.authority = source_record LOOP
17332
17333         UPDATE biblio.record_entry
17334           SET marc = REGEXP_REPLACE(marc,
17335             E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
17336             E'\\1' || target_record || '<', 'g')
17337           WHERE id = bib_rec.id;
17338
17339           moved_objects := moved_objects + 1;
17340     END LOOP;
17341
17342     -- 2. Grab the current value of reingest on same MARC flag
17343     SELECT enabled INTO ingest_same
17344       FROM config.internal_flag
17345       WHERE name = 'ingest.reingest.force_on_same_marc'
17346     ;
17347
17348     -- 3. Temporarily set reingest on same to TRUE
17349     UPDATE config.internal_flag
17350       SET enabled = TRUE
17351       WHERE name = 'ingest.reingest.force_on_same_marc'
17352     ;
17353
17354     -- 4. Make a harmless update to target_record to trigger auto-update
17355     --    in linked bibliographic records
17356     UPDATE authority.record_entry
17357       SET DELETED = FALSE
17358       WHERE id = source_record;
17359
17360     -- 5. "Delete" source_record
17361     DELETE FROM authority.record_entry
17362       WHERE id = source_record;
17363
17364     -- 6. Set "reingest on same MARC" flag back to initial value
17365     UPDATE config.internal_flag
17366       SET enabled = ingest_same
17367       WHERE name = 'ingest.reingest.force_on_same_marc'
17368     ;
17369
17370     RETURN moved_objects;
17371 END;
17372 $func$ LANGUAGE plpgsql;
17373
17374 -- serial.record_entry already had an owner column spelled "owning_lib"
17375 -- Adjust the table and affected functions accordingly
17376
17377 ALTER TABLE serial.record_entry DROP COLUMN owner;
17378
17379 CREATE TABLE actor.usr_saved_search (
17380     id              SERIAL          PRIMARY KEY,
17381         owner           INT             NOT NULL REFERENCES actor.usr (id)
17382                                         ON DELETE CASCADE
17383                                         DEFERRABLE INITIALLY DEFERRED,
17384         name            TEXT            NOT NULL,
17385         create_date     TIMESTAMPTZ     NOT NULL DEFAULT now(),
17386         query_text      TEXT            NOT NULL,
17387         query_type      TEXT            NOT NULL
17388                                         CONSTRAINT valid_query_text CHECK (
17389                                         query_type IN ( 'URL' )) DEFAULT 'URL',
17390                                         -- we may add other types someday
17391         target          TEXT            NOT NULL
17392                                         CONSTRAINT valid_target CHECK (
17393                                         target IN ( 'record', 'metarecord', 'callnumber' )),
17394         CONSTRAINT name_once_per_user UNIQUE (owner, name)
17395 );
17396
17397 -- Apply Dan Wells' changes to the serial schema, from the
17398 -- seials-integration branch
17399
17400 CREATE TABLE serial.subscription_note (
17401         id           SERIAL PRIMARY KEY,
17402         subscription INT    NOT NULL
17403                             REFERENCES serial.subscription (id)
17404                             ON DELETE CASCADE
17405                             DEFERRABLE INITIALLY DEFERRED,
17406         creator      INT    NOT NULL
17407                             REFERENCES actor.usr (id)
17408                             DEFERRABLE INITIALLY DEFERRED,
17409         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17410         pub          BOOL   NOT NULL DEFAULT FALSE,
17411         title        TEXT   NOT NULL,
17412         value        TEXT   NOT NULL
17413 );
17414 CREATE INDEX serial_subscription_note_sub_idx ON serial.subscription_note (subscription);
17415
17416 CREATE TABLE serial.distribution_note (
17417         id           SERIAL PRIMARY KEY,
17418         distribution INT    NOT NULL
17419                             REFERENCES serial.distribution (id)
17420                             ON DELETE CASCADE
17421                             DEFERRABLE INITIALLY DEFERRED,
17422         creator      INT    NOT NULL
17423                             REFERENCES actor.usr (id)
17424                             DEFERRABLE INITIALLY DEFERRED,
17425         create_date  TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
17426         pub          BOOL   NOT NULL DEFAULT FALSE,
17427         title        TEXT   NOT NULL,
17428         value        TEXT   NOT NULL
17429 );
17430 CREATE INDEX serial_distribution_note_dist_idx ON serial.distribution_note (distribution);
17431
17432 ------- Begin surgery on serial.unit
17433
17434 ALTER TABLE serial.unit
17435         DROP COLUMN label;
17436
17437 ALTER TABLE serial.unit
17438         RENAME COLUMN label_sort_key TO sort_key;
17439
17440 ALTER TABLE serial.unit
17441         RENAME COLUMN contents TO detailed_contents;
17442
17443 ALTER TABLE serial.unit
17444         ADD COLUMN summary_contents TEXT;
17445
17446 UPDATE serial.unit
17447 SET summary_contents = detailed_contents;
17448
17449 ALTER TABLE serial.unit
17450         ALTER column summary_contents SET NOT NULL;
17451
17452 ------- End surgery on serial.unit
17453
17454 -- 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' );
17455
17456 -- Now rebuild the constraints dropped via cascade.
17457 -- ALTER TABLE acq.provider    ADD CONSTRAINT provider_edi_default_fkey FOREIGN KEY (edi_default) REFERENCES acq.edi_account (id) DEFERRABLE INITIALLY DEFERRED;
17458 DROP INDEX IF EXISTS money.money_mat_summary_id_idx;
17459 ALTER TABLE money.materialized_billable_xact_summary ADD PRIMARY KEY (id);
17460
17461 -- ALTER TABLE staging.billing_address_stage ADD PRIMARY KEY (row_id);
17462
17463 DELETE FROM config.metabib_field_index_norm_map
17464     WHERE norm IN (
17465         SELECT id 
17466             FROM config.index_normalizer
17467             WHERE func IN ('first_word', 'naco_normalize', 'split_date_range')
17468     )
17469     AND field = 18
17470 ;
17471
17472 -- We won't necessarily use all of these, but they are here for completeness.
17473 -- Source is the EDI spec 6063 codelist, eg: http://www.stylusstudio.com/edifact/D04B/6063.htm
17474 -- Values are the EDI code value + 1200
17475
17476 INSERT INTO acq.cancel_reason (org_unit, keep_debits, id, label, description) VALUES 
17477 (1, 't', 1201, 'Discrete quantity', 'Individually separated and distinct quantity.'),
17478 (1, 't', 1202, 'Charge', 'Quantity relevant for charge.'),
17479 (1, 't', 1203, 'Cumulative quantity', 'Quantity accumulated.'),
17480 (1, 't', 1204, 'Interest for overdrawn account', 'Interest for overdrawing the account.'),
17481 (1, 't', 1205, 'Active ingredient dose per unit', 'The dosage of active ingredient per unit.'),
17482 (1, 't', 1206, 'Auditor', 'The number of entities that audit accounts.'),
17483 (1, 't', 1207, 'Branch locations, leased', 'The number of branch locations being leased by an entity.'),
17484 (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.'),
17485 (1, 't', 1209, 'Branch locations, owned', 'The number of branch locations owned by an entity.'),
17486 (1, 't', 1210, 'Judgements registered', 'The number of judgements registered against an entity.'),
17487 (1, 't', 1211, 'Split quantity', 'Part of the whole quantity.'),
17488 (1, 't', 1212, 'Despatch quantity', 'Quantity despatched by the seller.'),
17489 (1, 't', 1213, 'Liens registered', 'The number of liens registered against an entity.'),
17490 (1, 't', 1214, 'Livestock', 'The number of animals kept for use or profit.'),
17491 (1, 't', 1215, 'Insufficient funds returned cheques', 'The number of cheques returned due to insufficient funds.'),
17492 (1, 't', 1216, 'Stolen cheques', 'The number of stolen cheques.'),
17493 (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.'),
17494 (1, 't', 1218, 'Previous quantity', 'Quantity previously referenced.'),
17495 (1, 't', 1219, 'Paid-in security shares', 'The number of security shares issued and for which full payment has been made.'),
17496 (1, 't', 1220, 'Unusable quantity', 'Quantity not usable.'),
17497 (1, 't', 1221, 'Ordered quantity', '[6024] The quantity which has been ordered.'),
17498 (1, 't', 1222, 'Quantity at 100%', 'Equivalent quantity at 100% purity.'),
17499 (1, 't', 1223, 'Active ingredient', 'Quantity at 100% active agent content.'),
17500 (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.'),
17501 (1, 't', 1225, 'Retail sales', 'Quantity of retail point of sale activity.'),
17502 (1, 't', 1226, 'Promotion quantity', 'A quantity associated with a promotional event.'),
17503 (1, 't', 1227, 'On hold for shipment', 'Article received which cannot be shipped in its present form.'),
17504 (1, 't', 1228, 'Military sales quantity', 'Quantity of goods or services sold to a military organization.'),
17505 (1, 't', 1229, 'On premises sales',  'Sale of product in restaurants or bars.'),
17506 (1, 't', 1230, 'Off premises sales', 'Sale of product directly to a store.'),
17507 (1, 't', 1231, 'Estimated annual volume', 'Volume estimated for a year.'),
17508 (1, 't', 1232, 'Minimum delivery batch', 'Minimum quantity of goods delivered at one time.'),
17509 (1, 't', 1233, 'Maximum delivery batch', 'Maximum quantity of goods delivered at one time.'),
17510 (1, 't', 1234, 'Pipes', 'The number of tubes used to convey a substance.'),
17511 (1, 't', 1235, 'Price break from', 'The minimum quantity of a quantity range for a specified (unit) price.'),
17512 (1, 't', 1236, 'Price break to', 'Maximum quantity to which the price break applies.'),
17513 (1, 't', 1237, 'Poultry', 'The number of domestic fowl.'),
17514 (1, 't', 1238, 'Secured charges registered', 'The number of secured charges registered against an entity.'),
17515 (1, 't', 1239, 'Total properties owned', 'The total number of properties owned by an entity.'),
17516 (1, 't', 1240, 'Normal delivery', 'Quantity normally delivered by the seller.'),
17517 (1, 't', 1241, 'Sales quantity not included in the replenishment', 'calculation Sales which will not be included in the calculation of replenishment requirements.'),
17518 (1, 't', 1242, 'Maximum supply quantity, supplier endorsed', 'Maximum supply quantity endorsed by a supplier.'),
17519 (1, 't', 1243, 'Buyer', 'The number of buyers.'),
17520 (1, 't', 1244, 'Debenture bond', 'The number of fixed-interest bonds of an entity backed by general credit rather than specified assets.'),
17521 (1, 't', 1245, 'Debentures filed against directors', 'The number of notices of indebtedness filed against an entity''s directors.'),
17522 (1, 't', 1246, 'Pieces delivered', 'Number of pieces actually received at the final destination.'),
17523 (1, 't', 1247, 'Invoiced quantity', 'The quantity as per invoice.'),
17524 (1, 't', 1248, 'Received quantity', 'The quantity which has been received.'),
17525 (1, 't', 1249, 'Chargeable distance', '[6110] The distance between two points for which a specific tariff applies.'),
17526 (1, 't', 1250, 'Disposition undetermined quantity', 'Product quantity that has not yet had its disposition determined.'),
17527 (1, 't', 1251, 'Inventory category transfer', 'Inventory that has been moved from one inventory category to another.'),
17528 (1, 't', 1252, 'Quantity per pack', 'Quantity for each pack.'),
17529 (1, 't', 1253, 'Minimum order quantity', 'Minimum quantity of goods for an order.'),
17530 (1, 't', 1254, 'Maximum order quantity', 'Maximum quantity of goods for an order.'),
17531 (1, 't', 1255, 'Total sales', 'The summation of total quantity sales.'),
17532 (1, 't', 1256, 'Wholesaler to wholesaler sales', 'Sale of product to other wholesalers by a wholesaler.'),
17533 (1, 't', 1257, 'In transit quantity', 'A quantity that is en route.'),
17534 (1, 't', 1258, 'Quantity withdrawn', 'Quantity withdrawn from a location.'),
17535 (1, 't', 1259, 'Numbers of consumer units in the traded unit', 'Number of units for consumer sales in a unit for trading.'),
17536 (1, 't', 1260, 'Current inventory quantity available for shipment', 'Current inventory quantity available for shipment.'),
17537 (1, 't', 1261, 'Return quantity', 'Quantity of goods returned.'),
17538 (1, 't', 1262, 'Sorted quantity', 'The quantity that is sorted.'),
17539 (1, 'f', 1263, 'Sorted quantity rejected', 'The sorted quantity that is rejected.'),
17540 (1, 't', 1264, 'Scrap quantity', 'Remainder of the total quantity after split deliveries.'),
17541 (1, 'f', 1265, 'Destroyed quantity', 'Quantity of goods destroyed.'),
17542 (1, 't', 1266, 'Committed quantity', 'Quantity a party is committed to.'),
17543 (1, 't', 1267, 'Estimated reading quantity', 'The value that is estimated to be the reading of a measuring device (e.g. meter).'),
17544 (1, 't', 1268, 'End quantity', 'The quantity recorded at the end of an agreement or period.'),
17545 (1, 't', 1269, 'Start quantity', 'The quantity recorded at the start of an agreement or period.'),
17546 (1, 't', 1270, 'Cumulative quantity received', 'Cumulative quantity of all deliveries of this article received by the buyer.'),
17547 (1, 't', 1271, 'Cumulative quantity ordered', 'Cumulative quantity of all deliveries, outstanding and scheduled orders.'),
17548 (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.'),
17549 (1, 't', 1273, 'Outstanding quantity', 'Difference between quantity ordered and quantity received.'),
17550 (1, 't', 1274, 'Latest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product.'),
17551 (1, 't', 1275, 'Previous highest cumulative quantity', 'Cumulative quantity after complete delivery of all scheduled quantities of the product from a prior schedule period.'),
17552 (1, 't', 1276, 'Adjusted corrector reading', 'A corrector reading after it has been adjusted.'),
17553 (1, 't', 1277, 'Work days', 'Number of work days, e.g. per respective period.'),
17554 (1, 't', 1278, 'Cumulative quantity scheduled', 'Adding the quantity actually scheduled to previous cumulative quantity.'),
17555 (1, 't', 1279, 'Previous cumulative quantity', 'Cumulative quantity prior the actual order.'),
17556 (1, 't', 1280, 'Unadjusted corrector reading', 'A corrector reading before it has been adjusted.'),
17557 (1, 't', 1281, 'Extra unplanned delivery', 'Non scheduled additional quantity.'),
17558 (1, 't', 1282, 'Quantity requirement for sample inspection', 'Required quantity for sample inspection.'),
17559 (1, 't', 1283, 'Backorder quantity', 'The quantity of goods that is on back-order.'),
17560 (1, 't', 1284, 'Urgent delivery quantity', 'Quantity for urgent delivery.'),
17561 (1, 'f', 1285, 'Previous order quantity to be cancelled', 'Quantity ordered previously to be cancelled.'),
17562 (1, 't', 1286, 'Normal reading quantity', 'The value recorded or read from a measuring device (e.g. meter) in the normal conditions.'),
17563 (1, 't', 1287, 'Customer reading quantity', 'The value recorded or read from a measuring device (e.g. meter) by the customer.'),
17564 (1, 't', 1288, 'Information reading quantity', 'The value recorded or read from a measuring device (e.g. meter) for information purposes.'),
17565 (1, 't', 1289, 'Quality control held', 'Quantity of goods held pending completion of a quality control assessment.'),
17566 (1, 't', 1290, 'As is quantity', 'Quantity as it is in the existing circumstances.'),
17567 (1, 't', 1291, 'Open quantity', 'Quantity remaining after partial delivery.'),
17568 (1, 't', 1292, 'Final delivery quantity', 'Quantity of final delivery to a respective order.'),
17569 (1, 't', 1293, 'Subsequent delivery quantity', 'Quantity delivered to a respective order after it''s final delivery.'),
17570 (1, 't', 1294, 'Substitutional quantity', 'Quantity delivered replacing previous deliveries.'),
17571 (1, 't', 1295, 'Redelivery after post processing', 'Quantity redelivered after post processing.'),
17572 (1, 'f', 1296, 'Quality control failed', 'Quantity of goods which have failed quality control.'),
17573 (1, 't', 1297, 'Minimum inventory', 'Minimum stock quantity on which replenishment is based.'),
17574 (1, 't', 1298, 'Maximum inventory', 'Maximum stock quantity on which replenishment is based.'),
17575 (1, 't', 1299, 'Estimated quantity', 'Quantity estimated.'),
17576 (1, 't', 1300, 'Chargeable weight', 'The weight on which charges are based.'),
17577 (1, 't', 1301, 'Chargeable gross weight', 'The gross weight on which charges are based.'),
17578 (1, 't', 1302, 'Chargeable tare weight', 'The tare weight on which charges are based.'),
17579 (1, 't', 1303, 'Chargeable number of axles', 'The number of axles on which charges are based.'),
17580 (1, 't', 1304, 'Chargeable number of containers', 'The number of containers on which charges are based.'),
17581 (1, 't', 1305, 'Chargeable number of rail wagons', 'The number of rail wagons on which charges are based.'),
17582 (1, 't', 1306, 'Chargeable number of packages', 'The number of packages on which charges are based.'),
17583 (1, 't', 1307, 'Chargeable number of units', 'The number of units on which charges are based.'),
17584 (1, 't', 1308, 'Chargeable period', 'The period of time on which charges are based.'),
17585 (1, 't', 1309, 'Chargeable volume', 'The volume on which charges are based.'),
17586 (1, 't', 1310, 'Chargeable cubic measurements', 'The cubic measurements on which charges are based.'),
17587 (1, 't', 1311, 'Chargeable surface', 'The surface area on which charges are based.'),
17588 (1, 't', 1312, 'Chargeable length', 'The length on which charges are based.'),
17589 (1, 't', 1313, 'Quantity to be delivered', 'The quantity to be delivered.'),
17590 (1, 't', 1314, 'Number of passengers', 'Total number of passengers on the conveyance.'),
17591 (1, 't', 1315, 'Number of crew', 'Total number of crew members on the conveyance.'),
17592 (1, 't', 1316, 'Number of transport documents', 'Total number of air waybills, bills of lading, etc. being reported for a specific conveyance.'),
17593 (1, 't', 1317, 'Quantity landed', 'Quantity of goods actually arrived.'),
17594 (1, 't', 1318, 'Quantity manifested', 'Quantity of goods contracted for delivery by the carrier.'),
17595 (1, 't', 1319, 'Short shipped', 'Indication that part of the consignment was not shipped.'),
17596 (1, 't', 1320, 'Split shipment', 'Indication that the consignment has been split into two or more shipments.'),
17597 (1, 't', 1321, 'Over shipped', 'The quantity of goods shipped that exceeds the quantity contracted.'),
17598 (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.'),
17599 (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.'),
17600 (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.'),
17601 (1, 'f', 1325, 'Pilferage goods', 'Quantity of goods stolen during transport.'),
17602 (1, 'f', 1326, 'Lost goods', 'Quantity of goods that disappeared in transport.'),
17603 (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.'),
17604 (1, 't', 1328, 'Quantity loaded', 'Quantity of goods loaded onto a means of transport.'),
17605 (1, 't', 1329, 'Units per unit price', 'Number of units per unit price.'),
17606 (1, 't', 1330, 'Allowance', 'Quantity relevant for allowance.'),
17607 (1, 't', 1331, 'Delivery quantity', 'Quantity required by buyer to be delivered.'),
17608 (1, 't', 1332, 'Cumulative quantity, preceding period, planned', 'Cumulative quantity originally planned for the preceding period.'),
17609 (1, 't', 1333, 'Cumulative quantity, preceding period, reached', 'Cumulative quantity reached in the preceding period.'),
17610 (1, 't', 1334, 'Cumulative quantity, actual planned',            'Cumulative quantity planned for now.'),
17611 (1, 't', 1335, 'Period quantity, planned', 'Quantity planned for this period.'),
17612 (1, 't', 1336, 'Period quantity, reached', 'Quantity reached during this period.'),
17613 (1, 't', 1337, 'Cumulative quantity, preceding period, estimated', 'Estimated cumulative quantity reached in the preceding period.'),
17614 (1, 't', 1338, 'Cumulative quantity, actual estimated',            'Estimated cumulative quantity reached now.'),
17615 (1, 't', 1339, 'Cumulative quantity, preceding period, measured', 'Surveyed cumulative quantity reached in the preceding period.'),
17616 (1, 't', 1340, 'Cumulative quantity, actual measured', 'Surveyed cumulative quantity reached now.'),
17617 (1, 't', 1341, 'Period quantity, measured',            'Surveyed quantity reached during this period.'),
17618 (1, 't', 1342, 'Total quantity, planned', 'Total quantity planned.'),
17619 (1, 't', 1343, 'Quantity, remaining', 'Quantity remaining.'),
17620 (1, 't', 1344, 'Tolerance', 'Plus or minus tolerance expressed as a monetary amount.'),
17621 (1, 't', 1345, 'Actual stock',          'The stock on hand, undamaged, and available for despatch, sale or use.'),
17622 (1, 't', 1346, 'Model or target stock', 'The stock quantity required or planned to have on hand, undamaged and available for use.'),
17623 (1, 't', 1347, 'Direct shipment quantity', 'Quantity to be shipped directly to a customer from a manufacturing site.'),
17624 (1, 't', 1348, 'Amortization total quantity',     'Indication of final quantity for amortization.'),
17625 (1, 't', 1349, 'Amortization order quantity',     'Indication of actual share of the order quantity for amortization.'),
17626 (1, 't', 1350, 'Amortization cumulated quantity', 'Indication of actual cumulated quantity of previous and actual amortization order quantity.'),
17627 (1, 't', 1351, 'Quantity advised',  'Quantity advised by supplier or shipper, in contrast to quantity actually received.'),
17628 (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.'),
17629 (1, 't', 1353, 'Statistical sales quantity', 'Quantity of goods sold in a specified period.'),
17630 (1, 't', 1354, 'Sales quantity planned',     'Quantity of goods required to meet future demands. - Market intelligence quantity.'),
17631 (1, 't', 1355, 'Replenishment quantity',     'Quantity required to maintain the requisite on-hand stock of goods.'),
17632 (1, 't', 1356, 'Inventory movement quantity', 'To specify the quantity of an inventory movement.'),
17633 (1, 't', 1357, 'Opening stock balance quantity', 'To specify the quantity of an opening stock balance.'),
17634 (1, 't', 1358, 'Closing stock balance quantity', 'To specify the quantity of a closing stock balance.'),
17635 (1, 't', 1359, 'Number of stops', 'Number of times a means of transport stops before arriving at destination.'),
17636 (1, 't', 1360, 'Minimum production batch', 'The quantity specified is the minimum output from a single production run.'),
17637 (1, 't', 1361, 'Dimensional sample quantity', 'The quantity defined is a sample for the purpose of validating dimensions.'),
17638 (1, 't', 1362, 'Functional sample quantity', 'The quantity defined is a sample for the purpose of validating function and performance.'),
17639 (1, 't', 1363, 'Pre-production quantity', 'Quantity of the referenced item required prior to full production.'),
17640 (1, 't', 1364, 'Delivery batch', 'Quantity of the referenced item which constitutes a standard batch for deliver purposes.'),
17641 (1, 't', 1365, 'Delivery batch multiple', 'The multiples in which delivery batches can be supplied.'),
17642 (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.'),
17643 (1, 't', 1367, 'Total delivery quantity',  'The total quantity required by the buyer to be delivered.'),
17644 (1, 't', 1368, 'Single delivery quantity', 'The quantity required by the buyer to be delivered in a single shipment.'),
17645 (1, 't', 1369, 'Supplied quantity',  'Quantity of the referenced item actually shipped.'),
17646 (1, 't', 1370, 'Allocated quantity', 'Quantity of the referenced item allocated from available stock for delivery.'),
17647 (1, 't', 1371, 'Maximum stackability', 'The number of pallets/handling units which can be safely stacked one on top of another.'),
17648 (1, 't', 1372, 'Amortisation quantity', 'The quantity of the referenced item which has a cost for tooling amortisation included in the item price.'),
17649 (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.'),
17650 (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.'),
17651 (1, 't', 1375, 'Number of moulds', 'The number of pressing moulds contained within a single piece of the referenced tooling.'),
17652 (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.'),
17653 (1, 't', 1377, 'Periodic capacity of tooling', 'Maximum production output of the referenced tool over a period of time.'),
17654 (1, 't', 1378, 'Lifetime capacity of tooling', 'Maximum production output of the referenced tool over its productive lifetime.'),
17655 (1, 't', 1379, 'Number of deliveries per despatch period', 'The number of deliveries normally expected to be despatched within each despatch period.'),
17656 (1, 't', 1380, 'Provided quantity', 'The quantity of a referenced component supplied by the buyer for manufacturing of an ordered item.'),
17657 (1, 't', 1381, 'Maximum production batch', 'The quantity specified is the maximum output from a single production run.'),
17658 (1, 'f', 1382, 'Cancelled quantity', 'Quantity of the referenced item which has previously been ordered and is now cancelled.'),
17659 (1, 't', 1383, 'No delivery requirement in this instruction', 'This delivery instruction does not contain any delivery requirements.'),
17660 (1, 't', 1384, 'Quantity of material in ordered time', 'Quantity of the referenced material within the ordered time.'),
17661 (1, 'f', 1385, 'Rejected quantity', 'The quantity of received goods rejected for quantity reasons.'),
17662 (1, 't', 1386, 'Cumulative quantity scheduled up to accumulation start date', 'The cumulative quantity scheduled up to the accumulation start date.'),
17663 (1, 't', 1387, 'Quantity scheduled', 'The quantity scheduled for delivery.'),
17664 (1, 't', 1388, 'Number of identical handling units', 'Number of identical handling units in terms of type and contents.'),
17665 (1, 't', 1389, 'Number of packages in handling unit', 'The number of packages contained in one handling unit.'),
17666 (1, 't', 1390, 'Despatch note quantity', 'The item quantity specified on the despatch note.'),
17667 (1, 't', 1391, 'Adjustment to inventory quantity', 'An adjustment to inventory quantity.'),
17668 (1, 't', 1392, 'Free goods quantity',    'Quantity of goods which are free of charge.'),
17669 (1, 't', 1393, 'Free quantity included', 'Quantity included to which no charge is applicable.'),
17670 (1, 't', 1394, 'Received and accepted',  'Quantity which has been received and accepted at a given location.'),
17671 (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.'),
17672 (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.'),
17673 (1, 't', 1397, 'Reordering level', 'Quantity at which an order may be triggered to replenish.'),
17674 (1, 't', 1399, 'Inventory withdrawal quantity', 'Quantity which has been withdrawn from inventory since the last inventory report.'),
17675 (1, 't', 1400, 'Free quantity not included', 'Free quantity not included in ordered quantity.'),
17676 (1, 't', 1401, 'Recommended overhaul and repair quantity', 'To indicate the recommended quantity of an article required to support overhaul and repair activities.'),
17677 (1, 't', 1402, 'Quantity per next higher assembly', 'To indicate the quantity required for the next higher assembly.'),
17678 (1, 't', 1403, 'Quantity per unit of issue', 'Provides the standard quantity of an article in which one unit can be issued.'),
17679 (1, 't', 1404, 'Cumulative scrap quantity',  'Provides the cumulative quantity of an item which has been identified as scrapped.'),
17680 (1, 't', 1405, 'Publication turn size', 'The quantity of magazines or newspapers grouped together with the spine facing alternate directions in a bundle.'),
17681 (1, 't', 1406, 'Recommended maintenance quantity', 'Recommended quantity of an article which is required to meet an agreed level of maintenance.'),
17682 (1, 't', 1407, 'Labour hours', 'Number of labour hours.'),
17683 (1, 't', 1408, 'Quantity requirement for maintenance and repair of', 'equipment Quantity of the material needed to maintain and repair equipment.'),
17684 (1, 't', 1409, 'Additional replenishment demand quantity', 'Incremental needs over and above normal replenishment calculations, but not intended to permanently change the model parameters.'),
17685 (1, 't', 1410, 'Returned by consumer quantity', 'Quantity returned by a consumer.'),
17686 (1, 't', 1411, 'Replenishment override quantity', 'Quantity to override the normal replenishment model calculations, but not intended to permanently change the model parameters.'),
17687 (1, 't', 1412, 'Quantity sold, net', 'Net quantity sold which includes returns of saleable inventory and other adjustments.'),
17688 (1, 't', 1413, 'Transferred out quantity',   'Quantity which was transferred out of this location.'),
17689 (1, 't', 1414, 'Transferred in quantity',    'Quantity which was transferred into this location.'),
17690 (1, 't', 1415, 'Unsaleable quantity',        'Quantity of inventory received which cannot be sold in its present condition.'),
17691 (1, 't', 1416, 'Consumer reserved quantity', 'Quantity reserved for consumer delivery or pickup and not yet withdrawn from inventory.'),
17692 (1, 't', 1417, 'Out of inventory quantity',  'Quantity of inventory which was requested but was not available.'),
17693 (1, 't', 1418, 'Quantity returned, defective or damaged', 'Quantity returned in a damaged or defective condition.'),
17694 (1, 't', 1419, 'Taxable quantity',           'Quantity subject to taxation.'),
17695 (1, 't', 1420, 'Meter reading', 'The numeric value of measure units counted by a meter.'),
17696 (1, 't', 1421, 'Maximum requestable quantity', 'The maximum quantity which may be requested.'),
17697 (1, 't', 1422, 'Minimum requestable quantity', 'The minimum quantity which may be requested.'),
17698 (1, 't', 1423, 'Daily average quantity', 'The quantity for a defined period divided by the number of days of the period.'),
17699 (1, 't', 1424, 'Budgeted hours',     'The number of budgeted hours.'),
17700 (1, 't', 1425, 'Actual hours',       'The number of actual hours.'),
17701 (1, 't', 1426, 'Earned value hours', 'The number of earned value hours.'),
17702 (1, 't', 1427, 'Estimated hours',    'The number of estimated hours.'),
17703 (1, 't', 1428, 'Level resource task quantity', 'Quantity of a resource that is level for the duration of the task.'),
17704 (1, 't', 1429, 'Available resource task quantity', 'Quantity of a resource available to complete a task.'),
17705 (1, 't', 1430, 'Work time units',   'Quantity of work units of time.'),
17706 (1, 't', 1431, 'Daily work shifts', 'Quantity of work shifts per day.'),
17707 (1, 't', 1432, 'Work time units per shift', 'Work units of time per work shift.'),
17708 (1, 't', 1433, 'Work calendar units',       'Work calendar units of time.'),
17709 (1, 't', 1434, 'Elapsed duration',   'Quantity representing the elapsed duration.'),
17710 (1, 't', 1435, 'Remaining duration', 'Quantity representing the remaining duration.'),
17711 (1, 't', 1436, 'Original duration',  'Quantity representing the original duration.'),
17712 (1, 't', 1437, 'Current duration',   'Quantity representing the current duration.'),
17713 (1, 't', 1438, 'Total float time',   'Quantity representing the total float time.'),
17714 (1, 't', 1439, 'Free float time',    'Quantity representing the free float time.'),
17715 (1, 't', 1440, 'Lag time',           'Quantity representing lag time.'),
17716 (1, 't', 1441, 'Lead time',          'Quantity representing lead time.'),
17717 (1, 't', 1442, 'Number of months', 'The number of months.'),
17718 (1, 't', 1443, 'Reserved quantity customer direct delivery sales', 'Quantity of products reserved for sales delivered direct to the customer.'),
17719 (1, 't', 1444, 'Reserved quantity retail sales', 'Quantity of products reserved for retail sales.'),
17720 (1, 't', 1445, 'Consolidated discount inventory', 'A quantity of inventory supplied at consolidated discount terms.'),
17721 (1, 't', 1446, 'Returns replacement quantity',    'A quantity of goods issued as a replacement for a returned quantity.'),
17722 (1, 't', 1447, 'Additional promotion sales forecast quantity', 'A forecast of additional quantity which will be sold during a period of promotional activity.'),
17723 (1, 't', 1448, 'Reserved quantity', 'Quantity reserved for specific purposes.'),
17724 (1, 't', 1449, 'Quantity displayed not available for sale', 'Quantity displayed within a retail outlet but not available for sale.'),
17725 (1, 't', 1450, 'Inventory discrepancy', 'The difference recorded between theoretical and physical inventory.'),
17726 (1, 't', 1451, 'Incremental order quantity', 'The incremental quantity by which ordering is carried out.'),
17727 (1, 't', 1452, 'Quantity requiring manipulation before despatch', 'A quantity of goods which needs manipulation before despatch.'),
17728 (1, 't', 1453, 'Quantity in quarantine',              'A quantity of goods which are held in a restricted area for quarantine purposes.'),
17729 (1, 't', 1454, 'Quantity withheld by owner of goods', 'A quantity of goods which has been withheld by the owner of the goods.'),
17730 (1, 't', 1455, 'Quantity not available for despatch', 'A quantity of goods not available for despatch.'),
17731 (1, 't', 1456, 'Quantity awaiting delivery', 'Quantity of goods which are awaiting delivery.'),
17732 (1, 't', 1457, 'Quantity in physical inventory',      'A quantity of goods held in physical inventory.'),
17733 (1, 't', 1458, 'Quantity held by logistic service provider', 'Quantity of goods under the control of a logistic service provider.'),
17734 (1, 't', 1459, 'Optimal quantity', 'The optimal quantity for a given purpose.'),
17735 (1, 't', 1460, 'Delivery quantity balance', 'The difference between the scheduled quantity and the quantity delivered to the consignee at a given date.'),
17736 (1, 't', 1461, 'Cumulative quantity shipped', 'Cumulative quantity of all shipments.'),
17737 (1, 't', 1462, 'Quantity suspended', 'The quantity of something which is suspended.'),
17738 (1, 't', 1463, 'Control quantity', 'The quantity designated for control purposes.'),
17739 (1, 't', 1464, 'Equipment quantity', 'A count of a quantity of equipment.'),
17740 (1, 't', 1465, 'Factor', 'Number by which the measured unit has to be multiplied to calculate the units used.'),
17741 (1, 't', 1466, 'Unsold quantity held by wholesaler', 'Unsold quantity held by the wholesaler.'),
17742 (1, 't', 1467, 'Quantity held by delivery vehicle', 'Quantity of goods held by the delivery vehicle.'),
17743 (1, 't', 1468, 'Quantity held by retail outlet', 'Quantity held by the retail outlet.'),
17744 (1, 'f', 1469, 'Rejected return quantity', 'A quantity for return which has been rejected.'),
17745 (1, 't', 1470, 'Accounts', 'The number of accounts.'),
17746 (1, 't', 1471, 'Accounts placed for collection', 'The number of accounts placed for collection.'),
17747 (1, 't', 1472, 'Activity codes', 'The number of activity codes.'),
17748 (1, 't', 1473, 'Agents', 'The number of agents.'),
17749 (1, 't', 1474, 'Airline attendants', 'The number of airline attendants.'),
17750 (1, 't', 1475, 'Authorised shares',  'The number of shares authorised for issue.'),
17751 (1, 't', 1476, 'Employee average',   'The average number of employees.'),
17752 (1, 't', 1477, 'Branch locations',   'The number of branch locations.'),
17753 (1, 't', 1478, 'Capital changes',    'The number of capital changes made.'),
17754 (1, 't', 1479, 'Clerks', 'The number of clerks.'),
17755 (1, 't', 1480, 'Companies in same activity', 'The number of companies doing business in the same activity category.'),
17756 (1, 't', 1481, 'Companies included in consolidated financial statement', 'The number of companies included in a consolidated financial statement.'),
17757 (1, 't', 1482, 'Cooperative shares', 'The number of cooperative shares.'),
17758 (1, 't', 1483, 'Creditors',   'The number of creditors.'),
17759 (1, 't', 1484, 'Departments', 'The number of departments.'),
17760 (1, 't', 1485, 'Design employees', 'The number of employees involved in the design process.'),
17761 (1, 't', 1486, 'Physicians', 'The number of medical doctors.'),
17762 (1, 't', 1487, 'Domestic affiliated companies', 'The number of affiliated companies located within the country.'),
17763 (1, 't', 1488, 'Drivers', 'The number of drivers.'),
17764 (1, 't', 1489, 'Employed at location',     'The number of employees at the specified location.'),
17765 (1, 't', 1490, 'Employed by this company', 'The number of employees at the specified company.'),
17766 (1, 't', 1491, 'Total employees',    'The total number of employees.'),
17767 (1, 't', 1492, 'Employees shared',   'The number of employees shared among entities.'),
17768 (1, 't', 1493, 'Engineers',          'The number of engineers.'),
17769 (1, 't', 1494, 'Estimated accounts', 'The estimated number of accounts.'),
17770 (1, 't', 1495, 'Estimated employees at location', 'The estimated number of employees at the specified location.'),
17771 (1, 't', 1496, 'Estimated total employees',       'The total estimated number of employees.'),
17772 (1, 't', 1497, 'Executives', 'The number of executives.'),
17773 (1, 't', 1498, 'Agricultural workers',   'The number of agricultural workers.'),
17774 (1, 't', 1499, 'Financial institutions', 'The number of financial institutions.'),
17775 (1, 't', 1500, 'Floors occupied', 'The number of floors occupied.'),
17776 (1, 't', 1501, 'Foreign related entities', 'The number of related entities located outside the country.'),
17777 (1, 't', 1502, 'Group employees',    'The number of employees within the group.'),
17778 (1, 't', 1503, 'Indirect employees', 'The number of employees not associated with direct production.'),
17779 (1, 't', 1504, 'Installers',    'The number of employees involved with the installation process.'),
17780 (1, 't', 1505, 'Invoices',      'The number of invoices.'),
17781 (1, 't', 1506, 'Issued shares', 'The number of shares actually issued.'),
17782 (1, 't', 1507, 'Labourers',     'The number of labourers.'),
17783 (1, 't', 1508, 'Manufactured units', 'The number of units manufactured.'),
17784 (1, 't', 1509, 'Maximum number of employees', 'The maximum number of people employed.'),
17785 (1, 't', 1510, 'Maximum number of employees at location', 'The maximum number of people employed at a location.'),
17786 (1, 't', 1511, 'Members in group', 'The number of members within a group.'),
17787 (1, 't', 1512, 'Minimum number of employees at location', 'The minimum number of people employed at a location.'),
17788 (1, 't', 1513, 'Minimum number of employees', 'The minimum number of people employed.'),
17789 (1, 't', 1514, 'Non-union employees', 'The number of employees not belonging to a labour union.'),
17790 (1, 't', 1515, 'Floors', 'The number of floors in a building.'),
17791 (1, 't', 1516, 'Nurses', 'The number of nurses.'),
17792 (1, 't', 1517, 'Office workers', 'The number of workers in an office.'),
17793 (1, 't', 1518, 'Other employees', 'The number of employees otherwise categorised.'),
17794 (1, 't', 1519, 'Part time employees', 'The number of employees working on a part time basis.'),
17795 (1, 't', 1520, 'Accounts payable average overdue days', 'The average number of days accounts payable are overdue.'),
17796 (1, 't', 1521, 'Pilots', 'The number of pilots.'),
17797 (1, 't', 1522, 'Plant workers', 'The number of workers within a plant.'),
17798 (1, 't', 1523, 'Previous number of accounts', 'The number of accounts which preceded the current count.'),
17799 (1, 't', 1524, 'Previous number of branch locations', 'The number of branch locations which preceded the current count.'),
17800 (1, 't', 1525, 'Principals included as employees', 'The number of principals which are included in the count of employees.'),
17801 (1, 't', 1526, 'Protested bills', 'The number of bills which are protested.'),
17802 (1, 't', 1527, 'Registered brands distributed', 'The number of registered brands which are being distributed.'),
17803 (1, 't', 1528, 'Registered brands manufactured', 'The number of registered brands which are being manufactured.'),
17804 (1, 't', 1529, 'Related business entities', 'The number of related business entities.'),
17805 (1, 't', 1530, 'Relatives employed', 'The number of relatives which are counted as employees.'),
17806 (1, 't', 1531, 'Rooms',        'The number of rooms.'),
17807 (1, 't', 1532, 'Salespersons', 'The number of salespersons.'),
17808 (1, 't', 1533, 'Seats',        'The number of seats.'),
17809 (1, 't', 1534, 'Shareholders', 'The number of shareholders.'),
17810 (1, 't', 1535, 'Shares of common stock', 'The number of shares of common stock.'),
17811 (1, 't', 1536, 'Shares of preferred stock', 'The number of shares of preferred stock.'),
17812 (1, 't', 1537, 'Silent partners', 'The number of silent partners.'),
17813 (1, 't', 1538, 'Subcontractors',  'The number of subcontractors.'),
17814 (1, 't', 1539, 'Subsidiaries',    'The number of subsidiaries.'),
17815 (1, 't', 1540, 'Law suits',       'The number of law suits.'),
17816 (1, 't', 1541, 'Suppliers',       'The number of suppliers.'),
17817 (1, 't', 1542, 'Teachers',        'The number of teachers.'),
17818 (1, 't', 1543, 'Technicians',     'The number of technicians.'),
17819 (1, 't', 1544, 'Trainees',        'The number of trainees.'),
17820 (1, 't', 1545, 'Union employees', 'The number of employees who are members of a labour union.'),
17821 (1, 't', 1546, 'Number of units', 'The quantity of units.'),
17822 (1, 't', 1547, 'Warehouse employees', 'The number of employees who work in a warehouse setting.'),
17823 (1, 't', 1548, 'Shareholders holding remainder of shares', 'Number of shareholders owning the remainder of shares.'),
17824 (1, 't', 1549, 'Payment orders filed', 'Number of payment orders filed.'),
17825 (1, 't', 1550, 'Uncovered cheques', 'Number of uncovered cheques.'),
17826 (1, 't', 1551, 'Auctions', 'Number of auctions.'),
17827 (1, 't', 1552, 'Units produced', 'The number of units produced.'),
17828 (1, 't', 1553, 'Added employees', 'Number of employees that were added to the workforce.'),
17829 (1, 't', 1554, 'Number of added locations', 'Number of locations that were added.'),
17830 (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.'),
17831 (1, 't', 1556, 'Number of closed locations', 'Number of locations that were closed.'),
17832 (1, 't', 1557, 'Counter clerks', 'The number of clerks that work behind a flat-topped fitment.'),
17833 (1, 't', 1558, 'Payment experiences in the last 3 months', 'The number of payment experiences received for an entity over the last 3 months.'),
17834 (1, 't', 1559, 'Payment experiences in the last 12 months', 'The number of payment experiences received for an entity over the last 12 months.'),
17835 (1, 't', 1560, 'Total number of subsidiaries not included in the financial', 'statement The total number of subsidiaries not included in the financial statement.'),
17836 (1, 't', 1561, 'Paid-in common shares', 'The number of paid-in common shares.'),
17837 (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.'),
17838 (1, 't', 1563, 'Total number of foreign subsidiaries included in financial statement', 'The total number of foreign subsidiaries included in the financial statement.'),
17839 (1, 't', 1564, 'Total number of domestic subsidiaries included in financial statement', 'The total number of domestic subsidiaries included in the financial statement.'),
17840 (1, 't', 1565, 'Total transactions', 'The total number of transactions.'),
17841 (1, 't', 1566, 'Paid-in preferred shares', 'The number of paid-in preferred shares.'),
17842 (1, 't', 1567, 'Employees', 'Code specifying the quantity of persons working for a company, whose services are used for pay.'),
17843 (1, 't', 1568, 'Active ingredient dose per unit, dispensed', 'The dosage of active ingredient per dispensed unit.'),
17844 (1, 't', 1569, 'Budget', 'Budget quantity.'),
17845 (1, 't', 1570, 'Budget, cumulative to date', 'Budget quantity, cumulative to date.'),
17846 (1, 't', 1571, 'Actual units', 'The number of actual units.'),
17847 (1, 't', 1572, 'Actual units, cumulative to date', 'The number of cumulative to date actual units.'),
17848 (1, 't', 1573, 'Earned value', 'Earned value quantity.'),
17849 (1, 't', 1574, 'Earned value, cumulative to date', 'Earned value quantity accumulated to date.'),
17850 (1, 't', 1575, 'At completion quantity, estimated', 'The estimated quantity when a project is complete.'),
17851 (1, 't', 1576, 'To complete quantity, estimated', 'The estimated quantity required to complete a project.'),
17852 (1, 't', 1577, 'Adjusted units', 'The number of adjusted units.'),
17853 (1, 't', 1578, 'Number of limited partnership shares', 'Number of shares held in a limited partnership.'),
17854 (1, 't', 1579, 'National business failure incidences', 'Number of firms in a country that discontinued with a loss to creditors.'),
17855 (1, 't', 1580, 'Industry business failure incidences', 'Number of firms in a specific industry that discontinued with a loss to creditors.'),
17856 (1, 't', 1581, 'Business class failure incidences', 'Number of firms in a specific class that discontinued with a loss to creditors.'),
17857 (1, 't', 1582, 'Mechanics', 'Number of mechanics.'),
17858 (1, 't', 1583, 'Messengers', 'Number of messengers.'),
17859 (1, 't', 1584, 'Primary managers', 'Number of primary managers.'),
17860 (1, 't', 1585, 'Secretaries', 'Number of secretaries.'),
17861 (1, 't', 1586, 'Detrimental legal filings', 'Number of detrimental legal filings.'),
17862 (1, 't', 1587, 'Branch office locations, estimated', 'Estimated number of branch office locations.'),
17863 (1, 't', 1588, 'Previous number of employees', 'The number of employees for a previous period.'),
17864 (1, 't', 1589, 'Asset seizers', 'Number of entities that seize assets of another entity.'),
17865 (1, 't', 1590, 'Out-turned quantity', 'The quantity discharged.'),
17866 (1, 't', 1591, 'Material on-board quantity, prior to loading', 'The material in vessel tanks, void spaces, and pipelines prior to loading.'),
17867 (1, 't', 1592, 'Supplier estimated previous meter reading', 'Previous meter reading estimated by the supplier.'),
17868 (1, 't', 1593, 'Supplier estimated latest meter reading',   'Latest meter reading estimated by the supplier.'),
17869 (1, 't', 1594, 'Customer estimated previous meter reading', 'Previous meter reading estimated by the customer.'),
17870 (1, 't', 1595, 'Customer estimated latest meter reading',   'Latest meter reading estimated by the customer.'),
17871 (1, 't', 1596, 'Supplier previous meter reading',           'Previous meter reading done by the supplier.'),
17872 (1, 't', 1597, 'Supplier latest meter reading',             'Latest meter reading recorded by the supplier.'),
17873 (1, 't', 1598, 'Maximum number of purchase orders allowed', 'Maximum number of purchase orders that are allowed.'),
17874 (1, 't', 1599, 'File size before compression', 'The size of a file before compression.'),
17875 (1, 't', 1600, 'File size after compression', 'The size of a file after compression.'),
17876 (1, 't', 1601, 'Securities shares', 'Number of shares of securities.'),
17877 (1, 't', 1602, 'Patients',         'Number of patients.'),
17878 (1, 't', 1603, 'Completed projects', 'Number of completed projects.'),
17879 (1, 't', 1604, 'Promoters',        'Number of entities who finance or organize an event or a production.'),
17880 (1, 't', 1605, 'Administrators',   'Number of administrators.'),
17881 (1, 't', 1606, 'Supervisors',      'Number of supervisors.'),
17882 (1, 't', 1607, 'Professionals',    'Number of professionals.'),
17883 (1, 't', 1608, 'Debt collectors',  'Number of debt collectors.'),
17884 (1, 't', 1609, 'Inspectors',       'Number of individuals who perform inspections.'),
17885 (1, 't', 1610, 'Operators',        'Number of operators.'),
17886 (1, 't', 1611, 'Trainers',         'Number of trainers.'),
17887 (1, 't', 1612, 'Active accounts',  'Number of accounts in a current or active status.'),
17888 (1, 't', 1613, 'Trademarks used',  'Number of trademarks used.'),
17889 (1, 't', 1614, 'Machines',         'Number of machines.'),
17890 (1, 't', 1615, 'Fuel pumps',       'Number of fuel pumps.'),
17891 (1, 't', 1616, 'Tables available', 'Number of tables available for use.'),
17892 (1, 't', 1617, 'Directors',        'Number of directors.'),
17893 (1, 't', 1618, 'Freelance debt collectors', 'Number of debt collectors who work on a freelance basis.'),
17894 (1, 't', 1619, 'Freelance salespersons',    'Number of salespersons who work on a freelance basis.'),
17895 (1, 't', 1620, 'Travelling employees',      'Number of travelling employees.'),
17896 (1, 't', 1621, 'Foremen', 'Number of workers with limited supervisory responsibilities.'),
17897 (1, 't', 1622, 'Production workers', 'Number of employees engaged in production.'),
17898 (1, 't', 1623, 'Employees not including owners', 'Number of employees excluding business owners.'),
17899 (1, 't', 1624, 'Beds', 'Number of beds.'),
17900 (1, 't', 1625, 'Resting quantity', 'A quantity of product that is at rest before it can be used.'),
17901 (1, 't', 1626, 'Production requirements', 'Quantity needed to meet production requirements.'),
17902 (1, 't', 1627, 'Corrected quantity', 'The quantity has been corrected.'),
17903 (1, 't', 1628, 'Operating divisions', 'Number of divisions operating.'),
17904 (1, 't', 1629, 'Quantitative incentive scheme base', 'Quantity constituting the base for the quantitative incentive scheme.'),
17905 (1, 't', 1630, 'Petitions filed', 'Number of petitions that have been filed.'),
17906 (1, 't', 1631, 'Bankruptcy petitions filed', 'Number of bankruptcy petitions that have been filed.'),
17907 (1, 't', 1632, 'Projects in process', 'Number of projects in process.'),
17908 (1, 't', 1633, 'Changes in capital structure', 'Number of modifications made to the capital structure of an entity.'),
17909 (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.'),
17910 (1, 't', 1635, 'Number of failed businesses of directors', 'The number of failed businesses with which the directors have been associated.'),
17911 (1, 't', 1636, 'Professor', 'The number of professors.'),
17912 (1, 't', 1637, 'Seller',    'The number of sellers.'),
17913 (1, 't', 1638, 'Skilled worker', 'The number of skilled workers.'),
17914 (1, 't', 1639, 'Trademark represented', 'The number of trademarks represented.'),
17915 (1, 't', 1640, 'Number of quantitative incentive scheme units', 'Number of units allocated to a quantitative incentive scheme.'),
17916 (1, 't', 1641, 'Quantity in manufacturing process', 'Quantity currently in the manufacturing process.'),
17917 (1, 't', 1642, 'Number of units in the width of a layer', 'Number of units which make up the width of a layer.'),
17918 (1, 't', 1643, 'Number of units in the depth of a layer', 'Number of units which make up the depth of a layer.'),
17919 (1, 't', 1644, 'Return to warehouse', 'A quantity of products sent back to the warehouse.'),
17920 (1, 't', 1645, 'Return to the manufacturer', 'A quantity of products sent back from the manufacturer.'),
17921 (1, 't', 1646, 'Delta quantity', 'An increment or decrement to a quantity.'),
17922 (1, 't', 1647, 'Quantity moved between outlets', 'A quantity of products moved between outlets.'),
17923 (1, 't', 1648, 'Pre-paid invoice annual consumption, estimated', 'The estimated annual consumption used for a prepayment invoice.'),
17924 (1, 't', 1649, 'Total quoted quantity', 'The sum of quoted quantities.'),
17925 (1, 't', 1650, 'Requests pertaining to entity in last 12 months', 'Number of requests received in last 12 months pertaining to the entity.'),
17926 (1, 't', 1651, 'Total inquiry matches', 'Number of instances which correspond with the inquiry.'),
17927 (1, 't', 1652, 'En route to warehouse quantity',   'A quantity of products that is en route to a warehouse.'),
17928 (1, 't', 1653, 'En route from warehouse quantity', 'A quantity of products that is en route from a warehouse.'),
17929 (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.'),
17930 (1, 't', 1655, 'Not yet ordered quantity', 'The quantity which has not yet been ordered.'),
17931 (1, 't', 1656, 'Net reserve power', 'The reserve power available for the net.'),
17932 (1, 't', 1657, 'Maximum number of units per shelf', 'Maximum number of units of a product that can be placed on a shelf.'),
17933 (1, 't', 1658, 'Stowaway', 'Number of stowaway(s) on a conveyance.'),
17934 (1, 't', 1659, 'Tug', 'The number of tugboat(s).'),
17935 (1, 't', 1660, 'Maximum quantity capability of the package', 'Maximum quantity of a product that can be contained in a package.'),
17936 (1, 't', 1661, 'Calculated', 'The calculated quantity.'),
17937 (1, 't', 1662, 'Monthly volume, estimated', 'Volume estimated for a month.'),
17938 (1, 't', 1663, 'Total number of persons', 'Quantity representing the total number of persons.'),
17939 (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.'),
17940 (1, 't', 1665, 'Deducted tariff quantity',   'Quantity deducted from tariff quantity to reckon duty/tax/fee assessment bases.'),
17941 (1, 't', 1666, 'Advised but not arrived',    'Goods are advised by the consignor or supplier, but have not yet arrived at the destination.'),
17942 (1, 't', 1667, 'Received but not available', 'Goods have been received in the arrival area but are not yet available.'),
17943 (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.'),
17944 (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.'),
17945 (1, 't', 1670, 'Chargeable number of trailers', 'The number of trailers on which charges are based.'),
17946 (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.'),
17947 (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.'),
17948 (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.'),
17949 (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.'),
17950 (1, 't', 1675, 'Agreed maximum buying quantity', 'The agreed maximum quantity of the trade item that may be purchased.'),
17951 (1, 't', 1676, 'Agreed minimum buying quantity', 'The agreed minimum quantity of the trade item that may be purchased.'),
17952 (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.'),
17953 (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.'),
17954 (1, 't', 1679, 'Marine Diesel Oil bunkers, loaded',                  'Number of Marine Diesel Oil (MDO) bunkers taken on in the port.'),
17955 (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.'),
17956 (1, 't', 1681, 'Intermediate Fuel Oil bunkers, loaded',              'Number of Intermediate Fuel Oil (IFO) bunkers taken on in the port.'),
17957 (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.'),
17958 (1, 't', 1683, 'Bunker C bunkers, loaded', 'Number of Bunker C, or Number 6 fuel oil bunkers, taken on in the port.'),
17959 (1, 't', 1684, 'Number of individual units within the smallest packaging', 'unit Total number of individual units contained within the smallest unit of packaging.'),
17960 (1, 't', 1685, 'Percentage of constituent element', 'The part of a product or material that is composed of the constituent element, as a percentage.'),
17961 (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).'),
17962 (1, 't', 1687, 'Regulated commodity count', 'The number of regulated items.'),
17963 (1, 't', 1688, 'Number of passengers, embarking', 'The number of passengers going aboard a conveyance.'),
17964 (1, 't', 1689, 'Number of passengers, disembarking', 'The number of passengers disembarking the conveyance.'),
17965 (1, 't', 1690, 'Constituent element or component quantity', 'The specific quantity of the identified constituent element.')
17966 ;
17967 -- ZZZ, 'Mutually defined', 'As agreed by the trading partners.'),
17968
17969 CREATE TABLE acq.serial_claim (
17970     id     SERIAL           PRIMARY KEY,
17971     type   INT              NOT NULL REFERENCES acq.claim_type
17972                                      DEFERRABLE INITIALLY DEFERRED,
17973     item    BIGINT          NOT NULL REFERENCES serial.item
17974                                      DEFERRABLE INITIALLY DEFERRED
17975 );
17976
17977 CREATE INDEX serial_claim_lid_idx ON acq.serial_claim( item );
17978
17979 CREATE TABLE acq.serial_claim_event (
17980     id             BIGSERIAL        PRIMARY KEY,
17981     type           INT              NOT NULL REFERENCES acq.claim_event_type
17982                                              DEFERRABLE INITIALLY DEFERRED,
17983     claim          SERIAL           NOT NULL REFERENCES acq.serial_claim
17984                                              DEFERRABLE INITIALLY DEFERRED,
17985     event_date     TIMESTAMPTZ      NOT NULL DEFAULT now(),
17986     creator        INT              NOT NULL REFERENCES actor.usr
17987                                              DEFERRABLE INITIALLY DEFERRED,
17988     note           TEXT
17989 );
17990
17991 CREATE INDEX serial_claim_event_claim_date_idx ON acq.serial_claim_event( claim, event_date );
17992
17993 ALTER TABLE asset.stat_cat ADD COLUMN required BOOL NOT NULL DEFAULT FALSE;
17994
17995 -- now what about the auditor.*_lifecycle views??
17996
17997 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
17998     (26, 'identifier', 'tcn', oils_i18n_gettext(26, 'Title Control Number', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='a']$$ );
17999 INSERT INTO config.metabib_field ( id, field_class, name, label, format, xpath ) VALUES
18000     (27, 'identifier', 'bibid', oils_i18n_gettext(27, 'Internal ID', 'cmf', 'label'), 'marcxml', $$//marc:datafield[@tag='901']/marc:subfield[@code='c']$$ );
18001 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.tcn','identifier', 26);
18002 INSERT INTO config.metabib_search_alias (alias,field_class,field) VALUES ('eg.bibid','identifier', 27);
18003
18004 CREATE TABLE asset.call_number_class (
18005     id             bigserial     PRIMARY KEY,
18006     name           TEXT          NOT NULL,
18007     normalizer     TEXT          NOT NULL DEFAULT 'asset.normalize_generic',
18008     field          TEXT          NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18009 );
18010
18011 COMMENT ON TABLE asset.call_number_class IS $$
18012 Defines the call number normalization database functions in the "normalizer"
18013 column and the tag/subfield combinations to use to lookup the call number in
18014 the "field" column for a given classification scheme. Tag/subfield combinations
18015 are delimited by commas.
18016 $$;
18017
18018 INSERT INTO asset.call_number_class (name, normalizer) VALUES 
18019     ('Generic', 'asset.label_normalizer_generic'),
18020     ('Dewey (DDC)', 'asset.label_normalizer_dewey'),
18021     ('Library of Congress (LC)', 'asset.label_normalizer_lc')
18022 ;
18023
18024 -- Generic fields
18025 UPDATE asset.call_number_class
18026     SET field = '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'
18027     WHERE id = 1
18028 ;
18029
18030 -- Dewey fields
18031 UPDATE asset.call_number_class
18032     SET field = '080ab,082ab'
18033     WHERE id = 2
18034 ;
18035
18036 -- LC fields
18037 UPDATE asset.call_number_class
18038     SET field = '050ab,055ab'
18039     WHERE id = 3
18040 ;
18041  
18042 ALTER TABLE asset.call_number
18043         ADD COLUMN label_class BIGINT DEFAULT 1 NOT NULL
18044                 REFERENCES asset.call_number_class(id)
18045                 DEFERRABLE INITIALLY DEFERRED;
18046
18047 ALTER TABLE asset.call_number
18048         ADD COLUMN label_sortkey TEXT;
18049
18050 CREATE INDEX asset_call_number_label_sortkey
18051         ON asset.call_number(oils_text_as_bytea(label_sortkey));
18052
18053 ALTER TABLE auditor.asset_call_number_history
18054         ADD COLUMN label_class BIGINT;
18055
18056 ALTER TABLE auditor.asset_call_number_history
18057         ADD COLUMN label_sortkey TEXT;
18058
18059 -- Pick up the new columns in dependent views
18060
18061 DROP VIEW auditor.asset_call_number_lifecycle;
18062
18063 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18064
18065 DROP VIEW auditor.asset_call_number_lifecycle;
18066
18067 SELECT auditor.create_auditor_lifecycle( 'asset', 'call_number' );
18068
18069 DROP VIEW IF EXISTS stats.fleshed_call_number;
18070
18071 CREATE VIEW stats.fleshed_call_number AS
18072         SELECT  cn.*,
18073             CAST(cn.create_date AS DATE) AS create_date_day,
18074         CAST(cn.edit_date AS DATE) AS edit_date_day,
18075         DATE_TRUNC('hour', cn.create_date) AS create_date_hour,
18076         DATE_TRUNC('hour', cn.edit_date) AS edit_date_hour,
18077             rd.item_lang,
18078                 rd.item_type,
18079                 rd.item_form
18080         FROM    asset.call_number cn
18081                 JOIN metabib.rec_descriptor rd ON (rd.record = cn.record);
18082
18083 CREATE OR REPLACE FUNCTION asset.label_normalizer() RETURNS TRIGGER AS $func$
18084 DECLARE
18085     sortkey        TEXT := '';
18086 BEGIN
18087     sortkey := NEW.label_sortkey;
18088
18089     EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
18090        quote_literal( NEW.label ) || ')'
18091        FROM asset.call_number_class acnc
18092        WHERE acnc.id = NEW.label_class
18093        INTO sortkey;
18094
18095     NEW.label_sortkey = sortkey;
18096
18097     RETURN NEW;
18098 END;
18099 $func$ LANGUAGE PLPGSQL;
18100
18101 CREATE OR REPLACE FUNCTION asset.label_normalizer_generic(TEXT) RETURNS TEXT AS $func$
18102     # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
18103     # thus could probably be considered a derived work, although nothing was
18104     # directly copied - but to err on the safe side of providing attribution:
18105     # Copyright (C) 2007 LibLime
18106     # Licensed under the GPL v2 or later
18107
18108     use strict;
18109     use warnings;
18110
18111     # Converts the callnumber to uppercase
18112     # Strips spaces from start and end of the call number
18113     # Converts anything other than letters, digits, and periods into underscores
18114     # Collapses multiple underscores into a single underscore
18115     my $callnum = uc(shift);
18116     $callnum =~ s/^\s//g;
18117     $callnum =~ s/\s$//g;
18118     $callnum =~ s/[^A-Z0-9_.]/_/g;
18119     $callnum =~ s/_{2,}/_/g;
18120
18121     return $callnum;
18122 $func$ LANGUAGE PLPERLU;
18123
18124 CREATE OR REPLACE FUNCTION asset.label_normalizer_dewey(TEXT) RETURNS TEXT AS $func$
18125     # Derived from the Koha C4::ClassSortRoutine::Dewey module
18126     # Copyright (C) 2007 LibLime
18127     # Licensed under the GPL v2 or later
18128
18129     use strict;
18130     use warnings;
18131
18132     my $init = uc(shift);
18133     $init =~ s/^\s+//;
18134     $init =~ s/\s+$//;
18135     $init =~ s!/!!g;
18136     $init =~ s/^([\p{IsAlpha}]+)/$1 /;
18137     my @tokens = split /\.|\s+/, $init;
18138     my $digit_group_count = 0;
18139     for (my $i = 0; $i <= $#tokens; $i++) {
18140         if ($tokens[$i] =~ /^\d+$/) {
18141             $digit_group_count++;
18142             if (2 == $digit_group_count) {
18143                 $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
18144                 $tokens[$i] =~ tr/ /0/;
18145             }
18146         }
18147     }
18148     my $key = join("_", @tokens);
18149     $key =~ s/[^\p{IsAlnum}_]//g;
18150
18151     return $key;
18152
18153 $func$ LANGUAGE PLPERLU;
18154
18155 CREATE OR REPLACE FUNCTION asset.label_normalizer_lc(TEXT) RETURNS TEXT AS $func$
18156     use strict;
18157     use warnings;
18158
18159     # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
18160     # The author hopes to upload it to CPAN some day, which would make our lives easier
18161     use Library::CallNumber::LC;
18162
18163     my $callnum = Library::CallNumber::LC->new(shift);
18164     return $callnum->normalize();
18165
18166 $func$ LANGUAGE PLPERLU;
18167
18168 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$
18169 DECLARE
18170     ans RECORD;
18171     trans INT;
18172 BEGIN
18173     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;
18174
18175     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
18176         RETURN QUERY
18177         SELECT  ans.depth,
18178                 ans.id,
18179                 COUNT( av.id ),
18180                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18181                 COUNT( av.id ),
18182                 trans
18183           FROM
18184                 actor.org_unit_descendants(ans.id) d
18185                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18186                 JOIN asset.copy cp ON (cp.id = av.id)
18187           GROUP BY 1,2,6;
18188
18189         IF NOT FOUND THEN
18190             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18191         END IF;
18192
18193     END LOOP;
18194
18195     RETURN;
18196 END;
18197 $f$ LANGUAGE PLPGSQL;
18198
18199 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$
18200 DECLARE
18201     ans RECORD;
18202     trans INT;
18203 BEGIN
18204     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;
18205
18206     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18207         RETURN QUERY
18208         SELECT  -1,
18209                 ans.id,
18210                 COUNT( av.id ),
18211                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18212                 COUNT( av.id ),
18213                 trans
18214           FROM
18215                 actor.org_unit_descendants(ans.id) d
18216                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18217                 JOIN asset.copy cp ON (cp.id = av.id)
18218           GROUP BY 1,2,6;
18219
18220         IF NOT FOUND THEN
18221             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18222         END IF;
18223
18224     END LOOP;
18225
18226     RETURN;
18227 END;
18228 $f$ LANGUAGE PLPGSQL;
18229
18230 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$
18231 DECLARE
18232     ans RECORD;
18233     trans INT;
18234 BEGIN
18235     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;
18236
18237     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
18238         RETURN QUERY
18239         SELECT  ans.depth,
18240                 ans.id,
18241                 COUNT( cp.id ),
18242                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18243                 COUNT( cp.id ),
18244                 trans
18245           FROM
18246                 actor.org_unit_descendants(ans.id) d
18247                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18248                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18249           GROUP BY 1,2,6;
18250
18251         IF NOT FOUND THEN
18252             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18253         END IF;
18254
18255     END LOOP;
18256
18257     RETURN;
18258 END;
18259 $f$ LANGUAGE PLPGSQL;
18260
18261 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$
18262 DECLARE
18263     ans RECORD;
18264     trans INT;
18265 BEGIN
18266     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;
18267
18268     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18269         RETURN QUERY
18270         SELECT  -1,
18271                 ans.id,
18272                 COUNT( cp.id ),
18273                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18274                 COUNT( cp.id ),
18275                 trans
18276           FROM
18277                 actor.org_unit_descendants(ans.id) d
18278                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18279                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18280           GROUP BY 1,2,6;
18281
18282         IF NOT FOUND THEN
18283             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18284         END IF;
18285
18286     END LOOP;
18287
18288     RETURN;
18289 END;
18290 $f$ LANGUAGE PLPGSQL;
18291
18292 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$
18293 BEGIN
18294     IF staff IS TRUE THEN
18295         IF place > 0 THEN
18296             RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, record );
18297         ELSE
18298             RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, record );
18299         END IF;
18300     ELSE
18301         IF place > 0 THEN
18302             RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, record );
18303         ELSE
18304             RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, record );
18305         END IF;
18306     END IF;
18307
18308     RETURN;
18309 END;
18310 $f$ LANGUAGE PLPGSQL;
18311
18312 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$
18313 DECLARE
18314     ans RECORD;
18315     trans INT;
18316 BEGIN
18317     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;
18318
18319     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
18320         RETURN QUERY
18321         SELECT  ans.depth,
18322                 ans.id,
18323                 COUNT( av.id ),
18324                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18325                 COUNT( av.id ),
18326                 trans
18327           FROM
18328                 actor.org_unit_descendants(ans.id) d
18329                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18330                 JOIN asset.copy cp ON (cp.id = av.id)
18331                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18332           GROUP BY 1,2,6;
18333
18334         IF NOT FOUND THEN
18335             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18336         END IF;
18337
18338     END LOOP;
18339
18340     RETURN;
18341 END;
18342 $f$ LANGUAGE PLPGSQL;
18343
18344 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$
18345 DECLARE
18346     ans RECORD;
18347     trans INT;
18348 BEGIN
18349     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;
18350
18351     FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18352         RETURN QUERY
18353         SELECT  -1,
18354                 ans.id,
18355                 COUNT( av.id ),
18356                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18357                 COUNT( av.id ),
18358                 trans
18359           FROM
18360                 actor.org_unit_descendants(ans.id) d
18361                 JOIN asset.opac_visible_copies av ON (av.record = record AND av.circ_lib = d.id)
18362                 JOIN asset.copy cp ON (cp.id = av.id)
18363                 JOIN metabib.metarecord_source_map m ON (m.source = av.record)
18364           GROUP BY 1,2,6;
18365
18366         IF NOT FOUND THEN
18367             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18368         END IF;
18369
18370     END LOOP;
18371
18372     RETURN;
18373 END;
18374 $f$ LANGUAGE PLPGSQL;
18375
18376 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$
18377 DECLARE
18378     ans RECORD;
18379     trans INT;
18380 BEGIN
18381     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;
18382
18383     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
18384         RETURN QUERY
18385         SELECT  ans.depth,
18386                 ans.id,
18387                 COUNT( cp.id ),
18388                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18389                 COUNT( cp.id ),
18390                 trans
18391           FROM
18392                 actor.org_unit_descendants(ans.id) d
18393                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18394                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18395                 JOIN metabib.metarecord_source_map m ON (m.source = cn.record)
18396           GROUP BY 1,2,6;
18397
18398         IF NOT FOUND THEN
18399             RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
18400         END IF;
18401
18402     END LOOP;
18403
18404     RETURN;
18405 END;
18406 $f$ LANGUAGE PLPGSQL;
18407
18408 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$
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.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
18416         RETURN QUERY
18417         SELECT  -1,
18418                 ans.id,
18419                 COUNT( cp.id ),
18420                 SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
18421                 COUNT( cp.id ),
18422                 trans
18423           FROM
18424                 actor.org_unit_descendants(ans.id) d
18425                 JOIN asset.copy cp ON (cp.circ_lib = d.id)
18426                 JOIN asset.call_number cn ON (cn.record = record AND cn.id = cp.call_number)
18427                 JOIN metabib.metarecord_source_map m ON (m.source = cn.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.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$
18441 BEGIN
18442     IF staff IS TRUE THEN
18443         IF place > 0 THEN
18444             RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, record );
18445         ELSE
18446             RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, record );
18447         END IF;
18448     ELSE
18449         IF place > 0 THEN
18450             RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, record );
18451         ELSE
18452             RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, record );
18453         END IF;
18454     END IF;
18455
18456     RETURN;
18457 END;
18458 $f$ LANGUAGE PLPGSQL;
18459
18460 -- No transaction is required
18461
18462 -- Triggers on the vandelay.queued_*_record tables delete entries from
18463 -- the associated vandelay.queued_*_record_attr tables based on the record's
18464 -- ID; create an index on that column to avoid sequential scans for each
18465 -- queued record that is deleted
18466 CREATE INDEX queued_bib_record_attr_record_idx ON vandelay.queued_bib_record_attr (record);
18467 CREATE INDEX queued_authority_record_attr_record_idx ON vandelay.queued_authority_record_attr (record);
18468
18469 -- Avoid sequential scans for queue retrieval operations by providing an
18470 -- index on the queue column
18471 CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
18472 CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
18473
18474 -- Start picking up call number label prefixes and suffixes
18475 -- from asset.copy_location
18476 ALTER TABLE asset.copy_location ADD COLUMN label_prefix TEXT;
18477 ALTER TABLE asset.copy_location ADD COLUMN label_suffix TEXT;
18478
18479 DROP VIEW auditor.asset_copy_lifecycle;
18480
18481 SELECT auditor.create_auditor_lifecycle( 'asset', 'copy' );
18482
18483 ALTER TABLE reporter.report RENAME COLUMN recurance TO recurrence;
18484
18485 -- Let's not break existing reports
18486 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recuring(.*)$', E'\\1recurring\\2') WHERE data LIKE '%recuring%';
18487 UPDATE reporter.template SET data = REGEXP_REPLACE(data, E'^(.*)recurance(.*)$', E'\\1recurrence\\2') WHERE data LIKE '%recurance%';
18488
18489 -- Need to recreate this view with DISTINCT calls to ARRAY_ACCUM, thus avoiding duplicated ISBN and ISSN values
18490 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
18491 SELECT  r.id,
18492     r.fingerprint,
18493     r.quality,
18494     r.tcn_source,
18495     r.tcn_value,
18496     FIRST(title.value) AS title,
18497     FIRST(author.value) AS author,
18498     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT publisher.value), ', ') AS publisher,
18499     ARRAY_TO_STRING(ARRAY_ACCUM( DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$) ), ', ') AS pubdate,
18500     ARRAY_ACCUM( DISTINCT SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18501     ARRAY_ACCUM( DISTINCT SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn
18502   FROM  biblio.record_entry r
18503     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18504     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
18505     LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18506     LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18507     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18508     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18509   GROUP BY 1,2,3,4,5;
18510
18511 -- Correct the ISSN array definition for reporter.simple_record
18512
18513 CREATE OR REPLACE VIEW reporter.simple_record AS
18514 SELECT  r.id,
18515         s.metarecord,
18516         r.fingerprint,
18517         r.quality,
18518         r.tcn_source,
18519         r.tcn_value,
18520         title.value AS title,
18521         uniform_title.value AS uniform_title,
18522         author.value AS author,
18523         publisher.value AS publisher,
18524         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
18525         series_title.value AS series_title,
18526         series_statement.value AS series_statement,
18527         summary.value AS summary,
18528         ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn,
18529         ARRAY_ACCUM( REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
18530         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
18531         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
18532         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
18533         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
18534         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
18535         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
18536   FROM  biblio.record_entry r
18537         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
18538         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
18539         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
18540         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
18541         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b')
18542         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c')
18543         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
18544         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
18545         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')
18546         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
18547         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
18548   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
18549
18550 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
18551     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
18552 $$ LANGUAGE SQL;
18553
18554 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
18555
18556     DELETE FROM reporter.materialized_simple_record;
18557
18558     INSERT INTO reporter.materialized_simple_record
18559         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
18560         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
18561
18562     CREATE TRIGGER bbb_simple_rec_trigger
18563         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
18564         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
18565
18566 $$ LANGUAGE SQL;
18567
18568 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
18569 BEGIN
18570     IF TG_OP = 'DELETE' THEN
18571         PERFORM reporter.simple_rec_delete(NEW.id);
18572     ELSE
18573         PERFORM reporter.simple_rec_update(NEW.id);
18574     END IF;
18575
18576     RETURN NEW;
18577 END;
18578 $func$ LANGUAGE PLPGSQL;
18579
18580 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 ();
18581
18582 ALTER TABLE extend_reporter.legacy_circ_count DROP CONSTRAINT legacy_circ_count_id_fkey;
18583
18584 CREATE INDEX asset_copy_note_owning_copy_idx ON asset.copy_note ( owning_copy );
18585
18586 UPDATE config.org_unit_setting_type
18587     SET view_perm = (SELECT id FROM permission.perm_list
18588         WHERE code = 'VIEW_CREDIT_CARD_PROCESSING' LIMIT 1)
18589     WHERE name LIKE 'credit.processor%' AND view_perm IS NULL;
18590
18591 UPDATE config.org_unit_setting_type
18592     SET update_perm = (SELECT id FROM permission.perm_list
18593         WHERE code = 'ADMIN_CREDIT_CARD_PROCESSING' LIMIT 1)
18594     WHERE name LIKE 'credit.processor%' AND update_perm IS NULL;
18595
18596 INSERT INTO config.org_unit_setting_type (name, label, description, datatype)
18597     VALUES (
18598         'opac.fully_compressed_serial_holdings',
18599         'OPAC: Use fully compressed serial holdings',
18600         'Show fully compressed serial holdings for all libraries at and below
18601         the current context unit',
18602         'bool'
18603     );
18604
18605 CREATE OR REPLACE FUNCTION authority.normalize_heading( TEXT ) RETURNS TEXT AS $func$
18606     use strict;
18607     use warnings;
18608
18609     use utf8;
18610     use MARC::Record;
18611     use MARC::File::XML (BinaryEncoding => 'UTF8');
18612     use UUID::Tiny ':std';
18613
18614     my $xml = shift() or return undef;
18615
18616     my $r;
18617
18618     # Prevent errors in XML parsing from blowing out ungracefully
18619     eval {
18620         $r = MARC::Record->new_from_xml( $xml );
18621         1;
18622     } or do {
18623        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18624     };
18625
18626     if (!$r) {
18627        return 'BAD_MARCXML_' . create_uuid_as_string(UUID_MD5, $xml);
18628     }
18629
18630     # From http://www.loc.gov/standards/sourcelist/subject.html
18631     my $thes_code_map = {
18632         a => 'lcsh',
18633         b => 'lcshac',
18634         c => 'mesh',
18635         d => 'nal',
18636         k => 'cash',
18637         n => 'notapplicable',
18638         r => 'aat',
18639         s => 'sears',
18640         v => 'rvm',
18641     };
18642
18643     # Default to "No attempt to code" if the leader is horribly broken
18644     my $fixed_field = $r->field('008');
18645     my $thes_char = '|';
18646     if ($fixed_field) {
18647         $thes_char = substr($fixed_field->data(), 11, 1) || '|';
18648     }
18649
18650     my $thes_code = 'UNDEFINED';
18651
18652     if ($thes_char eq 'z') {
18653         # Grab the 040 $f per http://www.loc.gov/marc/authority/ad040.html
18654         $thes_code = $r->subfield('040', 'f') || 'UNDEFINED';
18655     } elsif ($thes_code_map->{$thes_char}) {
18656         $thes_code = $thes_code_map->{$thes_char};
18657     }
18658
18659     my $auth_txt = '';
18660     my $head = $r->field('1..');
18661     if ($head) {
18662         # Concatenate all of these subfields together, prefixed by their code
18663         # to prevent collisions along the lines of "Fiction, North Carolina"
18664         foreach my $sf ($head->subfields()) {
18665             $auth_txt .= '‡' . $sf->[0] . ' ' . $sf->[1];
18666         }
18667     }
18668
18669     # Perhaps better to parameterize the spi and pass as a parameter
18670     $auth_txt =~ s/'//go;
18671
18672     if ($auth_txt) {
18673         my $result = spi_exec_query("SELECT public.naco_normalize('$auth_txt') AS norm_text");
18674         my $norm_txt = $result->{rows}[0]->{norm_text};
18675         return $head->tag() . "_" . $thes_code . " " . $norm_txt;
18676     }
18677
18678     return 'NOHEADING_' . $thes_code . ' ' . create_uuid_as_string(UUID_MD5, $xml);
18679 $func$ LANGUAGE 'plperlu' IMMUTABLE;
18680
18681 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
18682 /**
18683 * Extract the authority heading, thesaurus, and NACO-normalized values
18684 * from an authority record. The primary purpose is to build a unique
18685 * index to defend against duplicated authority records from the same
18686 * thesaurus.
18687 */
18688 $$;
18689
18690 DROP INDEX authority.authority_record_unique_tcn;
18691 ALTER TABLE authority.record_entry DROP COLUMN arn_value;
18692 ALTER TABLE authority.record_entry DROP COLUMN arn_source;
18693
18694 ALTER TABLE acq.provider_contact
18695         ALTER COLUMN name SET NOT NULL;
18696
18697 ALTER TABLE actor.stat_cat
18698         ADD COLUMN usr_summary BOOL NOT NULL DEFAULT FALSE;
18699
18700 -- Recreate some foreign keys that were somehow dropped, probably
18701 -- by some kind of cascade from an inherited table:
18702
18703 ALTER TABLE action.reservation_transit_copy
18704         ADD CONSTRAINT artc_tc_fkey FOREIGN KEY (target_copy)
18705                 REFERENCES booking.resource(id)
18706                 ON DELETE CASCADE
18707                 DEFERRABLE INITIALLY DEFERRED,
18708         ADD CONSTRAINT reservation_transit_copy_reservation_fkey FOREIGN KEY (reservation)
18709                 REFERENCES booking.reservation(id)
18710                 ON DELETE SET NULL
18711                 DEFERRABLE INITIALLY DEFERRED;
18712
18713 CREATE INDEX user_bucket_item_target_user_idx
18714         ON container.user_bucket_item ( target_user );
18715
18716 CREATE INDEX m_c_t_collector_idx
18717         ON money.collections_tracker ( collector );
18718
18719 CREATE INDEX aud_actor_usr_address_hist_id_idx
18720         ON auditor.actor_usr_address_history ( id );
18721
18722 CREATE INDEX aud_actor_usr_hist_id_idx
18723         ON auditor.actor_usr_history ( id );
18724
18725 CREATE INDEX aud_asset_cn_hist_creator_idx
18726         ON auditor.asset_call_number_history ( creator );
18727
18728 CREATE INDEX aud_asset_cn_hist_editor_idx
18729         ON auditor.asset_call_number_history ( editor );
18730
18731 CREATE INDEX aud_asset_cp_hist_creator_idx
18732         ON auditor.asset_copy_history ( creator );
18733
18734 CREATE INDEX aud_asset_cp_hist_editor_idx
18735         ON auditor.asset_copy_history ( editor );
18736
18737 CREATE INDEX aud_bib_rec_entry_hist_creator_idx
18738         ON auditor.biblio_record_entry_history ( creator );
18739
18740 CREATE INDEX aud_bib_rec_entry_hist_editor_idx
18741         ON auditor.biblio_record_entry_history ( editor );
18742
18743 CREATE TABLE action.hold_request_note (
18744
18745     id     BIGSERIAL PRIMARY KEY,
18746     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
18747                               ON DELETE CASCADE
18748                               DEFERRABLE INITIALLY DEFERRED,
18749     title  TEXT      NOT NULL,
18750     body   TEXT      NOT NULL,
18751     slip   BOOL      NOT NULL DEFAULT FALSE,
18752     pub    BOOL      NOT NULL DEFAULT FALSE,
18753     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
18754
18755 );
18756 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
18757
18758 -- Tweak a constraint to add a CASCADE
18759
18760 ALTER TABLE action.hold_notification DROP CONSTRAINT hold_notification_hold_fkey;
18761
18762 ALTER TABLE action.hold_notification
18763         ADD CONSTRAINT hold_notification_hold_fkey
18764                 FOREIGN KEY (hold) REFERENCES action.hold_request (id)
18765                 ON DELETE CASCADE
18766                 DEFERRABLE INITIALLY DEFERRED;
18767
18768 CREATE TRIGGER asset_label_sortkey_trigger
18769     BEFORE UPDATE OR INSERT ON asset.call_number
18770     FOR EACH ROW EXECUTE PROCEDURE asset.label_normalizer();
18771
18772 CREATE OR REPLACE FUNCTION container.clear_all_expired_circ_history_items( )
18773 RETURNS VOID AS $$
18774 --
18775 -- Delete expired circulation bucket items for all users that have
18776 -- a setting for patron.max_reading_list_interval.
18777 --
18778 DECLARE
18779     today        TIMESTAMP WITH TIME ZONE;
18780     threshold    TIMESTAMP WITH TIME ZONE;
18781         usr_setting  RECORD;
18782 BEGIN
18783         SELECT date_trunc( 'day', now() ) INTO today;
18784         --
18785         FOR usr_setting in
18786                 SELECT
18787                         usr,
18788                         value
18789                 FROM
18790                         actor.usr_setting
18791                 WHERE
18792                         name = 'patron.max_reading_list_interval'
18793         LOOP
18794                 --
18795                 -- Make sure the setting is a valid interval
18796                 --
18797                 BEGIN
18798                         threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
18799                 EXCEPTION
18800                         WHEN OTHERS THEN
18801                                 RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
18802                                         usr_setting.usr, usr_setting.value;
18803                                 CONTINUE;
18804                 END;
18805                 --
18806                 --RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
18807                 --
18808         DELETE FROM container.copy_bucket_item
18809         WHERE
18810                 bucket IN
18811                 (
18812                     SELECT
18813                         id
18814                     FROM
18815                         container.copy_bucket
18816                     WHERE
18817                         owner = usr_setting.usr
18818                         AND btype = 'circ_history'
18819                 )
18820                 AND create_time < threshold;
18821         END LOOP;
18822         --
18823 END;
18824 $$ LANGUAGE plpgsql;
18825
18826 COMMENT ON FUNCTION container.clear_all_expired_circ_history_items( ) IS $$
18827 /*
18828  * Delete expired circulation bucket items for all users that have
18829  * a setting for patron.max_reading_list_interval.
18830 */
18831 $$;
18832
18833 CREATE OR REPLACE FUNCTION container.clear_expired_circ_history_items( 
18834          ac_usr IN INTEGER
18835 ) RETURNS VOID AS $$
18836 --
18837 -- Delete old circulation bucket items for a specified user.
18838 -- "Old" means older than the interval specified by a
18839 -- user-level setting, if it is so specified.
18840 --
18841 DECLARE
18842     threshold TIMESTAMP WITH TIME ZONE;
18843 BEGIN
18844         -- Sanity check
18845         IF ac_usr IS NULL THEN
18846                 RETURN;
18847         END IF;
18848         -- Determine the threshold date that defines "old".  Subtract the
18849         -- interval from the system date, then truncate to midnight.
18850         SELECT
18851                 date_trunc( 
18852                         'day',
18853                         now() - CAST( translate( value, '"', '' ) AS INTERVAL )
18854                 )
18855         INTO
18856                 threshold
18857         FROM
18858                 actor.usr_setting
18859         WHERE
18860                 usr = ac_usr
18861                 AND name = 'patron.max_reading_list_interval';
18862         --
18863         IF threshold is null THEN
18864                 -- No interval defined; don't delete anything
18865                 -- RAISE NOTICE 'No interval defined for user %', ac_usr;
18866                 return;
18867         END IF;
18868         --
18869         -- RAISE NOTICE 'Date threshold: %', threshold;
18870         --
18871         -- Threshold found; do the delete
18872         delete from container.copy_bucket_item
18873         where
18874                 bucket in
18875                 (
18876                         select
18877                                 id
18878                         from
18879                                 container.copy_bucket
18880                         where
18881                                 owner = ac_usr
18882                                 and btype = 'circ_history'
18883                 )
18884                 and create_time < threshold;
18885         --
18886         RETURN;
18887 END;
18888 $$ LANGUAGE plpgsql;
18889
18890 COMMENT ON FUNCTION container.clear_expired_circ_history_items( INTEGER ) IS $$
18891 /*
18892  * Delete old circulation bucket items for a specified user.
18893  * "Old" means older than the interval specified by a
18894  * user-level setting, if it is so specified.
18895 */
18896 $$;
18897
18898 CREATE OR REPLACE VIEW reporter.hold_request_record AS
18899 SELECT  id,
18900     target,
18901     hold_type,
18902     CASE
18903         WHEN hold_type = 'T'
18904             THEN target
18905         WHEN hold_type = 'I'
18906             THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
18907         WHEN hold_type = 'V'
18908             THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
18909         WHEN hold_type IN ('C','R','F')
18910             THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
18911         WHEN hold_type = 'M'
18912             THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
18913     END AS bib_record
18914   FROM  action.hold_request ahr;
18915
18916 UPDATE  metabib.rec_descriptor
18917   SET   date1=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date1, ''), E'\\D', '0', 'g')::INT,0)::TEXT,4,'0'),
18918         date2=LPAD(NULLIF(REGEXP_REPLACE(NULLIF(date2, ''), E'\\D', '9', 'g')::INT,9999)::TEXT,4,'0');
18919
18920 -- Change some ints to bigints:
18921
18922 ALTER TABLE container.biblio_record_entry_bucket_item
18923         ALTER COLUMN target_biblio_record_entry SET DATA TYPE bigint;
18924
18925 ALTER TABLE vandelay.queued_bib_record
18926         ALTER COLUMN imported_as SET DATA TYPE bigint;
18927
18928 ALTER TABLE action.hold_copy_map
18929         ALTER COLUMN id SET DATA TYPE bigint;
18930
18931 -- Make due times get pushed to 23:59:59 on insert OR update
18932 DROP TRIGGER IF EXISTS push_due_date_tgr ON action.circulation;
18933 CREATE TRIGGER push_due_date_tgr BEFORE INSERT OR UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE action.push_circ_due_time();
18934
18935 INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove )
18936 SELECT 'upc', 'UPC', '//*[@tag="024" and @ind1="1"]/*[@code="a"]', $r$(?:-|\s.+$)$r$
18937 WHERE NOT EXISTS (
18938     SELECT 1 FROM acq.lineitem_marc_attr_definition WHERE code = 'upc'
18939 );  
18940
18941 COMMIT;
18942
18943 -- Some operations go outside of the transaction, because they may
18944 -- legitimately fail.
18945
18946 \qecho ALTERs of auditor.action_hold_request_history will fail if the table
18947 \qecho doesn't exist; ignore those errors if they occur.
18948
18949 ALTER TABLE auditor.action_hold_request_history ADD COLUMN cut_in_line BOOL;
18950
18951 ALTER TABLE auditor.action_hold_request_history
18952 ADD COLUMN mint_condition boolean NOT NULL DEFAULT TRUE;
18953
18954 ALTER TABLE auditor.action_hold_request_history
18955 ADD COLUMN shelf_expire_time TIMESTAMPTZ;
18956
18957 \qecho Outside of the transaction: adding indexes that may or may not exist.
18958 \qecho If any of these CREATE INDEX statements fails because the index already
18959 \qecho exists, ignore the failure.
18960
18961 CREATE INDEX acq_picklist_owner_idx   ON acq.picklist ( owner );
18962 CREATE INDEX acq_picklist_creator_idx ON acq.picklist ( creator );
18963 CREATE INDEX acq_picklist_editor_idx  ON acq.picklist ( editor );
18964 CREATE INDEX acq_po_note_creator_idx  ON acq.po_note ( creator );
18965 CREATE INDEX acq_po_note_editor_idx   ON acq.po_note ( editor );
18966 CREATE INDEX fund_alloc_allocator_idx ON acq.fund_allocation ( allocator );
18967 CREATE INDEX li_creator_idx   ON acq.lineitem ( creator );
18968 CREATE INDEX li_editor_idx    ON acq.lineitem ( editor );
18969 CREATE INDEX li_selector_idx  ON acq.lineitem ( selector );
18970 CREATE INDEX li_note_creator_idx  ON acq.lineitem_note ( creator );
18971 CREATE INDEX li_note_editor_idx   ON acq.lineitem_note ( editor );
18972 CREATE INDEX li_usr_attr_def_usr_idx  ON acq.lineitem_usr_attr_definition ( usr );
18973 CREATE INDEX po_editor_idx   ON acq.purchase_order ( editor );
18974 CREATE INDEX po_creator_idx  ON acq.purchase_order ( creator );
18975 CREATE INDEX acq_po_org_name_order_date_idx ON acq.purchase_order( ordering_agency, name, order_date );
18976 CREATE INDEX action_in_house_use_staff_idx  ON action.in_house_use ( staff );
18977 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
18978 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
18979 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
18980 CREATE INDEX ahn_notify_staff_idx           ON action.hold_notification ( notify_staff );
18981 CREATE INDEX circ_all_usr_idx               ON action.circulation ( usr );
18982 CREATE INDEX circ_circ_staff_idx            ON action.circulation ( circ_staff );
18983 CREATE INDEX circ_checkin_staff_idx         ON action.circulation ( checkin_staff );
18984 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
18985 CREATE INDEX hold_request_requestor_idx     ON action.hold_request ( requestor );
18986 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
18987 CREATE INDEX actor_usr_note_creator_idx     ON actor.usr_note ( creator );
18988 CREATE INDEX actor_usr_standing_penalty_staff_idx ON actor.usr_standing_penalty ( staff );
18989 CREATE INDEX usr_org_unit_opt_in_staff_idx  ON actor.usr_org_unit_opt_in ( staff );
18990 CREATE INDEX asset_call_number_note_creator_idx ON asset.call_number_note ( creator );
18991 CREATE INDEX asset_copy_note_creator_idx    ON asset.copy_note ( creator );
18992 CREATE INDEX cp_creator_idx                 ON asset.copy ( creator );
18993 CREATE INDEX cp_editor_idx                  ON asset.copy ( editor );
18994
18995 CREATE INDEX actor_card_barcode_lower_idx ON actor.card (lower(barcode));
18996
18997 DROP INDEX IF EXISTS authority.unique_by_heading_and_thesaurus;
18998
18999 \qecho If the following CREATE INDEX fails, It will be necessary to do some
19000 \qecho data cleanup as described in the comments.
19001
19002 CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
19003     ON authority.record_entry (authority.normalize_heading(marc))
19004         WHERE deleted IS FALSE or deleted = FALSE;
19005
19006 -- If the unique index fails, uncomment the following to create
19007 -- a regular index that will help find the duplicates in a hurry:
19008 --CREATE INDEX by_heading_and_thesaurus
19009 --    ON authority.record_entry (authority.normalize_heading(marc))
19010 --    WHERE deleted IS FALSE or deleted = FALSE
19011 --;
19012
19013 -- Then find the duplicates like so to get an idea of how much
19014 -- pain you're looking at to clean things up:
19015 --SELECT id, authority.normalize_heading(marc)
19016 --    FROM authority.record_entry
19017 --    WHERE authority.normalize_heading(marc) IN (
19018 --        SELECT authority.normalize_heading(marc)
19019 --        FROM authority.record_entry
19020 --        GROUP BY authority.normalize_heading(marc)
19021 --        HAVING COUNT(*) > 1
19022 --    )
19023 --;
19024
19025 -- Once you have removed the duplicates and the CREATE UNIQUE INDEX
19026 -- statement succeeds, drop the temporary index to avoid unnecessary
19027 -- duplication:
19028 -- DROP INDEX authority.by_heading_and_thesaurus;
19029
19030 -- 0448.data.trigger.circ.staff_age_to_lost.sql
19031
19032 INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES 
19033     (   'circ.staff_age_to_lost',
19034         'circ', 
19035         oils_i18n_gettext(
19036             'circ.staff_age_to_lost',
19037             'An overdue circulation should be aged to a Lost status.',
19038             'ath',
19039             'description'
19040         ), 
19041         TRUE
19042     )
19043 ;
19044
19045 INSERT INTO action_trigger.event_definition (
19046         id,
19047         active,
19048         owner,
19049         name,
19050         hook,
19051         validator,
19052         reactor,
19053         delay_field
19054     ) VALUES (
19055         36,
19056         FALSE,
19057         1,
19058         'circ.staff_age_to_lost',
19059         'circ.staff_age_to_lost',
19060         'CircIsOverdue',
19061         'MarkItemLost',
19062         'due_date'
19063     )
19064 ;
19065
19066 -- Speed up item-age browse axis (new books feed)
19067 CREATE INDEX cp_create_date  ON asset.copy (create_date);
19068
19069 -- Speed up call number browsing
19070 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;
19071
19072 \qecho Upgrade script completed.
19073 \qecho But wait, there's more: please run reingest-1.6-2.0.pl
19074 \qecho in order to create an SQL script to run to partially reindex 
19075 \qecho the bib records; this is required to make the new facet
19076 \qecho sidebar in OPAC search results work and to upgrade the keyword 
19077 \qecho indexes to use the revised NACO normalization routine.